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

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
IT之家
IT之家
V
V2EX
Jina AI
Jina AI
V
Visual Studio Blog
有赞技术团队
有赞技术团队
博客园 - 司徒正美
博客园 - 叶小钗
The Cloudflare Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
腾讯CDC
Google Online Security Blog
Google Online Security Blog
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
N
News and Events Feed by Topic
N
News and Events Feed by Topic
The Last Watchdog
The Last Watchdog
W
WeLiveSecurity
月光博客
月光博客
Security Archives - TechRepublic
Security Archives - TechRepublic
Webroot Blog
Webroot Blog
SecWiki News
SecWiki News
博客园_首页
罗磊的独立博客
量子位
Latest news
Latest news
I
Intezer
V
Vulnerabilities – Threatpost
A
Arctic Wolf
Last Week in AI
Last Week in AI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
S
SegmentFault 最新的问题
S
Security Affairs
阮一峰的网络日志
阮一峰的网络日志
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
酷 壳 – CoolShell
酷 壳 – CoolShell
P
Palo Alto Networks Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
N
News | PayPal Newsroom

博客园 - 我才是银古

第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
第20章:实际应用与最佳实践
我才是银古 · 2026-06-24 · via 博客园 - 我才是银古

第20章:实际应用与最佳实践

20.1 概述

本章总结 Clipper2 在实际项目中的应用技巧、性能优化方法和常见问题的解决方案。

20.2 选择正确的类

20.2.1 类选择指南

需求 推荐类
整数坐标布尔运算 Clipper64
浮点坐标布尔运算 ClipperD
快速矩形裁剪 RectClip64
路径偏移 ClipperOffset
简单操作 静态方法 Clipper.*

20.2.2 静态方法 vs 类实例

// 简单操作用静态方法
Paths64 result = Clipper.Intersect(subjects, clips, FillRule.NonZero);

// 复杂操作用类实例
Clipper64 clipper = new Clipper64();
clipper.PreserveCollinear = true;
clipper.AddSubject(subjects);
clipper.AddClip(clips);
clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);

20.3 性能优化

20.3.1 重用 Clipper 实例

// 不好:每次创建新实例
foreach (var item in items)
{
    Clipper64 clipper = new Clipper64();  // 开销
    clipper.AddSubject(item.Subject);
    clipper.AddClip(item.Clip);
    clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
}

// 好:重用实例
Clipper64 clipper = new Clipper64();
foreach (var item in items)
{
    clipper.Clear();  // 清除数据,保留配置
    clipper.AddSubject(item.Subject);
    clipper.AddClip(item.Clip);
    clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
}

20.3.2 预分配容量

// 预估结果大小
int estimatedCount = subjects.Sum(p => p.Count);
Paths64 result = new Paths64(estimatedCount);

20.3.3 使用边界框过滤

// 快速跳过不相交的多边形
Rect64 clipBounds = Clipper.GetBounds(clips);

foreach (Path64 subject in subjects)
{
    Rect64 subjectBounds = Clipper.GetBounds(subject);
    
    if (!subjectBounds.Intersects(clipBounds))
    {
        // 完全不相交,跳过
        continue;
    }
    
    // 执行裁剪
    clipper.AddSubject(subject);
}

20.3.4 并行处理

// 对于大量独立的裁剪操作
var results = items.AsParallel().Select(item =>
{
    // 每个线程有自己的 Clipper 实例
    Clipper64 clipper = new Clipper64();
    clipper.AddSubject(item.Subject);
    clipper.AddClip(item.Clip);
    
    Paths64 result = new Paths64();
    clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
    return result;
}).ToList();

20.4 精度处理

20.4.1 选择合适的精度

// 屏幕坐标(像素)
ClipperD clipper = new ClipperD(0);  // 整数精度

// CAD 坐标(毫米,保留 2 位小数)
ClipperD clipper = new ClipperD(2);  // 0.01 精度

// 地理坐标(经纬度,保留 6 位小数)
ClipperD clipper = new ClipperD(6);  // 0.000001 精度

20.4.2 坐标缩放

// 手动缩放可以获得更好的控制
double scale = 1000.0;  // 保留 3 位小数

// 输入时缩放
Path64 scaledSubject = ScalePath(subject, scale);
Path64 scaledClip = ScalePath(clip, scale);

// 处理
Clipper64 clipper = new Clipper64();
clipper.AddSubject(scaledSubject);
clipper.AddClip(scaledClip);
Paths64 scaledResult = new Paths64();
clipper.Execute(ClipType.Intersection, FillRule.NonZero, scaledResult);

// 输出时还原
PathsD result = ScalePaths(scaledResult, 1.0 / scale);

20.4.3 避免坐标溢出

// 检查坐标范围
const long MaxCoord = InternalClipper.MaxCoord;

bool IsValidPath(Path64 path)
{
    foreach (var pt in path)
    {
        if (Math.Abs(pt.X) > MaxCoord || Math.Abs(pt.Y) > MaxCoord)
            return false;
    }
    return true;
}

