|
|
#pragma once
|
|
|
|
|
|
#include "DrawOperator/CurveEx.h"
|
|
|
#include "clipper2/clipper.h"
|
|
|
#include <memory>
|
|
|
|
|
|
/**
|
|
|
* \brief 提供基于 Clipper2 库的多边形布尔运算和转换的实用工具类。
|
|
|
*
|
|
|
* 此类封装了 Clipper2 的常用操作,例如交集、并集、差集,
|
|
|
* 并提供了在浮点坐标 (PathD) 和整数坐标 (PathD) 之间转换的辅助函数。
|
|
|
* 它还包括与自定义 `CCurveEx` 类型相互转换的方法。
|
|
|
*/
|
|
|
class PolygonUtils
|
|
|
{
|
|
|
public:
|
|
|
/**
|
|
|
* \brief 计算两个浮点路径的交集。
|
|
|
* \param subject 主体路径。
|
|
|
* \param clip 裁剪路径。
|
|
|
* \return 表示交集区域的一个或多个路径 (PathsD)。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Intersect(const Clipper2Lib::PathD& subject, const Clipper2Lib::PathD& clip)
|
|
|
{
|
|
|
return Clipper2Lib::Intersect({ subject }, { clip }, Clipper2Lib::FillRule::NonZero);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief 查找两个路径之间小于指定 `minGap` 的间隙区域。
|
|
|
*
|
|
|
* \warning 原始逻辑存在问题!
|
|
|
* 此方法通过膨胀路径来检测 "间隙"。
|
|
|
* 原始逻辑计算 `Difference(inflatedOverlap, inflatedUnion)`,
|
|
|
* 这将始终返回空集,因为 `inflatedOverlap` 总是 `inflatedUnion` 的子集。
|
|
|
*
|
|
|
* \param subject 主体路径。
|
|
|
* \param clip 裁剪路径。
|
|
|
* \param minGap 视为 "间隙" 的最大距离。
|
|
|
* \return 表示间隙区域的一个或多个路径 (PathsD)。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD FindGapRegions(const Clipper2Lib::PathD& subject, const Clipper2Lib::PathD& clip, double minGap)
|
|
|
{
|
|
|
double delta = minGap / 2.0;
|
|
|
|
|
|
Clipper2Lib::PathsD subjects = { subject };
|
|
|
Clipper2Lib::PathsD clips = { clip };
|
|
|
|
|
|
Clipper2Lib::PathsD inflatedSubjects = Clipper2Lib::InflatePaths(subjects, delta, Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon, 2.0);
|
|
|
Clipper2Lib::PathsD inflatedClips = Clipper2Lib::InflatePaths(clips, delta, Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon, 2.0);
|
|
|
|
|
|
// 2. 膨胀后求交
|
|
|
Clipper2Lib::PathsD inflatedOverlap = Clipper2Lib::Intersect(inflatedSubjects, inflatedClips, Clipper2Lib::FillRule::NonZero);
|
|
|
|
|
|
if (inflatedOverlap.empty())
|
|
|
{
|
|
|
return {};
|
|
|
}
|
|
|
|
|
|
// 3. 原始并集
|
|
|
Clipper2Lib::PathsD originalUnion = Clipper2Lib::Union(subjects, clips, Clipper2Lib::FillRule::NonZero);
|
|
|
|
|
|
// 4. 差集
|
|
|
return Clipper2Lib::Difference(inflatedOverlap, originalUnion, Clipper2Lib::FillRule::NonZero);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief 将 CCurveEx 对象转换为 Clipper2Lib::PathD 路径。
|
|
|
*
|
|
|
* Clipper2 的路径定义为不含重复闭合点的顶点列表。
|
|
|
* 此函数会检查 `curve->IsClosed()`:
|
|
|
* - 如果 curve 是闭合的 (最后一个点 == 第一个点),则转换时会 *省略* 最后一个重复点。
|
|
|
* - 如果 curve 是开放的,则包含所有点。
|
|
|
*
|
|
|
* \param curve 要转换的 CCurveEx 指针。
|
|
|
* \return 转换后的 Clipper2Lib::PathD。
|
|
|
*/
|
|
|
static Clipper2Lib::PathD ConvertCurveToPath(CCurveEx* curve) // 优化: pCurve -> curve
|
|
|
{
|
|
|
Clipper2Lib::PathD clipperPath;
|
|
|
clipperPath.reserve(curve->num);
|
|
|
|
|
|
for (int i = 0; i < curve->num; i++)
|
|
|
{
|
|
|
if (i != curve->num - 1)
|
|
|
{
|
|
|
clipperPath.push_back({ curve->x[i], curve->y[i] });
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Clipper2 路径不需要重复的闭合点。
|
|
|
// 如果 curve 是闭合的 (最后一个点 == 第一个点),我们省略最后一个点。
|
|
|
// 如果 curve 是开放的,我们包含所有点。
|
|
|
if (!curve->IsClosed())
|
|
|
{
|
|
|
clipperPath.push_back({ curve->x[i], curve->y[i] });
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return clipperPath;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief 将 Clipper2Lib::PathD 路径转换为一个 *闭合* 的 CCurveEx 对象。
|
|
|
*
|
|
|
* \param clipperPath 要转换的 Clipper 路径。
|
|
|
* \return 转换后的 CCurveEx 对象的 `std::unique_ptr`。
|
|
|
* 如果 `clipperPath` 的点数少于 3,则返回 `nullptr`。
|
|
|
*/
|
|
|
static std::unique_ptr<CCurveEx> ConvertPathToClosedCurve(const Clipper2Lib::PathD& clipperPath) // 优化: path -> clipperPath
|
|
|
{
|
|
|
if (clipperPath.size() < 3)
|
|
|
{
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
auto curve = std::make_unique<CCurveEx>();
|
|
|
int closedPointCount = clipperPath.size() + 1;
|
|
|
curve->Create(closedPointCount);
|
|
|
curve->num = closedPointCount;
|
|
|
|
|
|
for (size_t i = 0; i < clipperPath.size(); ++i)
|
|
|
{
|
|
|
curve->x[i] = clipperPath[i].x;
|
|
|
curve->y[i] = clipperPath[i].y;
|
|
|
}
|
|
|
|
|
|
// 复制第一个点到末尾以闭合
|
|
|
curve->x[clipperPath.size()] = clipperPath[0].x;
|
|
|
curve->y[clipperPath.size()] = clipperPath[0].y;
|
|
|
|
|
|
return curve;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (辅助函数) 计算两个整数路径 (PathD) 的交集。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Intersect(const Clipper2Lib::PathD& subjects, const Clipper2Lib::PathD& clips, Clipper2Lib::FillRule fillRule)
|
|
|
{
|
|
|
return ClipGeometry({ subjects }, { clips }, Clipper2Lib::ClipType::Intersection, fillRule);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (辅助函数) 计算两组整数路径 (PathsD) 的交集。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Intersect(const Clipper2Lib::PathsD& subjects, const Clipper2Lib::PathsD& clips, Clipper2Lib::FillRule fillRule) // 优化: polygons1/2 -> subjects/clips
|
|
|
{
|
|
|
return ClipGeometry(subjects, clips, Clipper2Lib::ClipType::Intersection, fillRule);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (辅助函数) 计算两个整数路径 (PathD) 的并集。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Union(const Clipper2Lib::PathD& subjects, const Clipper2Lib::PathD& clips, Clipper2Lib::FillRule fillRule)
|
|
|
{
|
|
|
return ClipGeometry({ subjects }, { clips }, Clipper2Lib::ClipType::Union, fillRule);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (辅助函数) 计算两组整数路径 (PathsD) 的并集。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Union(const Clipper2Lib::PathsD& subjects, const Clipper2Lib::PathsD& clips, Clipper2Lib::FillRule fillRule) // 优化: polygons1/2 -> subjects/clips
|
|
|
{
|
|
|
return ClipGeometry(subjects, clips, Clipper2Lib::ClipType::Union, fillRule);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (辅助函数) 计算两个整数路径 (PathD) 的差集 (subjects - clips)。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Difference(const Clipper2Lib::PathD& subjects, const Clipper2Lib::PathD& clips, Clipper2Lib::FillRule fillRule)
|
|
|
{
|
|
|
return ClipGeometry({ subjects }, { clips }, Clipper2Lib::ClipType::Difference, fillRule);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (辅助函数) 计算两组整数路径 (PathsD) 的差集 (subjects - clips)。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Difference(const Clipper2Lib::PathsD& subjects, const Clipper2Lib::PathsD& clips, Clipper2Lib::FillRule fillRule) // 优化: polygons1/2 -> subjects/clips
|
|
|
{
|
|
|
return ClipGeometry(subjects, clips, Clipper2Lib::ClipType::Difference, fillRule);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (辅助函数) 计算两个整数路径 (PathD) 的异或 (Xor)。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Xor(const Clipper2Lib::PathD& subjects, const Clipper2Lib::PathD& clips, Clipper2Lib::FillRule fillRule)
|
|
|
{
|
|
|
return ClipGeometry({ subjects }, { clips }, Clipper2Lib::ClipType::Xor, fillRule);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (辅助函数) 计算两组整数路径 (PathsD) 的异或 (Xor)。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD Xor(const Clipper2Lib::PathsD& subjects, const Clipper2Lib::PathsD& clips, Clipper2Lib::FillRule fillRule) // 优化: polygons1/2 -> subjects/clips
|
|
|
{
|
|
|
return ClipGeometry(subjects, clips, Clipper2Lib::ClipType::Xor, fillRule);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief 膨胀
|
|
|
*
|
|
|
* \param paths
|
|
|
* \param delta
|
|
|
* \return
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD InflatePaths(const Clipper2Lib::PathsD& paths, double delta)
|
|
|
{
|
|
|
return Clipper2Lib::InflatePaths(paths, delta, Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* \brief (核心函数) 执行通用的 Clipper2 布尔运算。
|
|
|
*
|
|
|
* 这是所有布尔运算 (Intersect, Union, Difference, Xor) 的核心实现。
|
|
|
*
|
|
|
* \param subjects 主体路径集合。
|
|
|
* \param clips 裁剪路径集合。
|
|
|
* \param clipType 布尔运算类型 (Intersection, Union, Difference, Xor)。
|
|
|
* \param fillRule 填充规则。
|
|
|
* \return 运算结果路径集合。
|
|
|
*/
|
|
|
static Clipper2Lib::PathsD ClipGeometry(const Clipper2Lib::PathsD& subjects, const Clipper2Lib::PathsD& clips, Clipper2Lib::ClipType clipType, Clipper2Lib::FillRule fillRule)
|
|
|
{
|
|
|
Clipper2Lib::ClipperD clipper;
|
|
|
|
|
|
clipper.AddSubject(subjects);
|
|
|
clipper.AddClip(clips);
|
|
|
|
|
|
Clipper2Lib::PathsD solution;
|
|
|
clipper.Execute(clipType, fillRule, solution);
|
|
|
|
|
|
return solution;
|
|
|
}
|
|
|
}; |