【问题标题】:Rotated rectangle rasterisation algorithm旋转矩形光栅化算法
【发布时间】:2014-05-09 06:03:50
【问题描述】:

简而言之:我想做一个非近似版本的 Bresenham 线算法,但针对的是矩形而不是线,其点不一定与网格对齐。



给定一个正方形网格和一个包含四个非网格对齐点的矩形,我想找到一个列表,其中包含该矩形部分或完全覆盖的所有网格正方形。

Bresenham 的线算法是近似的 - 并非所有部分覆盖的正方形都被识别出来。我正在寻找一种“完美”的算法,它没有误报或误报。

【问题讨论】:

  • 你现在如何光栅化矩形? (见stackoverflow.com/a/19078088/2521214)。如果您使用一组平行线来填充该区域,则会出现伪影(孔洞或透明的彩​​色区域)。如果您不想实现多边形光栅化,那么处理孔洞的技巧很少。只需为内线设置 2 或 3 个像素而不是 1。如果 Bresenham 对您不利,请使用 DDA(我一直在整数版本中使用它,没有任何直接除法或乘法)
  • 那么为什么要精确光栅化矩形呢?您可以以相当的速度使用任何可用的多边形光栅化。那么,您确定要自定义矩形算法吗?

标签: algorithm math graphics language-agnostic geometry


【解决方案1】:

这是一个老问题,但我已经解决了这个问题(C++)

https://github.com/feelinfine/tracer

也许对某人有用

(对不起我的英语不好)

单线追踪

template <typename PointType>
std::set<V2i> trace_line(const PointType& _start_point, const PointType& _end_point, size_t _cell_size)
{   
    auto point_to_grid_fnc = [_cell_size](const auto& _point)
    {
        return V2i(std::floor((double)_point.x / _cell_size), std::floor((double)_point.y / _cell_size));
    };

    V2i start_cell = point_to_grid_fnc(_start_point);
    V2i last_cell = point_to_grid_fnc(_end_point);

    PointType direction = _end_point - _start_point;

    //Moving direction (cells)
    int step_x = (direction.x >= 0) ? 1 : -1;
    int step_y = (direction.y >= 0) ? 1 : -1;

    //Normalize vector
    double hypot = std::hypot(direction.x, direction.y);
    V2d norm_direction(direction.x / hypot, direction.y / hypot);

    //Distance to the nearest square side
    double near_x = (step_x >= 0) ? (start_cell.x + 1)*_cell_size - _start_point.x : _start_point.x - (start_cell.x*_cell_size);    
    double near_y = (step_y >= 0) ? (start_cell.y + 1)*_cell_size - _start_point.y : _start_point.y - (start_cell.y*_cell_size);

    //How far along the ray we must move to cross the first vertical (ray_step_to_vside) / or horizontal (ray_step_to_hside) grid line
    double ray_step_to_vside = (norm_direction.x != 0) ? near_x / norm_direction.x : std::numeric_limits<double>::max();
    double ray_step_to_hside = (norm_direction.y != 0) ? near_y / norm_direction.y : std::numeric_limits<double>::max();

    //How far along the ray we must move for horizontal (dx)/ or vertical (dy) component of such movement to equal the cell size
    double dx = (norm_direction.x != 0) ? _cell_size / norm_direction.x : std::numeric_limits<double>::max();
    double dy = (norm_direction.y != 0) ? _cell_size / norm_direction.y : std::numeric_limits<double>::max();

    //Tracing loop
    std::set<V2i> cells;
    cells.insert(start_cell);

    V2i current_cell = start_cell;

    size_t grid_bound_x = std::abs(last_cell.x - start_cell.x);
    size_t grid_bound_y = std::abs(last_cell.y - start_cell.y);

    size_t counter = 0;

    while (counter != (grid_bound_x + grid_bound_y))
    {
        if (std::abs(ray_step_to_vside) < std::abs(ray_step_to_hside))
        {
            ray_step_to_vside = ray_step_to_vside + dx; //to the next vertical grid line
            current_cell.x = current_cell.x + step_x;
        }
        else
        {
            ray_step_to_hside = ray_step_to_hside + dy;//to the next horizontal grid line
            current_cell.y = current_cell.y + step_y;
        }

        ++counter;

        cells.insert(current_cell);
    };

    return cells;
}