// 必要时缩小坐标
PathD NormalizePath(PathD path, double maxRange)
{
    Rect64 bounds = Clipper.GetBounds(Clipper.ScalePath64(path, 1.0));
    double scale = maxRange / Math.Max(
        Math.Max(bounds.Width, bounds.Height), 1);
    
    return ScalePath(path, scale);
}

20.5 错误处理

20.5.1 检查输入有效性

bool IsValidPolygon(Path64 path)
{
    // 至少 3 个点
    if (path.Count < 3) return false;
    
    // 检查面积
    if (Math.Abs(Clipper.Area(path)) < 1.0) return false;
    
    // 检查退化边
    for (int i = 0; i < path.Count; i++)
    {
        int j = (i + 1) % path.Count;
        if (path[i] == path[j]) return false;
    }
    
    return true;
}

20.5.2 处理空结果

Paths64 result = new Paths64();
bool success = clipper.Execute(ClipType.Intersection, 
    FillRule.NonZero, result);

if (!success)
{
    Console.WriteLine("裁剪操作失败");
    return null;
}

if (result.Count == 0)
{
    Console.WriteLine("结果为空(可能完全不相交)");
    return result;
}

20.5.3 异常处理

try
{
    clipper.AddSubject(subject);
    clipper.AddClip(clip);
    clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
}
catch (Exception ex)
{
    // 记录错误
    Console.WriteLine($"裁剪错误: {ex.Message}");
    
    // 尝试简化后重试
    subject = Clipper.SimplifyPath(subject, 1.0);
    clip = Clipper.SimplifyPath(clip, 1.0);
    
    clipper.Clear();
    clipper.AddSubject(subject);
    clipper.AddClip(clip);
    clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
}

20.6 常见应用场景

20.6.1 地图瓦片切割

public Paths64 CutToTile(Paths64 geometry, int tileX, int tileY, int zoom)
{
    // 计算瓦片边界
    double tileSize = 360.0 / Math.Pow(2, zoom);
    double left = -180.0 + tileX * tileSize;
    double bottom = -90.0 + tileY * tileSize;
    
    // 使用 RectClip 快速裁剪
    Rect64 tileBounds = new Rect64(
        (long)(left * 1000000),
        (long)(bottom * 1000000),
        (long)((left + tileSize) * 1000000),
        (long)((bottom + tileSize) * 1000000)
    );
    
    RectClip64 rc = new RectClip64(tileBounds);
    return rc.Execute(geometry);
}

20.6.2 缓冲区分析

public Paths64 CreateBuffer(Path64 path, double distance, 
    bool isPolygon)
{
    ClipperOffset co = new ClipperOffset();
    
    // 配置参数
    co.ArcTolerance = distance * 0.01;  // 1% 精度
    
    EndType endType = isPolygon ? EndType.Polygon : EndType.Round;
    co.AddPath(path, JoinType.Round, endType);
    
    Paths64 result = new Paths64();
    co.Execute(distance, result);
    
    return result;
}

20.6.3 多边形简化

public Path64 SimplifyPolygon(Path64 path, double tolerance)
{
    // Douglas-Peucker 简化
    Path64 simplified = Clipper.SimplifyPath(path, tolerance);
    
    // 清理自相交
    Paths64 clean = Clipper.SimplifyPaths(new Paths64 { simplified }, 
        tolerance);
    
    if (clean.Count > 0)
        return clean[0];
    
    return path;
}

20.6.4 布尔运算组合

public Paths64 ComplexOperation(Paths64 baseShape, 
    Paths64 additions, Paths64 subtractions)
{
    Clipper64 clipper = new Clipper64();
    
    // 先合并基础形状和添加部分
    clipper.AddSubject(baseShape);
    clipper.AddClip(additions);
    
    Paths64 unionResult = new Paths64();
    clipper.Execute(ClipType.Union, FillRule.NonZero, unionResult);
    
    // 再减去要删除的部分
    clipper.Clear();
    clipper.AddSubject(unionResult);
    clipper.AddClip(subtractions);
    
    Paths64 finalResult = new Paths64();
    clipper.Execute(ClipType.Difference, FillRule.NonZero, finalResult);
    
    return finalResult;
}

20.6.5 路径膨胀/收缩

public Paths64 CreateOutline(Path64 shape, double outlineWidth)
{
    ClipperOffset co = new ClipperOffset();
    co.AddPath(shape, JoinType.Miter, EndType.Polygon);
    
    // 外边界
    Paths64 outer = new Paths64();
    co.Execute(outlineWidth / 2, outer);
    
    // 内边界
    co.Clear();
    co.AddPath(shape, JoinType.Miter, EndType.Polygon);
    Paths64 inner = new Paths64();
    co.Execute(-outlineWidth / 2, inner);
    
    // 相减得到轮廓
    return Clipper.Difference(outer, inner, FillRule.NonZero);
}

