惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Hacker News
The Hacker News
C
Cisco Blogs
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
S
Security Affairs
PCI Perspectives
PCI Perspectives
The Last Watchdog
The Last Watchdog
AWS News Blog
AWS News Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
N
News and Events Feed by Topic
W
WeLiveSecurity
T
Tenable Blog
L
LINUX DO - 最新话题
T
Tor Project blog
Help Net Security
Help Net Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Proofpoint News Feed
爱范儿
爱范儿
O
OpenAI News
Hacker News - Newest:
Hacker News - Newest: "LLM"
Y
Y Combinator Blog
I
Intezer
C
Check Point Blog
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
S
Securelist
P
Privacy International News Feed
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
Vulnerabilities – Threatpost
Schneier on Security
Schneier on Security
量子位
SecWiki News
SecWiki News
L
Lohrmann on Cybersecurity
T
Threat Research - Cisco Blogs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
M
MIT News - Artificial intelligence
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Scott Helme
Scott Helme
H
Help Net Security
Vercel News
Vercel News
云风的 BLOG
云风的 BLOG
Spread Privacy
Spread Privacy
Know Your Adversary
Know Your Adversary
I
InfoQ
TaoSecurity Blog
TaoSecurity Blog
Blog — PlanetScale
Blog — PlanetScale
N
News | PayPal Newsroom
小众软件
小众软件
C
CERT Recently Published Vulnerability Notes

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第六章 高级应用与性能优化(C#版)
我才是银古 · 2026-06-19 · via 博客园 - 我才是银古

第六章 高级应用与性能优化(C#版)

6.1 引言

在前面的章节中,我们学习了Clipper2的核心功能:布尔运算、多边形偏移、矩形裁剪和闵可夫斯基运算。本章将深入探讨Clipper2 C#版本的高级应用技巧、性能优化方法以及与其他.NET工具和系统的集成方式。通过本章的学习,您将能够在实际.NET项目中更加高效地使用Clipper2。

6.2 Z轴值支持(USINGZ)

6.2.1 启用Z轴支持

Clipper2支持在每个顶点上附加一个Z轴值。这个功能需要通过编译选项启用:

C++

// 在包含头文件之前定义宏
#define CLIPPER2_USINGZ
#include "clipper2/clipper.h"

或者在CMake中启用:

add_compile_definitions(CLIPPER2_USINGZ)

C#

在C#版本中,需要添加USINGZ程序集:

using Clipper2Lib;
using Clipper2Lib.USINGZ;  // 添加Z值支持

6.2.2 Z值的用途

Z值可以用于多种目的:

存储顶点标识

// 为每个顶点分配唯一ID
Path64 path;
for (int i = 0; i < points.size(); i++) {
    path.push_back(Point64(points[i].x, points[i].y, i));  // z = 顶点索引
}

存储高程数据

// 在GIS应用中存储高程
Path64 contour;
for (const auto& pt : terrainPoints) {
    contour.push_back(Point64(
        static_cast<int64_t>(pt.x * 1000),
        static_cast<int64_t>(pt.y * 1000),
        static_cast<int64_t>(pt.elevation * 1000)
    ));
}

存储自定义属性

// 存储颜色索引或材质ID
Path64 polygon;
for (const auto& vertex : vertices) {
    polygon.push_back(Point64(
        vertex.x, vertex.y,
        vertex.materialId  // 材质ID作为Z值
    ));
}

6.2.3 Z值回调函数

当Clipper2执行布尔运算时,可能会产生新的顶点(在两边相交处)。通过设置回调函数,可以控制这些新顶点的Z值如何计算:

#define CLIPPER2_USINGZ
#include "clipper2/clipper.h"

using namespace Clipper2Lib;

// Z值回调函数
void ZCallback(const Point64& e1bot, const Point64& e1top,
               const Point64& e2bot, const Point64& e2top,
               Point64& pt) {
    // 使用线性插值计算新顶点的Z值
    
    // 计算交点在edge1上的位置比例
    double t1 = 0.5;  // 简化处理,实际应该根据交点位置计算
    
    // 插值Z值
    int64_t z1 = e1bot.z + static_cast<int64_t>((e1top.z - e1bot.z) * t1);
    int64_t z2 = e2bot.z + static_cast<int64_t>((e2top.z - e2bot.z) * 0.5);
    
    // 使用平均值
    pt.z = (z1 + z2) / 2;
}

int main() {
    Clipper64 clipper;
    
    // 设置Z值回调
    clipper.SetZCallback(ZCallback);
    
    // 添加多边形(带Z值)
    Paths64 subject;
    Path64 path;
    path.push_back(Point64(0, 0, 100));
    path.push_back(Point64(100, 0, 200));
    path.push_back(Point64(100, 100, 300));
    path.push_back(Point64(0, 100, 400));
    subject.push_back(path);
    
    clipper.AddSubject(subject);
    clipper.AddClip(clipPaths);
    
    Paths64 result;
    clipper.Execute(ClipType::Intersection, FillRule::NonZero, result);
    
    // result中的顶点包含计算后的Z值
    return 0;
}

6.2.4 C#中的Z值回调

using Clipper2Lib;

class Program
{
    // Z值回调委托
    static void MyZCallback(Point64 e1bot, Point64 e1top,
                            Point64 e2bot, Point64 e2top,
                            ref Point64 pt)
    {
        // 计算新顶点的Z值
        pt.Z = (e1bot.Z + e1top.Z + e2bot.Z + e2top.Z) / 4;
    }
    
    static void Main()
    {
        Clipper64 clipper = new Clipper64();
        clipper.ZCallback = MyZCallback;
        
        // ... 执行运算
    }
}

6.3 输出格式控制

6.3.1 PolyTree与Paths的选择

Clipper2支持两种输出格式:

Paths输出

Clipper64 clipper;
clipper.AddSubject(subject);
clipper.AddClip(clip);

Paths64 result;
clipper.Execute(ClipType::Intersection, FillRule::NonZero, result);
// result是一个扁平的路径列表,不包含层次信息

PolyTree输出

Clipper64 clipper;
clipper.AddSubject(subject);
clipper.AddClip(clip);

PolyTree64 tree;
Paths64 openPaths;  // 开放路径输出
clipper.Execute(ClipType::Intersection, FillRule::NonZero, tree, openPaths);
// tree包含完整的层次信息

选择建议

场景 推荐格式
简单的多边形处理 Paths
需要区分外边界和孔洞 PolyTree
后续需要进行嵌套分析 PolyTree
性能敏感的场景 Paths(略快)
需要遍历层次结构 PolyTree

6.3.2 保留共线点

默认情况下,Clipper2会移除共线的点(位于同一直线上的中间点)。可以通过设置选项保留这些点:

Clipper64 clipper;
clipper.PreserveCollinear(true);  // 保留共线点

clipper.AddSubject(subject);
Paths64 result;
clipper.Execute(ClipType::Union, FillRule::NonZero, result);

应用场景

  • 需要保持原始顶点数量
  • 后续处理依赖于特定的顶点位置
  • 与其他系统交换数据时需要保持一致性

6.3.3 反转输出方向

可以设置输出多边形的方向反转:

Clipper64 clipper;
clipper.ReverseSolution(true);  // 反转输出方向

// 原本逆时针的外边界会变成顺时针
// 原本顺时针的孔洞会变成逆时针

应用场景

  • 与使用不同顶点顺序约定的系统集成
  • 图形渲染中的背面剔除

6.4 错误处理与验证

6.4.1 输入验证

// 验证路径是否有效
bool ValidatePath(const Path64& path) {
    // 至少需要3个顶点
    if (path.size() < 3) {
        return false;
    }
    
    // 检查是否有重复的相邻顶点
    for (size_t i = 0; i < path.size(); i++) {
        if (path[i] == path[(i + 1) % path.size()]) {
            return false;
        }
    }
    
    // 检查面积是否为零
    double area = Area(path);
    if (std::abs(area) < 1.0) {
        return false;
    }
    
    return true;
}

// 验证所有路径
bool ValidatePaths(const Paths64& paths) {
    for (const auto& path : paths) {
        if (!ValidatePath(path)) {
            return false;
        }
    }
    return true;
}

6.4.2 结果验证

// 验证布尔运算结果
bool ValidateResult(const Paths64& result, ClipType clipType,
                    const Paths64& subject, const Paths64& clip) {
    if (result.empty()) {
        // 对于某些情况,空结果可能是正确的
        if (clipType == ClipType::Intersection) {
            // 如果没有相交,结果可以为空
            return true;
        }
    }
    
    // 检查结果是否有效
    for (const auto& path : result) {
        if (path.size() < 3) {
            return false;
        }
    }
    
    // 可以添加更多验证逻辑...
    return true;
}

6.4.3 异常处理

try {
    Clipper64 clipper;
    clipper.AddSubject(subject);
    clipper.AddClip(clip);
    
    Paths64 result;
    bool success = clipper.Execute(ClipType::Intersection, FillRule::NonZero, result);
    
    if (!success) {
        // 处理执行失败
        std::cerr << "Clipper执行失败" << std::endl;
    }
} catch (const std::exception& e) {
    std::cerr << "异常: " << e.what() << std::endl;
}

6.5 性能优化技巧

6.5.1 减少顶点数量

顶点数量是影响性能的主要因素。可以通过路径简化来减少顶点:

// 使用Douglas-Peucker算法简化路径
PathsD simplified = SimplifyPaths(paths, tolerance);
// tolerance值越大,简化越多,但形状变形也越大

// 或者使用Ramer-Douglas-Peucker变体
PathsD rdpSimplified = RamerDouglasPeucker(paths, tolerance);

选择合适的容差

// 根据应用场景选择容差
double tolerance;
if (isScreenRendering) {
    // 屏幕渲染:1像素以下的细节不可见
    tolerance = 1.0;
} else if (isCNC) {
    // CNC加工:保持0.01mm精度
    tolerance = 10;  // 假设单位是0.001mm
} else if (isGIS) {
    // GIS应用:根据地图比例尺选择
    tolerance = mapScale / 1000.0;
}

6.5.2 使用边界框预筛选

在执行布尔运算之前,使用边界框快速排除不可能相交的情况:

bool MayIntersect(const Paths64& paths1, const Paths64& paths2) {
    Rect64 bounds1 = Bounds(paths1);
    Rect64 bounds2 = Bounds(paths2);
    return bounds1.Intersects(bounds2);
}

// 使用预筛选
if (MayIntersect(subject, clip)) {
    Paths64 result = Intersect(subject, clip, FillRule::NonZero);
    // 处理结果
} else {
    // 不相交,跳过计算
}

6.5.3 批量操作优化

// 不优化的方式:每次创建新的Clipper对象
for (const auto& subject : subjects) {
    Clipper64 clipper;  // 每次创建新对象
    clipper.AddSubject(subject);
    clipper.AddClip(clip);
    Paths64 result;
    clipper.Execute(ClipType::Intersection, FillRule::NonZero, result);
}

// 优化的方式:复用Clipper对象
Clipper64 clipper;
for (const auto& subject : subjects) {
    clipper.Clear();  // 清空但保留内存分配
    clipper.AddSubject(subject);
    clipper.AddClip(clip);
    Paths64 result;
    clipper.Execute(ClipType::Intersection, FillRule::NonZero, result);
}

6.5.4 并行处理

#include <thread>
#include <future>
#include <vector>

std::vector<Paths64> ParallelProcess(
        const std::vector<Paths64>& subjects,
        const Paths64& clip,
        int numThreads) {
    
    std::vector<std::future<Paths64>> futures;
    std::vector<Paths64> results(subjects.size());
    
    // 启动异步任务
    for (size_t i = 0; i < subjects.size(); i++) {
        futures.push_back(std::async(std::launch::async, 
            [&subjects, &clip, i]() {
                // 每个线程有自己的Clipper实例
                Clipper64 clipper;
                clipper.AddSubject(subjects[i]);
                clipper.AddClip(clip);
                Paths64 result;
                clipper.Execute(ClipType::Intersection, FillRule::NonZero, result);
                return result;
            }
        ));
    }
    
    // 收集结果
    for (size_t i = 0; i < futures.size(); i++) {
        results[i] = futures[i].get();
    }
    
    return results;
}

6.5.5 内存优化

// 预分配内存
Paths64 subject;
subject.reserve(100);  // 预分配100个路径的空间

for (int i = 0; i < 100; i++) {
    Path64 path;
    path.reserve(1000);  // 每个路径预分配1000个顶点
    // ... 填充path
    subject.push_back(std::move(path));  // 使用移动语义
}

6.5.6 选择合适的数据类型

// 如果坐标范围较小,可以考虑使用较小的类型
// 但Clipper2默认使用int64_t以确保精度

// 对于浮点数,如果不需要高精度,可以降低精度
PathsD floatPaths = ...;
int precision = 2;  // 只保留2位小数
Paths64 intPaths = ConvertToInt64(floatPaths, std::pow(10, precision));

6.6 与其他库的集成

6.6.1 与OpenGL集成

#include <GL/gl.h>
#include "clipper2/clipper.h"

void RenderPaths(const Paths64& paths) {
    for (const auto& path : paths) {
        glBegin(GL_LINE_LOOP);
        for (const auto& pt : path) {
            glVertex2d(pt.x / 1000.0, pt.y / 1000.0);
        }
        glEnd();
    }
}

void RenderFilledPaths(const Paths64& paths) {
    // 使用三角化(需要额外的三角化库)
    std::vector<Triangle> triangles = Triangulate(paths);
    
    glBegin(GL_TRIANGLES);
    for (const auto& tri : triangles) {
        glVertex2d(tri.a.x / 1000.0, tri.a.y / 1000.0);
        glVertex2d(tri.b.x / 1000.0, tri.b.y / 1000.0);
        glVertex2d(tri.c.x / 1000.0, tri.c.y / 1000.0);
    }
    glEnd();
}

6.6.2 与SVG集成

Clipper2提供了SVG辅助工具:

#include "clipper2/clipper.h"
#include "utils/clipper.svg.utils.h"

void SaveToSVG(const Paths64& paths, const std::string& filename) {
    SvgWriter writer;
    
    writer.AddPaths(paths, true,   // 闭合路径
                    FillRule::NonZero,
                    0x1000AA00,    // 填充颜色
                    0xFF009900,    // 描边颜色
                    1);            // 描边宽度
    
    writer.SaveToFile(filename, 800, 600);  // 800x600的SVG
}

6.6.3 与GeoJSON集成

#include <nlohmann/json.hpp>

using json = nlohmann::json;

// 将Paths转换为GeoJSON
json PathsToGeoJSON(const Paths64& paths, double scale = 1000.0) {
    json features = json::array();
    
    for (const auto& path : paths) {
        json coordinates = json::array();
        
        for (const auto& pt : path) {
            coordinates.push_back({pt.x / scale, pt.y / scale});
        }
        // 闭合多边形
        coordinates.push_back({path[0].x / scale, path[0].y / scale});
        
        json feature = {
            {"type", "Feature"},
            {"geometry", {
                {"type", "Polygon"},
                {"coordinates", json::array({coordinates})}
            }},
            {"properties", json::object()}
        };
        
        features.push_back(feature);
    }
    
    return {
        {"type", "FeatureCollection"},
        {"features", features}
    };
}

// 从GeoJSON读取Paths
Paths64 GeoJSONToPaths(const json& geojson, double scale = 1000.0) {
    Paths64 result;
    
    for (const auto& feature : geojson["features"]) {
        const auto& geometry = feature["geometry"];
        
        if (geometry["type"] == "Polygon") {
            for (const auto& ring : geometry["coordinates"]) {
                Path64 path;
                for (size_t i = 0; i < ring.size() - 1; i++) {  // 跳过闭合点
                    path.push_back(Point64(
                        static_cast<int64_t>(ring[i][0].get<double>() * scale),
                        static_cast<int64_t>(ring[i][1].get<double>() * scale)
                    ));
                }
                result.push_back(path);
            }
        }
    }
    
    return result;
}

6.6.4 与GDAL/OGR集成

#include <ogrsf_frmts.h>
#include "clipper2/clipper.h"

// 从OGR几何体转换
Paths64 OGRGeometryToPaths(OGRGeometry* geom, double scale = 1000.0) {
    Paths64 result;
    
    if (geom->getGeometryType() == wkbPolygon) {
        OGRPolygon* polygon = (OGRPolygon*)geom;
        
        // 外环
        OGRLinearRing* exteriorRing = polygon->getExteriorRing();
        Path64 exterior;
        for (int i = 0; i < exteriorRing->getNumPoints() - 1; i++) {
            exterior.push_back(Point64(
                static_cast<int64_t>(exteriorRing->getX(i) * scale),
                static_cast<int64_t>(exteriorRing->getY(i) * scale)
            ));
        }
        result.push_back(exterior);
        
        // 内环(孔洞)
        for (int r = 0; r < polygon->getNumInteriorRings(); r++) {
            OGRLinearRing* ring = polygon->getInteriorRing(r);
            Path64 hole;
            for (int i = 0; i < ring->getNumPoints() - 1; i++) {
                hole.push_back(Point64(
                    static_cast<int64_t>(ring->getX(i) * scale),
                    static_cast<int64_t>(ring->getY(i) * scale)
                ));
            }
            result.push_back(hole);
        }
    }
    
    return result;
}

// 转换回OGR几何体
OGRGeometry* PathsToOGRGeometry(const Paths64& paths, double scale = 1000.0) {
    if (paths.empty()) return nullptr;
    
    OGRPolygon* polygon = new OGRPolygon();
    
    for (size_t i = 0; i < paths.size(); i++) {
        const auto& path = paths[i];
        OGRLinearRing* ring = new OGRLinearRing();
        
        for (const auto& pt : path) {
            ring->addPoint(pt.x / scale, pt.y / scale);
        }
        ring->closeRings();
        
        if (i == 0) {
            polygon->addRingDirectly(ring);
        } else {
            polygon->addRingDirectly(ring);
        }
    }
    
    return polygon;
}

6.7 调试与可视化

6.7.1 使用SVG进行调试

void DebugVisualize(const Paths64& subject,
                    const Paths64& clip,
                    const Paths64& result,
                    const std::string& filename) {
    SvgWriter svg;
    
    // 绘制主体(半透明蓝色)
    svg.AddPaths(subject, true, FillRule::NonZero,
                 0x200000FF, 0xFF0000FF, 2);
    
    // 绘制裁剪区域(半透明红色)
    svg.AddPaths(clip, true, FillRule::NonZero,
                 0x20FF0000, 0xFFFF0000, 2);
    
    // 绘制结果(半透明绿色)
    svg.AddPaths(result, true, FillRule::NonZero,
                 0x4000FF00, 0xFF00FF00, 3);
    
    svg.SaveToFile(filename, 800, 600);
}

6.7.2 打印路径信息

void PrintPathInfo(const Paths64& paths, const std::string& name) {
    std::cout << "===== " << name << " =====" << std::endl;
    std::cout << "路径数量: " << paths.size() << std::endl;
    
    size_t totalVertices = 0;
    for (const auto& path : paths) {
        totalVertices += path.size();
    }
    std::cout << "总顶点数: " << totalVertices << std::endl;
    
    double totalArea = Area(paths);
    std::cout << "总面积: " << totalArea << std::endl;
    
    Rect64 bounds = Bounds(paths);
    std::cout << "边界框: (" << bounds.left << ", " << bounds.top 
              << ") - (" << bounds.right << ", " << bounds.bottom << ")" << std::endl;
    
    for (size_t i = 0; i < paths.size(); i++) {
        const auto& path = paths[i];
        double area = Area(path);
        bool isHole = area < 0;
        std::cout << "  路径 " << i << ": " 
                  << path.size() << " 顶点, "
                  << "面积 " << std::abs(area)
                  << (isHole ? " (孔洞)" : " (外边界)")
                  << std::endl;
    }
}

6.7.3 性能分析

#include <chrono>
#include <iostream>

class Timer {
public:
    Timer(const std::string& name) : name_(name) {
        start_ = std::chrono::high_resolution_clock::now();
    }
    
    ~Timer() {
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start_);
        std::cout << name_ << ": " << duration.count() / 1000.0 << " ms" << std::endl;
    }
    
private:
    std::string name_;
    std::chrono::high_resolution_clock::time_point start_;
};

// 使用
void ProfileOperation() {
    Paths64 subject = ...;
    Paths64 clip = ...;
    
    {
        Timer t("交集运算");
        Paths64 result = Intersect(subject, clip, FillRule::NonZero);
    }
    
    {
        Timer t("偏移运算");
        Paths64 result = InflatePaths(subject, 10, JoinType::Round, EndType::Polygon);
    }
}

6.8 最佳实践

6.8.1 代码组织

// 封装Clipper2操作的工具类
class GeometryProcessor {
public:
    GeometryProcessor() : scale_(1000.0) {}
    
    // 设置精度
    void SetScale(double scale) { scale_ = scale; }
    
    // 布尔运算接口
    PathsD Intersect(const PathsD& subject, const PathsD& clip) {
        Paths64 subj64 = ConvertToInt(subject);
        Paths64 clip64 = ConvertToInt(clip);
        Paths64 result = Clipper2Lib::Intersect(subj64, clip64, FillRule::NonZero);
        return ConvertToDouble(result);
    }
    
    PathsD Union(const PathsD& paths) {
        Paths64 paths64 = ConvertToInt(paths);
        Paths64 result = Clipper2Lib::Union(paths64, FillRule::NonZero);
        return ConvertToDouble(result);
    }
    
    PathsD Offset(const PathsD& paths, double delta) {
        Paths64 paths64 = ConvertToInt(paths);
        int64_t delta64 = static_cast<int64_t>(delta * scale_);
        Paths64 result = InflatePaths(paths64, delta64, JoinType::Round, EndType::Polygon);
        return ConvertToDouble(result);
    }
    
private:
    double scale_;
    
    Paths64 ConvertToInt(const PathsD& paths) {
        return ScalePaths<int64_t, double>(paths, scale_);
    }
    
    PathsD ConvertToDouble(const Paths64& paths) {
        return ScalePaths<double, int64_t>(paths, 1.0 / scale_);
    }
};

6.8.2 错误处理策略

class ClipperException : public std::runtime_error {
public:
    ClipperException(const std::string& msg) : std::runtime_error(msg) {}
};

Paths64 SafeIntersect(const Paths64& subject, const Paths64& clip) {
    // 输入验证
    if (subject.empty()) {
        throw ClipperException("主体多边形为空");
    }
    if (clip.empty()) {
        throw ClipperException("裁剪多边形为空");
    }
    
    // 检查边界框是否相交
    Rect64 subjectBounds = Bounds(subject);
    Rect64 clipBounds = Bounds(clip);
    if (!subjectBounds.Intersects(clipBounds)) {
        return Paths64();  // 不相交,返回空
    }
    
    try {
        Clipper64 clipper;
        clipper.AddSubject(subject);
        clipper.AddClip(clip);
        
        Paths64 result;
        bool success = clipper.Execute(ClipType::Intersection, FillRule::NonZero, result);
        
        if (!success) {
            throw ClipperException("Clipper执行失败");
        }
        
        return result;
    } catch (const std::exception& e) {
        throw ClipperException(std::string("Clipper错误: ") + e.what());
    }
}

6.8.3 配置管理

struct ClipperConfig {
    double scale = 1000.0;
    FillRule fillRule = FillRule::NonZero;
    JoinType joinType = JoinType::Round;
    EndType endType = EndType::Polygon;
    double miterLimit = 2.0;
    double arcTolerance = 0.25;
    bool preserveCollinear = false;
    bool reverseSolution = false;
};

class ConfigurableClipper {
public:
    void SetConfig(const ClipperConfig& config) {
        config_ = config;
    }
    
    Paths64 Offset(const Paths64& paths, double delta) {
        ClipperOffset offsetter;
        offsetter.MiterLimit(config_.miterLimit);
        offsetter.ArcTolerance(config_.arcTolerance);
        offsetter.PreserveCollinear(config_.preserveCollinear);
        offsetter.ReverseSolution(config_.reverseSolution);
        
        offsetter.AddPaths(paths, config_.joinType, config_.endType);
        
        Paths64 result;
        offsetter.Execute(delta * config_.scale, result);
        return result;
    }
    
private:
    ClipperConfig config_;
};

6.9 常见陷阱与解决方案

6.9.1 坐标范围溢出

// 错误:坐标过大可能导致溢出
Path64 badPath = MakePath({
    1e18, 0,
    1e18, 1e18,
    0, 1e18
});

// 正确:使用合理的缩放
const double scale = 1000000.0;  // 6位小数精度
Path64 goodPath = MakePath({
    static_cast<int64_t>(1e12 * scale), 0,
    static_cast<int64_t>(1e12 * scale), static_cast<int64_t>(1e12 * scale),
    0, static_cast<int64_t>(1e12 * scale)
});

6.9.2 填充规则不匹配

// 问题:使用不匹配的填充规则
Paths64 paths;
paths.push_back(MakePath({0, 0, 100, 0, 100, 100, 0, 100}));  // 逆时针
paths.push_back(MakePath({25, 25, 25, 75, 75, 75, 75, 25}));  // 顺时针孔洞

// 使用EvenOdd规则可能不会正确识别孔洞
// Paths64 result = Union(paths, FillRule::EvenOdd);

// 正确:使用NonZero规则并确保方向正确
Paths64 result = Union(paths, FillRule::NonZero);

6.9.3 精度损失

// 问题:浮点数精度不足
double x = 1.23456789012345;  // 精度可能丢失

// 解决:使用足够的缩放因子
int64_t x_int = static_cast<int64_t>(x * 1e10);  // 保留10位小数

6.10 本章小结

本章我们学习了Clipper2的高级应用技巧:

  1. Z轴支持:启用USINGZ、Z值用途、Z值回调
  2. 输出格式控制:PolyTree与Paths选择、保留共线点、反转输出
  3. 错误处理:输入验证、结果验证、异常处理
  4. 性能优化:减少顶点、边界框预筛选、批量操作、并行处理、内存优化
  5. 与其他库集成:OpenGL、SVG、GeoJSON、GDAL/OGR
  6. 调试与可视化:SVG调试、打印信息、性能分析
  7. 最佳实践:代码组织、错误处理策略、配置管理
  8. 常见陷阱:坐标溢出、填充规则不匹配、精度损失

通过本章的学习,您应该能够在实际项目中更加高效、可靠地使用Clipper2库。