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

推荐订阅源

C
Check Point Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
I
Intezer
P
Privacy & Cybersecurity Law Blog
C
CERT Recently Published Vulnerability Notes
T
Tor Project blog
K
Kaspersky official blog
AWS News Blog
AWS News Blog
Schneier on Security
Schneier on Security
雷峰网
雷峰网
www.infosecurity-magazine.com
www.infosecurity-magazine.com
宝玉的分享
宝玉的分享
G
Google Developers Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Forbes - Security
Forbes - Security
T
The Blog of Author Tim Ferriss
S
Security @ Cisco Blogs
NISL@THU
NISL@THU
N
News and Events Feed by Topic
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
爱范儿
爱范儿
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
Blog — PlanetScale
Blog — PlanetScale
Help Net Security
Help Net Security
F
Full Disclosure
V
Vulnerabilities – Threatpost
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
Arctic Wolf
D
Docker
T
Tailwind CSS Blog
L
LangChain Blog
The Last Watchdog
The Last Watchdog
美团技术团队
博客园 - Franky
H
Hacker News: Front Page
Stack Overflow Blog
Stack Overflow Blog
W
WeLiveSecurity
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Recorded Future
Recorded Future
V
Visual Studio Blog
N
Netflix TechBlog - Medium
Hacker News: Ask HN
Hacker News: Ask HN
博客园 - 司徒正美
Cyberwarzone
Cyberwarzone
S
Schneier on Security
Know Your Adversary
Know Your Adversary

博客园 - 我才是银古

第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
第1章:Clipper2 概述与入门
我才是银古 · 2026-06-24 · via 博客园 - 我才是银古

第1章:Clipper2 概述与入门

1.1 引言

在计算机图形学、地理信息系统(GIS)、计算机辅助设计(CAD)以及游戏开发等领域,多边形的几何运算是一个基础且至关重要的功能。无论是地图数据的处理、建筑设计中的布尔运算、游戏中的碰撞检测,还是工业制造中的刀具路径规划,都需要对多边形进行精确的裁剪、合并、求交、偏移等操作。

Clipper2 是由澳大利亚程序员 Angus Johnson 开发的 Clipper1 库的全面升级版本。它不仅继承了 Clipper1 的所有功能,还在算法效率、代码结构、API 设计等方面进行了大量改进。本教程将深入解读其 C# 源代码实现,帮助读者理解核心算法原理和代码设计。

1.2 Clipper2 vs Clipper1

1.2.1 主要改进

特性 Clipper1 Clipper2
坐标类型 仅整数(Int64/Int32) 整数 + 浮点双精度
命名空间 ClipperLib Clipper2Lib / Clipper2ZLib
类型别名 Path = List Path64 类、PathD 类
Z坐标支持 条件编译 条件编译(USINGZ)
矩形裁剪 无专门优化 RectClip64 高速算法
三角剖分 内置 Delaunay 三角剖分
对象池 内置对象池减少 GC
API 风格 较繁琐 更简洁的静态方法

1.2.2 代码结构对比

Clipper1:所有代码在单个 clipper.cs 文件中,约 4800 行。

Clipper2:模块化设计,分为多个源文件:

CSharp/Clipper2Lib/
├── Clipper.cs           # 静态工具方法
├── Clipper.Core.cs      # 核心数据结构
├── Clipper.Engine.cs    # 裁剪引擎核心
├── Clipper.Offset.cs    # 路径偏移功能
├── Clipper.RectClip.cs  # 矩形裁剪优化
├── Clipper.Minkowski.cs # Minkowski 和/差
└── Clipper.Extras.cs    # 扩展功能(三角剖分等)

1.3 源代码头部声明

每个源文件都以标准的版权声明开始:

/*******************************************************************************
* Author    :  Angus Johnson                                                   *
* Date      :  21 February 2026                                                *
* Website   :  https://www.angusj.com                                          *
* Copyright :  Angus Johnson 2010-2026                                         *
* Purpose   :  This is the main polygon clipping module                        *
* Thanks    :  Special thanks to Thong Nguyen, Guus Kuiper, Phil Stopford,     *
*           :  and Daniel Gosnell for their invaluable assistance with C#.     *
* License   :  https://www.boost.org/LICENSE_1_0.txt                           *
*******************************************************************************/

从声明中可以看出:

  1. 开源许可:采用 Boost Software License,允许商业和非商业使用
  2. 持续维护:项目从 2010 年开始,持续更新至今
  3. 社区贡献:感谢多位开发者对 C# 版本的贡献

1.4 预处理器指令

Clipper2 使用预处理器指令控制编译选项:

#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

#if USINGZ
namespace Clipper2ZLib
#else
namespace Clipper2Lib
#endif

1.4.1 nullable enable

#nullable enable

这是 C# 8.0 引入的可空引用类型特性。启用后:

  • 引用类型默认不可为 null
  • 可空引用类型需显式声明为 Type?
  • 编译器会在可能的空引用处发出警告