20.7 调试技巧

20.7.1 可视化输出

public void SaveToSvg(Paths64 paths, string filename)
{
    Rect64 bounds = Clipper.GetBounds(paths);
    
    using (StreamWriter sw = new StreamWriter(filename))
    {
        sw.WriteLine($"<svg viewBox=\"{bounds.left} {bounds.top} " +
            $"{bounds.Width} {bounds.Height}\" " +
            $"xmlns=\"http://www.w3.org/2000/svg\">");
        
        foreach (Path64 path in paths)
        {
            sw.Write("<polygon points=\"");
            foreach (Point64 pt in path)
            {
                sw.Write($"{pt.X},{pt.Y} ");
            }
            sw.WriteLine("\" fill=\"blue\" stroke=\"black\" />");
        }
        
        sw.WriteLine("</svg>");
    }
}

20.7.2 日志记录

public void LogOperation(string operation, Paths64 subject, 
    Paths64 clip, Paths64 result)
{
    Console.WriteLine($"=== {operation} ===");
    Console.WriteLine($"Subject: {subject.Count} paths, " +
        $"{subject.Sum(p => p.Count)} points");
    Console.WriteLine($"Clip: {clip.Count} paths, " +
        $"{clip.Sum(p => p.Count)} points");
    Console.WriteLine($"Result: {result.Count} paths, " +
        $"{result.Sum(p => p.Count)} points");
    Console.WriteLine($"Area: {result.Sum(p => Math.Abs(Clipper.Area(p)))}");
}

20.7.3 边界情况测试

[Test]
public void TestEdgeCases()
{
    // 空输入
    Assert.DoesNotThrow(() => 
        Clipper.Intersect(new Paths64(), new Paths64(), FillRule.NonZero));
    
    // 单点
    var singlePoint = new Paths64 { new Path64 { new Point64(0, 0) } };
    Assert.DoesNotThrow(() => 
        Clipper.Union(singlePoint, FillRule.NonZero));
    
    // 共线点
    var collinear = new Path64 { 
        new Point64(0, 0), 
        new Point64(50, 0), 
        new Point64(100, 0) 
    };
    Assert.DoesNotThrow(() => 
        Clipper.SimplifyPath(collinear, 0.0));
}

20.8 内存管理

20.8.1 及时清理

// 大批量处理时,定期调用 GC
int batchSize = 1000;
int processed = 0;

foreach (var item in items)
{
    ProcessItem(item);
    processed++;
    
    if (processed % batchSize == 0)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

20.8.2 避免内存泄漏

// 使用 using 或 try-finally 确保清理
void ProcessWithCleanup()
{
    Clipper64 clipper = new Clipper64();
    Paths64 result = new Paths64();
    
    try
    {
        clipper.AddSubject(subjects);
        clipper.AddClip(clips);
        clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
    }
    finally
    {
        clipper.Clear();
        // 如果不再需要 result,也清理
    }
}

20.9 最佳实践清单

20.9.1 输入验证

  • 检查路径点数(闭合路径 ≥ 3,开放路径 ≥ 2)
  • 检查坐标范围不超过 MaxCoord
  • 移除重复点
  • 确保路径方向一致

20.9.2 性能优化

  • 重用 Clipper 实例
  • 使用边界框预过滤
  • 对矩形裁剪使用 RectClip64
  • 考虑并行处理

20.9.3 精度控制

  • 选择合适的精度级别
  • 理解整数与浮点的转换
  • 避免精度丢失

20.9.4 错误处理

  • 检查 Execute 返回值
  • 处理空结果
  • 捕获并记录异常

20.10 本章小结

使用 Clipper2 的最佳实践:

  1. 选择正确的类:根据需求选择 Clipper64、ClipperD 或 RectClip64
  2. 性能优化:重用实例、预过滤、并行处理
  3. 精度处理:选择合适精度、避免溢出
  4. 错误处理:验证输入、处理异常、检查结果
  5. 内存管理:及时清理、避免泄漏

遵循这些最佳实践可以帮助你在项目中高效、正确地使用 Clipper2。


上一章:PolyTree多边形树结构 | 返回目录


教程总结

本教程全面介绍了 Clipper2 C# 源代码的各个方面:

  1. 基础篇(第1-5章):核心数据结构、路径表示、枚举类型
  2. 核心架构篇(第6-10章):内部工具类、高精度运算、扫描线算法核心结构
  3. 输出结构篇(第11-13章):输出多边形构建、Clipper64 和 ClipperD 类
  4. 布尔运算篇(第14-15章):执行流程、填充规则
  5. 高级功能篇(第16-18章):偏移、矩形裁剪、Minkowski 运算
  6. 进阶篇(第19-20章):PolyTree 结构、实际应用

希望本教程能帮助你深入理解 Clipper2 的实现原理,在实际项目中更好地应用这个优秀的几何库。