#pragma once #include "DrawOperator/CurveEx.h" #include "clipper2/clipper.h" #include /** * \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 ConvertPathToClosedCurve(const Clipper2Lib::PathD& clipperPath) // 优化: path -> clipperPath { if (clipperPath.size() < 3) { return nullptr; } auto curve = std::make_unique(); 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; } };