获取所有单元格

template <typename Container>
std::set<V2i> pick_cells(Container&& _points, size_t _cell_size)
{
    if (_points.size() < 2 || _cell_size <= 0)
        return std::set<V2i>();

    Container points = std::forward<Container>(_points);

    auto add_to_set = [](auto& _set, const auto& _to_append)
    {
        _set.insert(std::cbegin(_to_append), std::cend(_to_append));
    };

    //Outline
    std::set<V2i> cells;

    /*
    for (auto it = std::begin(_points); it != std::prev(std::end(_points)); ++it)
        add_to_set(cells, trace_line(*it, *std::next(it), _cell_size));
    add_to_set(cells, trace_line(_points.back(), _points.front(), _cell_size));
    */

    //Maybe this code works faster
    std::vector<std::future<std::set<V2i> > > results;

    using PointType = decltype(points.cbegin())::value_type;

    for (auto it = points.cbegin(); it != std::prev(points.cend()); ++it)           
        results.push_back(std::async(trace_line<PointType>, *it, *std::next(it), _cell_size));

    results.push_back(std::async(trace_line<PointType>, points.back(), points.front(), _cell_size));    

    for (auto& it : results)
        add_to_set(cells, it.get());

    //Inner
    std::set<V2i> to_add;

    int last_x = cells.begin()->x;
    int counter = cells.begin()->y;

    for (auto& it : cells)
    {
        if (last_x != it.x)
        {
            counter = it.y;
            last_x = it.x;
        }

        if (it.y > counter) 
        {
            for (int i = counter; i < it.y; ++i)
                to_add.insert(V2i(it.x, i));
        }

        ++counter;
    }

    add_to_set(cells, to_add);

    return cells;
}

类型

template <typename _T>
struct V2
{
    _T x, y;

    V2(_T _x = 0, _T _y = 0) : x(_x), y(_y)
    {
    };

    V2 operator-(const V2& _rhs) const
    {
        return V2(x - _rhs.x, y - _rhs.y);
    }

    bool operator==(const V2& _rhs) const
    {
        return (x == _rhs.x) && (y == _rhs.y);
    }

    //for std::set sorting
    bool operator<(const V2& _rhs) const
    {
        return (x == _rhs.x) ? (y < _rhs.y) : (x < _rhs.x);
    }
};

using V2d = V2<double>;
using V2i = V2<int>;

用法

std::vector<V2d> points = { {200, 200}, {400, 400}, {500,100} };
size_t cell_size = 30;
auto cells = pick_cells(points, cell_size);
for (auto& it : cells)
    ...                 //do something with cells

【讨论】:

  • 扫描线跟踪是一个很好的方法,但不要使用这个实现。我已经对其进行了基准测试,它比我实现的应该更慢的不同算法慢了大约 130 倍。此外,无论您使用什么算法直接使用索引,都不要将它们存储在向量或集合中。绝对的性能杀手。
  • 我知道我的实现性能低下。它不是为在严肃的项目中使用而设计的。只是一个大概的想法。所以我可以确认 - 这是一个非常非常缓慢和糟糕的设计解决方案。不要在没有良好重构的情况下使用它:)
【解决方案2】:

您可以使用扫描线方法。矩形是一个封闭的凸多边形,因此存储每个水平扫描线的最左边和最右边的像素就足够了。 (还有顶部和底部的扫描线。)

Bresenham 算法试图绘制一条细的、视觉上令人愉悦的线,而在较小的维度中没有相邻的单元格。我们需要一种算法来访问多边形边缘通过的每个单元格。基本思想是找到每条边的起始单元格(x, y),然后在边与垂直边框相交时调整x,并在与水平边框相交时调整y

