旋转矩形光栅化算法

时间:2014-05-09 06:03:51

标签: algorithm math graphics language-agnostic geometry

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



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

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

4 个答案:

答案 0 :(得分:2)

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

https://github.com/feelinfine/tracer

也许它会对某人有用

(抱歉我的英语不好)

Example picture

单行追踪

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

答案 1 :(得分:1)

这是次优的,但可能会提供一般性的想法。

首先将矩形的特殊情况分别水平或垂直对齐。这很容易测试,其余的更简单。

您可以将矩形表示为一组4个不等式a1 x + b1 y >= c1 a1 x + b1 y <= c2 a3 x + b3 y >= c3 a3 x + b3 y <= c4,因为矩形的边是平行的,其中一些常数是相同的。您还拥有(最多一个)a3=b1b3=-a1。您可以将每个不等式乘以一个公因子,这样您就可以使用整数。

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

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

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

答案 2 :(得分:1)

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

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

我们可以通过沿着边缘传播的标准化坐标s来表示交叉点,第一个节点n1为0.0,第二个节点为n2为1.0。 / p>

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

垂直分割可以表示为与dsx初始sx的等距步骤。

    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 :(得分:0)

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