这有助于在编译期发现潜在的空引用异常,提高代码质量。

1.4.2 USINGZ 条件编译

#if USINGZ
namespace Clipper2ZLib
#else
namespace Clipper2Lib
#endif
  • 默认(未定义 USINGZ):使用 Clipper2Lib 命名空间,坐标只有 X、Y
  • 定义 USINGZ:使用 Clipper2ZLib 命名空间,坐标增加 Z 分量

启用 Z 坐标后,点结构变为:

public struct Point64
{
    public long X;
    public long Y;
#if USINGZ
    public long Z;
#endif
}

1.4.3 性能优化特性

源代码大量使用 MethodImplOptions.AggressiveInlining

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPositive(Path64 poly)
{
    return Area(poly) >= 0;
}

这告诉 JIT 编译器尽可能将方法内联,减少方法调用开销,对于频繁调用的小方法特别有效。

1.5 核心类型概览

1.5.1 公共类型(用户直接使用)

类型 说明
Point64 64位整数坐标点结构
PointD 双精度浮点坐标点结构
Path64 整数路径类(继承 List<Point64>
PathD 浮点路径类(继承 List<PointD>
Paths64 整数路径集合类(继承 List<Path64>
PathsD 浮点路径集合类(继承 List<PathD>
Rect64 64位整数矩形结构
RectD 双精度浮点矩形结构
Clipper64 整数坐标裁剪类
ClipperD 浮点坐标裁剪类
ClipperOffset 路径偏移类
PolyTree64 整数多边形树
PolyTreeD 浮点多边形树

1.5.2 枚举类型

枚举 说明
ClipType 裁剪操作类型(交、并、差、异或)
PathType 路径类型(主体、裁剪)
FillRule 填充规则(奇偶、非零、正向、负向)
JoinType 连接类型(斜接、方形、斜角、圆形)
EndType 端点类型(多边形、连接、平头、方形、圆形)

1.5.3 内部类型

类型 说明
InternalClipper 内部工具方法类
Vertex 顶点数据结构
LocalMinima 局部极小值结构
Active 活动边数据结构
OutRec 输出多边形记录
OutPt 输出点结构
ClipperBase 裁剪器基类

1.6 Vatti 算法简介

Clipper2 仍然基于 Bala Vatti 在 1992 年发表的多边形裁剪算法。该算法的核心思想是使用扫描线方法处理多边形:

1.6.1 扫描线方法

  1. 预处理阶段:将多边形的边按 Y 坐标排序,识别所有局部极小值点
  2. 扫描阶段:维护一个活动边表(Active Edge List, AEL),记录当前扫描线穿过的边
  3. 事件处理:在每个关键 Y 坐标处理边的插入、删除和交点

1.6.2 关键概念

局部极小值(Local Minima)

  • 多边形轮廓中 Y 坐标最低的点(在 Y 轴向下的坐标系中)
  • 算法从这些点开始向上扫描

活动边表(AEL)

  • 存储当前扫描线穿过的所有边
  • 按 X 坐标排序
  • 在 Clipper2 中由 Active 结构表示

顶点标志(VertexFlags)

[Flags]
internal enum VertexFlags
{
    None = 0,
    OpenStart = 1,   // 开放路径起点
    OpenEnd = 2,     // 开放路径终点
    LocalMax = 4,    // 局部最大值
    LocalMin = 8     // 局部最小值
}

1.6.3 算法流程示意

扫描方向 ↑
          
Y=5  ━━━━━━━━━━━ 扫描线 5 (局部最大值)
     ╱╲        ╱╲
Y=4 ╱  ╲━━━━━━╱  ╲ 扫描线 4
   ╱    ╲    ╱    ╲
Y=3╱      ╲╱       ╲ 扫描线 3 (交点处理)
   ╲      ╱╲      ╱
Y=2 ╲    ╱  ╲    ╱ 扫描线 2
     ╲  ╱    ╲  ╱
Y=1   ╲╱      ╲╱   扫描线 1 (局部极小值)

1.7 整数坐标的设计哲学

Clipper2 的核心裁剪引擎仍然使用整数坐标,这是一个关键的设计决策:

1.7.1 浮点数的问题

// 浮点数精度问题示例
double a = 0.1 + 0.2;  // 结果可能是 0.30000000000000004
bool equal = (0.1 + 0.2 == 0.3);  // 可能为 false

1.7.2 整数坐标的优势

  1. 精确比较:整数比较是精确的,不存在舍入误差
  2. 确定性结果:相同输入在任何平台上产生相同输出
  3. 数值稳定性:避免了浮点数累积误差

1.7.3 浮点支持的实现

Clipper2 通过 ClipperD 类提供浮点支持,其原理是:

public class ClipperD : ClipperBase
{
    private readonly double _scale;
    private readonly double _invScale;

    public ClipperD(int roundingDecimalPrecision = 2)
    {
        // 将精度转换为缩放因子
        _scale = Math.Pow(10, roundingDecimalPrecision);
        _invScale = 1 / _scale;
    }
}

浮点坐标在内部被缩放为整数处理,结果再缩放回浮点数。

1.8 坐标范围与精度

1.8.1 坐标范围常量

internal const long MaxInt64 = 9223372036854775807;
internal const long MaxCoord = MaxInt64 / 4;
internal const double max_coord = MaxCoord;
internal const double min_coord = -MaxCoord;

MaxCoord 约为 2.3×10¹⁸,这个限制是为了防止中间计算溢出。

1.8.2 精度检查

internal static void CheckPrecision(int precision)
{
    if (precision < -8 || precision > 8)
        throw new Exception("Error: Precision is out of range.");
}

浮点精度范围为 -8 到 8,表示小数点后的位数(或前的位数)。

1.9 快速使用示例

1.9.1 使用静态方法(推荐)

Clipper2 提供了便捷的静态方法:

using Clipper2Lib;

class Example
{
    static void Main()
    {
        // 创建主体多边形(正方形)
        Path64 subject = Clipper.MakePath(new long[] {
            0, 0, 100, 0, 100, 100, 0, 100
        });

        // 创建裁剪多边形
        Path64 clip = Clipper.MakePath(new long[] {
            50, 50, 150, 50, 150, 150, 50, 150
        });

        // 执行交集运算
        Paths64 solution = Clipper.Intersect(
            new Paths64 { subject },
            new Paths64 { clip },
            FillRule.NonZero
        );

        // 输出结果
        Console.WriteLine($"结果多边形数量: {solution.Count}");
        foreach (var path in solution)
        {
            Console.WriteLine($"多边形: {path}");
        }
    }
}

1.9.2 使用 Clipper64 类

using Clipper2Lib;

class Example
{
    static void Main()
    {
        Path64 subject = new Path64 {
            new Point64(0, 0),
            new Point64(100, 0),
            new Point64(100, 100),
            new Point64(0, 100)
        };

        Path64 clip = new Path64 {
            new Point64(50, 50),
            new Point64(150, 50),
            new Point64(150, 150),
            new Point64(50, 150)
        };

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

        Paths64 solution = new Paths64();
        clipper.Execute(ClipType.Intersection, FillRule.NonZero, solution);

        Console.WriteLine($"结果多边形数量: {solution.Count}");
    }
}

1.9.3 使用浮点坐标

using Clipper2Lib;

class Example
{
    static void Main()
    {
        PathD subject = Clipper.MakePath(new double[] {
            0.0, 0.0, 100.5, 0.0, 100.5, 100.5, 0.0, 100.5
        });

        PathD clip = Clipper.MakePath(new double[] {
            50.25, 50.25, 150.75, 50.25, 150.75, 150.75, 50.25, 150.75
        });

        // 使用 precision = 2 表示保留2位小数
        PathsD solution = Clipper.Intersect(
            new PathsD { subject },
            new PathsD { clip },
            FillRule.NonZero,
            precision: 2
        );

        Console.WriteLine($"结果: {solution}");
    }
}

1.10 四种布尔运算

Clipper2 支持四种布尔运算:

1.10.1 交集(Intersection)

返回两个多边形的公共区域:

Paths64 result = Clipper.Intersect(subject, clip, FillRule.NonZero);

1.10.2 并集(Union)

返回两个多边形的合并区域:

Paths64 result = Clipper.Union(subject, clip, FillRule.NonZero);
// 或单个多边形组的自并
Paths64 result = Clipper.Union(subject, FillRule.NonZero);

1.10.3 差集(Difference)

返回主体多边形减去裁剪多边形的区域:

Paths64 result = Clipper.Difference(subject, clip, FillRule.NonZero);

1.10.4 异或(Xor)

返回两个多边形的非公共区域:

Paths64 result = Clipper.Xor(subject, clip, FillRule.NonZero);

1.11 项目配置

1.11.1 NuGet 包

<PackageReference Include="Clipper2" Version="1.4.0" />

1.11.2 启用 Z 坐标

在项目文件中添加:

<PropertyGroup>
    <DefineConstants>USINGZ</DefineConstants>
</PropertyGroup>

1.11.3 .NET 版本要求

Clipper2 需要 .NET Standard 2.0 或更高版本,支持:

  • .NET Framework 4.6.1+
  • .NET Core 2.0+
  • .NET 5/6/7/8+

1.12 本章小结

本章介绍了 Clipper2 C# 源代码的整体结构和设计理念:

  1. 模块化设计:多个源文件分工明确
  2. 双精度支持:整数核心 + 浮点封装
  3. 现代 C# 特性:可空引用、内联优化
  4. 算法基础:仍基于经典的 Vatti 扫描线算法
  5. 简洁 API:静态方法 + 类实例两种风格

在后续章节中,我们将深入分析每个核心数据结构和算法实现的细节。


返回目录 | 下一章:核心数据结构