我们可以通过沿边缘行进的归一化坐标s 来表示交叉点,即在第一个节点n1 为0.0,在第二个节点n2 为1.0。

    var x = Math.floor(n1.x / cellsize);
    var y = Math.floor(n1.y / cellsize);
    var s = 0;

垂直插入可以表示为从初始sxdsx 的等距步长。

    var dx = n2.x - n1.x;

    var sx = 10;            // default value > 1.0

    // first intersection
    if (dx < 0) sx = (cellsize * x - n1.x) / dx;
    if (dx > 0) sx = (cellsize * (x + 1) - n1.x) / dx;

    var dsx = (dx != 0) ? grid / Math.abs(dx) : 0;

对于水平交叉点也是如此。大于 1.0 的默认值捕获水平线和垂直线的情况。将第一个点添加到扫描线数据中:

    add(scan, x, y);

然后我们可以通过查看下一个与s最小的交叉点来访问下一个相邻单元格。

    while (sx <= 1 || sy <= 1) {
        if (sx < sy) {
            sx += dsx;
            if (dx > 0) x++; else x--;
        } else {
            sy += dsy;
            if (dy > 0) y++; else y--;
        }
        add(scan, x, y);
    }

对所有四个边执行此操作,并使用相同的扫描线数据。然后填充所有单元格:

    for (var y in scan) {
        var x = scan[y].min;
        var xend = scan[y].max + 1;
        while (x < xend) {
            // do something with cell (x, y)
            x++;
        }
    }

(我只浏览了 MBo 提供的链接。似乎该论文中提出的方法与我的基本相同。如果是这样,请原谅多余的答案,但在解决了这个问题后,我想我也可以发布它。)

【讨论】:

    【解决方案3】:

    这是次优的,但可能会给出一个大致的想法。

    首先分别处理水平或垂直对齐的矩形的特殊情况。这很容易测试并使其余部分变得更简单。

    您可以将矩形表示为一组 4 个不等式a1 x + b1 y &gt;= c1a1 x + b1 y &lt;= c2a3 x + b3 y &gt;= c3a3 x + b3 y &lt;= c4,因为矩形的边缘是平行的,其中一些常数是相同的。您还拥有(最多多个)a3=b1b3=-a1。您可以将每个不等式乘以一个公因数,以便使用整数。

    现在考虑具有固定值 y 的每条扫描线。 对于 y 的每个值,找到线与扫描线相交的四个点。那就是找到上面每一行的解决方案。一点点逻辑就会找到 x 的最小值和最大值。绘制这些值之间的所有像素。

    你的条件是你想要所有部分覆盖的方块,这会让事情变得有点棘手。您可以通过考虑两条相邻的扫描线来解决这个问题。您想要绘制两条线的最小值 x 和两条线的最大值之间的点。如果说 a1 x+b1 y&gt;=c 是图中左下线的不等式。您希望找到最大的 x 使得 a1 x + b1 y &lt; c 这将是 floor((c-b1 y)/a1) 调用此 minx(y) 还找到 minx(y+1) 并且左侧点将是这两个值中的最小值。

    有许多简单的优化方法,您可以找到顶角和底角的 y 值,从而缩小要测试的 y 值范围。你应该只需要测试两个方面。对于每一行的每个端点,都有一个乘法、一个减法和一个除法。除法是我认为比其他操作慢 4 倍的最慢部分。您可能可以使用其他人提到的 Bresenham 或 DDA 算法来删除它。

    【讨论】:

      【解决方案4】:

      有 Amanatides 和 Woo 的方法来枚举所有相交的单元格 A Fast Voxel Traversal Algorithm for Ray Tracing.
      Here is实际实现。
      作为对您的副作用 - 您将获得与网格线的交点 - 如果您需要部分覆盖的单元格区域(用于抗锯齿等),它可能会很有用。

      【讨论】:

        猜你喜欢
        • 2012-04-21
        • 1970-01-01
        • 2017-05-04
        • 1970-01-01
        • 1970-01-01
        • 2011-08-12
        • 1970-01-01
        • 2017-06-21
        相关资源
        最近更新 更多