本文给出多边形布尔运算的实现,采用的算法在多边形的布尔运算(1)一文介绍。
首先定义MarkedPoint结构体作为辅助数据结构,其定义如下:其中original表示多边形原始的点,即非交点;value是点的几何数。
struct MarkedPoint
{
bool original = true;
bool active = true;
int value = 0;
double x = 0;
double y = 0;
MarkedPoint() {}
MarkedPoint(const double x_, const double y_, const bool original_ = true, const int value_ = 0)
: x(x_), y(y_), value(value_), original(original_) {}
bool operator==(const MarkedPoint &point) const
{
return x == point.x && y == point.y;
}
bool operator!=(const MarkedPoint &point) const
{
return x != point.x || y != point.y;
}
};
并运算的实现:
bool Geo::polygon_union(const Polygon &polygon0, const Polygon &polygon1, std::vector<Polygon> &output)
{
Geo::Polygon polygon2(polygon0), polygon3(polygon1);
polygon2.reorder_points(false);
polygon3.reorder_points(false);
std::vector<MarkedPoint> points0, points1;
for (const Point &point : polygon2)
{
points0.emplace_back(point.x, point.y);
}
for (const Point &point : polygon3)
{
points1.emplace_back(point.x, point.y);
}
Point point, pre_point; // 找到交点并计算其几何数
const AABBRect rect(polygon1.bounding_rect());
for (size_t i = 1, count0 = points0.size(); i < count0; ++i)
{
if (!is_intersected(rect, points0[i - 1], points0[i])) // 粗筛
{
continue;
}
pre_point = points0[i - 1];
for (size_t k = 1, j = 1, count1 = points1.size(); j < count1; ++j)
{
k = j - 1;
while (!points1[k].active)
{
--k; // 跳过非活动交点
}
while (j < count1 && (points1[k] == points1[j] || !points1[j].active))
{
k = j;
++j;
while (!points1[k].active)
{
--k; // 跳过非活动交点
}
}
if (j >= count1)
{
continue;
}
k = j - 1;
while (!points1[k].active)
{
--k; // 跳过非活动交点
}
if (!is_parallel(pre_point, points0[i], points1[k], points1[j]) &&
is_intersected(pre_point, points0[i], points1[k], points1[j], point))
{
points0.insert(points0.begin() + i++, MarkedPoint(point.x, point.y, false));
points1.insert(points1.begin() + j++, MarkedPoint(point.x, point.y, false));
++count0;
++count1;
if (cross(pre_point, points0[i], points1[k], points1[j]) >= 0)
{
points0[i - 1].value = 1;
points1[j - 1].value = -1;
}
else
{
points0[i - 1].value = -1;
points1[j - 1].value = 1;
}
}
}
// 将本次循环添加的交点休眠,令下次循环polygon1处于无活动交点状态以排除干扰
for (MarkedPoint &p : points1)
{
if (!p.original)
{
p.active = false;
}
}
}
if (points0.size() == polygon0.size()) // 无交点
{
if (std::all_of(polygon0.begin(), polygon0.end(), [&](const Point &point) { return Geo::is_inside(point, polygon1, true); }))
{
output.emplace_back(polygon1); // 无交点,且polygon0的点都在polygon1内,则并集必然为polygon1
return true;
}
else if (std::all_of(polygon1.begin(), polygon1.end(), [&](const Point &point) { return Geo::is_inside(point, polygon0, true); }))
{
output.emplace_back(polygon0); // 无交点,且polygon1的点都在polygon0内,则并集必然为polygon0
return true;
}
else
{
return false;
}
}
// 调整交点顺序,同一条边上的交点按照顺序排列
std::vector<MarkedPoint> points;
for (size_t i = 1, j = 0, count = points0.size() - 1; i < count; ++i)
{
if (points0[i].original)
{
continue;
}
else
{
j = i;
}
while (j < count && !points0[j].original)
{
++j;
}
if (j == i + 1)
{
++i;
continue;
}
points.assign(points0.begin() + i, j < count ? points0.begin() + j : points0.end());
std::sort(points.begin(), points.end(), [&](const MarkedPoint &p0, const MarkedPoint &p1)
{ return Geo::distance(p0, points0[i - 1]) < Geo::distance(p1, points0[i - 1]); });
for (size_t k = i, n = 0; k < j; ++k)
{
points0[k] = points[n++];
}
i = j;
}
for (size_t i = points0.size() - 1; i > 1;)
{
if (polygon0.front() == points0[i])
{
if (!points0[i].original)
{
points0.insert(points0.begin(), points0[i]);
points0.erase(points0.begin() + i + 1);
}
else
{
--i;
}
}
else
{
break;
}
}
for (size_t i = 1, j = 0, count = points1.size() - 1; i < count; ++i)
{
if (points1[i].original)
{
continue;
}
else
{
j = i;
}
while (j < count && !points1[j].original)
{
++j;
}
if (j == i + 1)
{
++i;
continue;
}
points.assign(points1.begin() + i, j < count ? points1.begin() + j : points1.end());
std::sort(points.begin(), points.end(), [&](const MarkedPoint &p0, const MarkedPoint &p1)
{ return Geo::distance(p0, points1[i - 1]) < Geo::distance(p1, points1[i - 1]); });
for (size_t k = i, n = 0; k < j; ++k)
{
points1[k] = points[n++];
}
i = j;
}
for (size_t i = points1.size() - 1; i > 1;)
{
if (polygon1.front() == points1[i])
{
if (!points1[i].original)
{
points1.insert(points1.begin(), points1[i]);
points1.erase(points1.begin() + i + 1);
}
else
{
--i;
}
}
else
{
break;
}
}
// 去除重复交点
int value;
Geo::Point point_a, point_b, point_c, point_d;
bool flags[5];
for (size_t count, j, i = points0.size() - 1; i > 0; --i)
{
count = points0[i].original ? 0 : 1;
for (j = i; j > 0; --j)
{
if (std::abs(points0[i].x - points0[j - 1].x) > Geo::EPSILON ||
std::abs(points0[i].y - points0[j - 1].y) > Geo::EPSILON)
{
break;
}
if (!points0[j - 1].original)
{
++count;
}
}
if (count < 2)
{
continue;
}
value = 0;
for (size_t k = i; k > j; --k)
{
if (!points0[k].original)
{
value += points0[k].value;
}
}
if (!points0[j].original)
{
value += points0[j].value;
}
if (count < 4)
{
if (value == 0)
{
for (size_t k = i; k > j; --k)
{
if (!points0[k].original)
{
points0.erase(points0.begin() + k);
}
}
if (!points0[j].original)
{
points0.erase(points0.begin() + j);
}
}
else
{
flags[0] = false;
for (size_t k = i; k > j; --k)
{
flags[0] = (flags[0] || points0[k].original);
points0.erase(points0.begin() + k);
}
points0[j].value = value;
points0[j].original = (flags[0] || points0[j].original);
}
}
else
{
point = points0[i];
point_a = polygon0.last_point(polygon0.index(point.x, point.y));
point_b = polygon0.next_point(polygon0.index(point.x, point.y));
point_c = polygon1.last_point(polygon1.index(point.x, point.y));
point_d = polygon1.next_point(polygon1.index(point.x, point.y));
flags[2] = Geo::cross(point_a - point, point_b - point) > 0;
flags[3] = Geo::cross(point_a - point, point_c - point) > 0;
flags[4] = Geo::cross(point_c - point, point_b - point) > 0;
flags[0] = !(flags[2] == flags[3] && flags[3] == flags[4]);
flags[2] = Geo::cross(point_a - point, point_b - point) > 0;
flags[3] = Geo::cross(point_a - point, point_d - point) > 0;
flags[4] = Geo::cross(point_d - point, point_b - point) > 0;
flags[1] = !(flags[2] == flags[3] && flags[3] == flags[4]);
if (flags[0] && flags[1])
{
for (size_t k = i; k > j; --k)
{
if (!points0[k].original)
{
points0.erase(points0.begin() + k);
}
}
if (!points0[j].original)
{
points0.erase(points0.begin() + j);
}
}
else
{
flags[0] = false;
for (size_t k = i; k > j; --k)
{
flags[0] = (flags[0] || points0[k].original);
points0.erase(points0.begin() + k);
}
points0[j].value = value;
points0[j].original = (flags[0] || points0[j].original);
}
}
i = j > 0 ? j : 1;
}
for (size_t count, j, i = points1.size() - 1; i > 0; --i)
{
count = points1[i].original ? 0 : 1;
for (j = i; j > 0; --j)
{
if (std::abs(points1[i].x - points1[j - 1].x) > Geo::EPSILON ||
std::abs(points1[i].y - points1[j - 1].y) > Geo::EPSILON)
{
break;
}
if (!points1[j - 1].original)
{
++count;
}
}
if (count < 2)
{
continue;
}
value = 0;
for (size_t k = i; k > j; --k)
{
if (!points1[k].original)
{
value += points1[k].value;
}
}
if (!points1[j].original)
{
value += points1[j].value;
}
if (count < 4)
{
if (value == 0)
{
for (size_t k = i; k > j; --k)
{
if (!points1[k].original)
{
points1.erase(points1.begin() + k);
}
}
if (!points1[j].original)
{
points1.erase(points1.begin() + j);
}
}
else
{
flags[0] = false;
for (size_t k = i; k > j; --k)
{
flags[0] = (flags[0] || points1[k].original);
points1.erase(points1.begin() + k);
}
points1[j].value = value;
points1[j].original = (flags[0] || points1[j].original);
}
}
else
{
point = points1[i];
point_a = polygon1.last_point(polygon1.index(point.x, point.y));
point_b = polygon1.next_point(polygon1.index(point.x, point.y));
point_c = polygon0.last_point(polygon0.index(point.x, point.y));
point_d = polygon0.next_point(polygon0.index(point.x, point.y));
flags[2] = Geo::cross(point_a - point, point_b - point) > 0;
flags[3] = Geo::cross(point_a - point, point_c - point) > 0;
flags[4] = Geo::cross(point_c - point, point_b - point) > 0;
flags[0] = !(flags[2] == flags[3] && flags[3] == flags[4]);
flags[2] = Geo::cross(point_a - point, point_b - point) > 0;
flags[3] = Geo::cross(point_a - point, point_d - point) > 0;
flags[4] = Geo::cross(point_d - point, point_b - point) > 0;
flags[1] = !(flags[2] == flags[3] && flags[3] == flags[4]);
if (flags[0] && flags[1])
{
for (size_t k = i; k > j; --k)
{
if (!points1[k].original)
{
points1.erase(points1.begin() + k);
}
}
if (!points1[j].original)
{
points1.erase(points1.begin() + j);
}
}
else
{
flags[0] = false;
for (size_t k = i; k > j; --k)
{
flags[0] = (flags[0] || points1[k].original);
points1.erase(points1.begin() + k);
}
points1[j].value = value;
points1[j].original = (flags[0] || points1[j].original);
}
}
i = j > 0 ? j : 1;
}
if (std::count_if(points0.begin(), points0.end(), [](const MarkedPoint &p) { return p.value < 0; }) == 0 ||
std::count_if(points1.begin(), points1.end(), [](const MarkedPoint &p) { return p.value < 0; }) == 0)
{
return false; // 交点都是出点,即两多边形只有一个点相交
}
// 处理重边上的交点
std::vector<Geo::MarkedPoint>::iterator it0, it1;
for (size_t i = 0, j = 1, count0 = points0.size(), count1 = polygon3.size(); j < count0; i = j)
{
while (i < count0 && points0[i].value == 0)
{
++i;
}
j = i + 1;
while (j < count0 && points0[j].value == 0)
{
++j;
}
if (j >= count0)
{
break;
}
if (!Geo::is_coincide(points0[i], points0[j], polygon2))
{
continue;
}
it0 = std::find_if(points1.begin(), points1.end(), [&](const MarkedPoint &p)
{ return !p.original && Geo::distance(p, points0[i]) < Geo::EPSILON; });
it1 = std::find_if(points1.begin(), points1.end(), [&](const MarkedPoint &p)
{ return !p.original && Geo::distance(p, points0[j]) < Geo::EPSILON; });
if (it0 ==