You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

623 lines
18 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "StdAfx.h"
#include <algorithm>
#include "keyholing.h"
#include "clipper2/clipper.core.h"
using namespace Clipper2Lib;
inline size_t GetLowestPtIdx(const Path64& path)
{
size_t result = 0;
Point64 resPt = path[0];
for (size_t i = 1; i < path.size(); ++i)
if (path[i].y < resPt.y) continue;
else if (path[i].y > resPt.y || path[i].x < resPt.x)
{
result = i;
resPt = path[i];
}
return result;
}
//inline size_t GetNearestPtIdx(const Path64& path, const Point64& pt)
//{
// size_t result = 0;
// double distSqr = DistanceSqr(path[0], pt);
// for (size_t i = 1; i < path.size(); ++i)
// {
// double ds = DistanceSqr(path[i], pt);
// if (ds >= distSqr) continue;
// distSqr = ds;
// result = i;
// }
// return result;
//}
static Point64 GetClosestPtOnSegment(const Point64& seg1, const Point64& seg2, const Point64& pt)
{
double dx = (seg2.x - seg1.x);
double dy = (seg2.y - seg1.y);
if (dx == 0.0 && dy == 0.0) return seg1;
double q = (dx * (pt.x - seg1.x) + dy * (pt.y - seg1.y)) / (Sqr(dx) + Sqr(dy));
// and restrict the point to the segment ...
q = std::fmax(0, std::fmin(1, q));
return Point64((1 - q) * seg1.x + q * seg2.x, (1 - q) * seg1.y + q * seg2.y);
}
// 旋转点 (x, y) 逆时针旋转角度 theta (使用预先计算的 cosTheta 和 sinTheta)
inline Point64 rotatePoint(const Point64& p, double cosTheta, double sinTheta) {
return { p.x * cosTheta + p.y * sinTheta, -p.x * sinTheta + p.y * cosTheta };
}
//// 将整个坐标系旋转,使线段水平
//inline Point64 rotatePointToHorizontal(const Point64& p, const Point64& origin, double cosTheta, double sinTheta) {
// // 平移点到原点,然后应用旋转
// double translatedX = p.x - origin.x;
// double translatedY = p.y - origin.y;
// return {
// translatedX * cosTheta + translatedY * sinTheta, // X轴方向
// -translatedX * sinTheta + translatedY * cosTheta // Y轴方向
// };
//}
//// 点与X轴平行的两个点的最近距离点
//static Point64 GetClosestPtOnSegmentX(const Point64& seg1, const Point64& seg2, const Point64& pt)
//{
// int64_t dx = (seg2.x - seg1.x);
// int64_t dy = (seg2.y - seg1.y);
// if (dx == 0.0 && dy == 0.0) return seg1;
//
// int64_t segMinX = std::fmin(seg1.x, seg2.x);
//
// //if(pt.x<std::fmin(seg1.x, seg2.x))
// double q = (dx * (pt.x - seg1.x) + dy * (pt.y - seg1.y)) / (Sqr(dx) + Sqr(dy));
// // and restrict the point to the segment ...
// q = std::fmax(0, std::fmin(1, q));
// return Point64((1 - q) * seg1.x + q * seg2.x, (1 - q) * seg1.y + q * seg2.y);
//}
inline Point64 GetYIntersectPt(const Point64& ln1, const Point64& ln2, int64_t Y)
{
double dx = static_cast<double>(ln2.x - ln1.x);
if (dx == 0.0) return Point64(ln1.x, Y);
double dy = static_cast<double>(ln2.y - ln1.y);
// y = dy/dx * x + b
// b = y1 - dy/dx * x1;
double b = ln1.y - dy / dx * ln1.x;
// x = (Y - b) * dx/dy
return Point64(static_cast<int64_t>(round((Y - b) * dx / dy)), Y);
}
static void JoinAtClosestPtBelowHole(Path64& outer, Path64 hole)
{
// precondition: hole[0] is the hole's lowest vertex
// find the closest point on the outer path (cpOuter) to hole[0],
// while ensuring cpOuter is **below** holePt. (Joining with an
// 'outer' segment that's above holePt (ie y < holePt.y) risks the
// new join passing through a hole that hasn't yet been keyholed.)
int highI = static_cast<int>(outer.size()) - 1;
Point64 holePt = hole[0];
Point64 cpOuter;
int cpOuterIdx = -1;
double distSqrd = MAX_DBL;
for (size_t i = 0; i < highI; ++i)
{
Point64 nextPt = (i == highI) ? outer[0] : outer[i + 1];
// Since cpOuter.y must be below holePt.y ...
if (outer[i].y <= holePt.y && nextPt.y <= holePt.y) continue;
// 'outer' may have overlapping collinear segments due to previous
// hole joins. Therefore it's imperative when the closest segment
// happens to be one of these overlapping collinear segments, that
// the new join is with the correct one. For example, when a hole
// is on the left of an outer segment, that outer segment **must**
// be descending. Likewise, when a hole is on the right of an
// outer segment, then that outer segment must be descending.
if (CrossProduct(holePt, outer[i], nextPt) < 0) continue;
// Again make sure cpOuter.y is below holePt.y ...
Point64 cp;
if (outer[i].y <= holePt.y)
{
Point64 pt = GetYIntersectPt(outer[i], nextPt, holePt.y + 1);
cp = GetClosestPtOnSegment(pt, nextPt, holePt);
}
else if (nextPt.y <= holePt.y)
{
Point64 pt = GetYIntersectPt(outer[i], nextPt, holePt.y + 1);
cp = GetClosestPtOnSegment(outer[i], pt, holePt);
}
else
cp = GetClosestPtOnSegment(outer[i], nextPt, holePt);
double ds = DistanceSqr(cp, holePt);
if (cpOuterIdx >= 0 && ds >= distSqrd) continue;
cpOuter = cp;
distSqrd = ds;
cpOuterIdx = (cp == nextPt) ? i + 1 : i;
if (ds == 0) break;
}
// insert duplicate points into both outer and hole paths
if (cpOuter != outer[cpOuterIdx])
{
++cpOuterIdx;
if (cpOuterIdx > highI) cpOuterIdx = 0;
outer.insert(outer.begin() + cpOuterIdx, cpOuter);
}
outer.insert(outer.begin() + cpOuterIdx, cpOuter);
++cpOuterIdx;
if (cpOuterIdx >= outer.size()) cpOuterIdx = 0;
hole.insert(hole.begin(), holePt);
// finally join the hole with its outer path
outer.reserve(outer.size() + hole.size());
outer.insert(outer.begin() + cpOuterIdx, hole.begin(), hole.begin() + 1);
outer.insert(outer.begin() + cpOuterIdx, hole.begin() + 1, hole.end());
}
struct PolySorter {
inline bool operator()(const Path64& path1, const Path64& path2)
{
return path2[0].y < path1[0].y;
}
};
static bool KeyHoleOuter(const PolyPath64* outer, Paths64& paths)
{
if (!outer->Count())
{
paths.push_back(outer->Polygon());
return true;
}
Paths64 tmp;
tmp.reserve(outer->Count());
for (const auto& hole : *outer)
{
Path64 p = hole->Polygon();
// rearrange p so the lower vertex is first
size_t i = GetLowestPtIdx(p);
for (size_t j = 0; j < i; ++j)
{
Path64::iterator it = p.begin();
std::rotate(it, it + 1, p.end());
}
tmp.push_back(p);
}
// sort holes so the lowest ones are first
std::sort(tmp.begin(), tmp.end(), PolySorter());
Path64 merged = outer->Polygon();
// merge all holes into the outer path
for (Paths64::iterator it = tmp.begin(); it != tmp.end(); ++it)
{
JoinAtClosestPtBelowHole(merged, *it);
//std::cout << merged;
}
paths.push_back(merged);
// finally do any nested outer polygons
for (const auto& hole : *outer)
{
for (const auto& nested_outer : *hole)
{
if (!KeyHoleOuter(nested_outer.get(), paths))
{
return false;
}
}
}
return true;
}
bool KeyHole(const PolyTree64& polytree, Paths64& solution)
{
for (const auto& outer : polytree)
if (!KeyHoleOuter(outer.get(), solution)) return false;
return true;
}
//////////////////////////////////////////////////////////////////////////////////////
struct BoundingBox64 {
int64_t minX, minY, maxX, maxY;
BoundingBox64() {}
BoundingBox64(int64_t minX, int64_t minY, int64_t maxX, int64_t maxY)
: minX(minX), minY(minY), maxX(maxX), maxY(maxY) {}
// 判断两个包围盒是否有交集
bool Intersects(const BoundingBox64& other) const {
return !(other.maxX < this->minX || other.minX > this->maxX ||
other.maxY < this->minY || other.minY > this->maxY);
}
// 计算两个包围盒之间的最小距离(粗略估算)
double DistanceTo(const BoundingBox64& other) const {
// if (Intersects(other)) return 0;
int64_t dx = max(int64_t(0), max(minX - other.maxX, other.minX - maxX));
int64_t dy = max(int64_t(0), max(minY - other.maxY, other.minY - maxY));
return sqrt(dx * dx + dy * dy);
// return min(dx, dy, )
}
void CreateBoundary(const Path64 path) {
minX = INT64_MAX;
maxX = INT64_MIN;
minY = INT64_MAX;
maxY = INT64_MIN;
for (const auto& p : path) {
minX = min(minX, p.x);
minY = min(minY, p.y);
maxX = max(maxX, p.x);
maxY = max(maxY, p.y);
}
}
};
// 计算两点之间的距离
inline double distance(const Point64& p1, const Point64& p2) {
return sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
}
double Clipper64Factor = 10000;
Point64 toPoint64(double x, double y) {
int64_t nX = static_cast<int64_t>(x * Clipper64Factor);
int64_t nY = static_cast<int64_t>(y* Clipper64Factor);
return Point64(nX, nY);
}
// 将多边形转换为 Clipper 使用的 IntPoint 格式
Path64 toPolygon64(const PathD& poly) {
Path64 clipperPoly;
for (const PointD& p : poly) {
clipperPoly.push_back(Point64(static_cast<int64_t>(p.x * Clipper64Factor), static_cast<int64_t>(p.y * Clipper64Factor)));
}
return clipperPoly;
}
// 将 Clipper 的 IntPoint 结果转换回原始 Polygon 格式
PathD fromPolygon64(const Path64& clipperPoly) {
vector<PointD> vertices;
for (const Point64& ip : clipperPoly) {
vertices.emplace_back(ip.x / Clipper64Factor, ip.y/ Clipper64Factor);
}
return PathD(vertices);
}
// 找出旋转后距离线段最近的点
tuple<Point64, Point64, int, int, double> findClosestPointWithRotation(const Path64& poly1, const Path64& poly2, double& minDist) {
Point64 closest1, closest2;
//double minDist = distInit;
int index1 = -1, index2 = -1;
// 遍历第一个多边形的每一条边
for (int i = 0; i < poly1.size(); i++)
{
Point64 p1 = poly1[i];
Point64 p2 = poly1[(i + 1) % poly1.size()];
// 计算线段 a-b 与 X 轴的夹角
double dx = p2.x - p1.x;
double dy = p2.y - p1.y;
double theta = atan2(dy, dx); // 不需要特别处理 dx == 0atan2 自动处理
// 预先计算 cos 和 sin避免在旋转时多次计算
double cosTheta = cos(theta);
double sinTheta = sin(theta);
// 坐标轴旋转后的坐标
Point64 rotatedA = rotatePoint(p1, cosTheta, sinTheta);
Point64 rotatedB = rotatePoint(p2, cosTheta, sinTheta);
// 遍历第二个多边形的每个顶点,计算到每条边的距离
for (int j = 0; j < poly2.size(); j++)
{
Point64 point = poly2[j];
Point64 rotatedP = rotatePoint(point, cosTheta, sinTheta);
double distY = fabs(rotatedP.y - rotatedA.y);
if (distY > minDist)
{
continue;
}
// 如果旋转后的点的 x 坐标在线段的范围内,则计算垂直距离
if (rotatedP.x >= rotatedA.x && rotatedP.x <= rotatedB.x) {
//double dist = fabs(rotatedP.y - rotatedA.y); // 垂直距离为 y 坐标之差
minDist = distY;
//closestPoint = point; // 保存原坐标系中的点
index1 = i;
index2 = j;
closest1 = Point64{ rotatedP.x , rotatedA.y };
closest1 = rotatePoint(closest1, cosTheta, -sinTheta); // 旋转回原坐标系
closest2 = point;
}
else {
// 如果不在范围内,则计算到线段端点的距离
size_t dSpaceXA = abs(rotatedP.x - rotatedA.x);
size_t dSpaceXB = abs(rotatedP.x - rotatedB.x);
size_t dSpaceXMin = min(dSpaceXA, dSpaceXB);
if (dSpaceXA > minDist && dSpaceXB > minDist)
{
continue;
}
if (dSpaceXA < dSpaceXB) {
double dDist = distance(rotatedA, rotatedP);
if (dDist < minDist)
{
minDist = dDist;
index1 = i;
index2 = j;
//closest1 = Point64{ rotatedA.x , rotatedA.y };
//closest1 = rotatePoint(closest1, cosTheta, -sinTheta); // 旋转回原坐标
closest1 = p1;
closest2 = point;
}
}
else {
double dDist = distance(rotatedB, rotatedP);
if (dDist < minDist)
{
minDist = dDist;
index1 = i;
index2 = j;
//closest1 = Point64{ rotatedB.x , rotatedB.y };
//closest1 = rotatePoint(closest1, cosTheta, -sinTheta); // 旋转回原坐标
closest1 = p2;
closest2 = point;
}
}
}
}
}
// 遍历第二个多边形的每一条边
for (int i = 0; i < poly2.size(); i++)
{
Point64 p1 = poly2[i];
Point64 p2 = poly2[(i + 1) % poly2.size()];
// 计算线段 a-b 与 X 轴的夹角
double dx = p2.x - p1.x;
double dy = p2.y - p1.y;
double theta = atan2(dy, dx); // 不需要特别处理 dx == 0atan2 自动处理
// 预先计算 cos 和 sin避免在旋转时多次计算
double cosTheta = cos(theta);
double sinTheta = sin(theta);
// 将线段端点旋转到与 X 轴平行的坐标系
Point64 rotatedA = rotatePoint(p1, cosTheta, sinTheta);
Point64 rotatedB = rotatePoint(p2, cosTheta, sinTheta);
// 遍历第一个多边形的每个顶点,计算到每条边的距离
for (int j = 0; j < poly1.size(); j++)
{
Point64 point = poly1[j];
Point64 rotatedP = rotatePoint(point, cosTheta, sinTheta);
double distY = fabs(rotatedP.y - rotatedA.y);
if (distY > minDist)
{
continue;
}
// 如果旋转后的点的 x 坐标在线段的范围内,则计算垂直距离
if (rotatedP.x >= rotatedA.x && rotatedP.x <= rotatedB.x) {
//double dist = fabs(rotatedP.y - rotatedA.y); // 垂直距离为 y 坐标之差
minDist = distY;
//closestPoint = point; // 保存原坐标系中的点
index2 = i;
index1 = j;
closest2 = Point64{ rotatedP.x , rotatedA.y };
closest2 = rotatePoint(closest2, cosTheta, -sinTheta); // 旋转回原坐标
closest1 = point;
}
else
{
// 如果不在范围内,则计算到线段端点的距离
size_t dSpaceXA = abs(rotatedP.x - rotatedA.x);
size_t dSpaceXB = abs(rotatedP.x - rotatedB.x);
size_t dSpaceXMin = min(dSpaceXA, dSpaceXB);
if (dSpaceXA > minDist && dSpaceXB > minDist)
{
continue;
}
if (dSpaceXA < dSpaceXB) {
double dDist = distance(rotatedA, rotatedP);
if (dDist < minDist)
{
minDist = dDist;
index2 = i;
index1 = j;
closest2 = p1;
closest1 = point;
}
}
else {
double dDist = distance(rotatedB, rotatedP);
if (dDist < minDist)
{
minDist = dDist;
index2 = i;
index1 = j;
closest2 = p2;
closest1 = point;
}
}
}
}
}
return { closest1, closest2, index1, index2, minDist };
}
// 计算两个多边形的最近顶点对
tuple<Point64, Point64, int, int, double> findClosestPoints(const Path64& poly1, const Path64& poly2) {
Point64 closest1, closest2;
double minDist = 1e30;
int index1 = -1, index2 = -1;
// 遍历第一个多边形的每一条边
for (int i = 0; i < poly1.size(); i++)
{
Point64 p1 = poly1[i];
Point64 p2 = poly1[(i + 1) % poly1.size()];
// 遍历第二个多边形的每个顶点,计算到每条边的距离
for (int j = 0; j < poly2.size(); j++)
{
Point64 point = poly2[j];
Point64 ptConn = GetClosestPtOnSegment(p1, p2, point);
double dDisConn = distance(ptConn, point);
if (dDisConn < minDist) {
minDist = dDisConn;
index1 = i;
index2 = j;
closest1 = ptConn;
closest2 = point;
}
}
}
// 遍历第二个多边形的每一条边
for (int i = 0; i < poly2.size(); i++)
{
Point64 p1 = poly2[i];
Point64 p2 = poly2[(i + 1) % poly2.size()];
// 遍历第一个多边形的每个顶点,计算到每条边的距离
for(int j=0;j<poly1.size();j++)
{
Point64 point = poly1[j];
Point64 ptConn = GetClosestPtOnSegment(p1, p2, point);
double dDisConn = distance(ptConn, point);
if (dDisConn < minDist)
{
minDist = dDisConn;
index2 = i;
index1 = j;
closest2 = ptConn;
closest1 = point;
}
}
}
//for (int i = 0; i < poly1.size();i++) {
// Point64 p1 = poly1[i];
// for (int j = 0; j < poly2.size();j++) {
// Point64 p2 = poly2[j];
// double dist = distance(p1, p2);
// if (dist < minDist) {
// minDist = dist;
// closest1 = p1;
// closest2 = p2;
// index1 = i;
// index2 = j;
// }
// }
//}
return { closest1, closest2, index1, index2, minDist };
}
tuple<int, Point64, Point64, int, int, double> findClosestPath(const Path64& path, const Paths64& source, const std::vector< BoundingBox64> boundings)
{
vector<int> vecSearchIndexs;
BoundingBox64 bounding;
bounding.CreateBoundary(path);
double dDistMin = INT64_MAX;
int nSearchNeareast = -1;
for (int i = 0; i < boundings.size(); i++) {
BoundingBox64* pBdCur = (BoundingBox64*)(&boundings[i]);
if (pBdCur->Intersects(bounding)) {
vecSearchIndexs.emplace_back(i);
continue;
}
double dDist = bounding.DistanceTo(*pBdCur);
if (dDist < dDistMin) {
nSearchNeareast = i;
dDistMin = dDist;
}
}
if (nSearchNeareast > -1) {
vecSearchIndexs.emplace_back(nSearchNeareast);
}
Point64 closest1, closest2;
int idxFind = -1;
int idxEdge1 = -1, idxEdge2 = -1;
dDistMin = INT64_MAX;
for (const auto& index : vecSearchIndexs)
{
Path64 item = source[index];
//auto[p1, p2, index1, index2, distance] = findClosestPoints(path, item);
double dDistOld = dDistMin;
auto[p1, p2, index1, index2, distance] = findClosestPointWithRotation(path, item, dDistMin);
if (dDistMin <= 1E-6) {
idxFind = index;
dDistMin = distance;
closest1 = p1;
closest2 = p2;
idxEdge1 = index1;
idxEdge2 = index2;
break;
}
else if (distance < dDistOld) {
idxFind = index;
dDistMin = distance;
closest1 = p1;
closest2 = p2;
idxEdge1 = index1;
idxEdge2 = index2;
}
}
//for (int i = 0; i < source.size();i++) {
// Path64 item = source[i];
// auto[p1, p2, index1, index2, distance] = findClosestPoints(path, item);
// if (distance < minDist) {
// idxFind = i;
// minDist = distance;
// closest1 = p1;
// closest2 = p2;
// idxEdge1 = index1;
// idxEdge2 = index2;
// }
//}
return { idxFind, closest1, closest2, idxEdge1, idxEdge2, dDistMin };
}
// 将多个多边形连通
Path64 ConnectPolygons(const Paths64& polygons) {
if (polygons.size() == 0) {
return Path64();
}
Paths64 lstSource;
std::vector< BoundingBox64> boundings;
for (const auto& polygon : polygons) {
lstSource.emplace_back(polygon);
BoundingBox64 bounding;
bounding.CreateBoundary(polygon);
boundings.emplace_back(bounding);
}
Path64 pathResult = lstSource[0];
lstSource.erase(lstSource.begin());
boundings.erase(boundings.begin());
while (lstSource.size() > 0) {
// 查找最近的多边形
auto[findIndex, p1, p2, index1, index2, findDist] = findClosestPath(pathResult, lstSource, boundings);
// 插入连通点
pathResult.insert(pathResult.begin() + index1+1, p1);
pathResult.insert(pathResult.begin() + index1+2, p2);
// 合并最近的多边形
Path64 pathFind = lstSource[findIndex];
std::rotate(pathFind.begin(), pathFind.begin() + index2+1, pathFind.end());
pathResult.insert(pathResult.begin() + index1+3, pathFind.begin(), pathFind.end());
// 插入连通点
pathResult.insert(pathResult.begin() + index1 + 3 + pathFind.size(), p2);
pathResult.insert(pathResult.begin() + index1+3 + pathFind.size()+1, p1);
lstSource.erase(lstSource.begin() + findIndex);
boundings.erase(boundings.begin() + findIndex);
}
return pathResult;
}