























在计算机图形学、地理信息系统(GIS)、计算机辅助设计(CAD)以及游戏开发等领域,多边形的几何运算是一个基础且至关重要的功能。无论是地图数据的处理、建筑设计中的布尔运算、游戏中的碰撞检测,还是工业制造中的刀具路径规划,都需要对多边形进行精确的裁剪、合并、求交、偏移等操作。
Clipper2 是由澳大利亚程序员 Angus Johnson 开发的 Clipper1 库的全面升级版本。它不仅继承了 Clipper1 的所有功能,还在算法效率、代码结构、API 设计等方面进行了大量改进。本教程将深入解读其 C# 源代码实现,帮助读者理解核心算法原理和代码设计。
| 特性 | Clipper1 | Clipper2 |
|---|---|---|
| 坐标类型 | 仅整数(Int64/Int32) | 整数 + 浮点双精度 |
| 命名空间 | ClipperLib | Clipper2Lib / Clipper2ZLib |
| 类型别名 | Path = List | Path64 类、PathD 类 |
| Z坐标支持 | 条件编译 | 条件编译(USINGZ) |
| 矩形裁剪 | 无专门优化 | RectClip64 高速算法 |
| 三角剖分 | 无 | 内置 Delaunay 三角剖分 |
| 对象池 | 无 | 内置对象池减少 GC |
| API 风格 | 较繁琐 | 更简洁的静态方法 |
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 # 扩展功能(三角剖分等)
每个源文件都以标准的版权声明开始:
/*******************************************************************************
* 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 *
*******************************************************************************/
从声明中可以看出:
Clipper2 使用预处理器指令控制编译选项:
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
#if USINGZ
namespace Clipper2ZLib
#else
namespace Clipper2Lib
#endif
#nullable enable
这是 C# 8.0 引入的可空引用类型特性。启用后:
Type?这有助于在编译期发现潜在的空引用异常,提高代码质量。
#if USINGZ
namespace Clipper2ZLib
#else
namespace Clipper2Lib
#endif
Clipper2Lib 命名空间,坐标只有 X、YClipper2ZLib 命名空间,坐标增加 Z 分量启用 Z 坐标后,点结构变为:
public struct Point64
{
public long X;
public long Y;
#if USINGZ
public long Z;
#endif
}
源代码大量使用 MethodImplOptions.AggressiveInlining:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPositive(Path64 poly)
{
return Area(poly) >= 0;
}
这告诉 JIT 编译器尽可能将方法内联,减少方法调用开销,对于频繁调用的小方法特别有效。
| 类型 | 说明 |
|---|---|
Point64 |
64位整数坐标点结构 |
PointD |
双精度浮点坐标点结构 |
Path64 |
整数路径类(继承 List<Point64>) |
PathD |
浮点路径类(继承 List<PointD>) |
Paths64 |
整数路径集合类(继承 List<Path64>) |
PathsD |
浮点路径集合类(继承 List<PathD>) |
Rect64 |
64位整数矩形结构 |
RectD |
双精度浮点矩形结构 |
Clipper64 |
整数坐标裁剪类 |
ClipperD |
浮点坐标裁剪类 |
ClipperOffset |
路径偏移类 |
PolyTree64 |
整数多边形树 |
PolyTreeD |
浮点多边形树 |
| 枚举 | 说明 |
|---|---|
ClipType |
裁剪操作类型(交、并、差、异或) |
PathType |
路径类型(主体、裁剪) |
FillRule |
填充规则(奇偶、非零、正向、负向) |
JoinType |
连接类型(斜接、方形、斜角、圆形) |
EndType |
端点类型(多边形、连接、平头、方形、圆形) |
| 类型 | 说明 |
|---|---|
InternalClipper |
内部工具方法类 |
Vertex |
顶点数据结构 |
LocalMinima |
局部极小值结构 |
Active |
活动边数据结构 |
OutRec |
输出多边形记录 |
OutPt |
输出点结构 |
ClipperBase |
裁剪器基类 |
Clipper2 仍然基于 Bala Vatti 在 1992 年发表的多边形裁剪算法。该算法的核心思想是使用扫描线方法处理多边形:
局部极小值(Local Minima):
活动边表(AEL):
Active 结构表示顶点标志(VertexFlags):
[Flags]
internal enum VertexFlags
{
None = 0,
OpenStart = 1, // 开放路径起点
OpenEnd = 2, // 开放路径终点
LocalMax = 4, // 局部最大值
LocalMin = 8 // 局部最小值
}
扫描方向 ↑
Y=5 ━━━━━━━━━━━ 扫描线 5 (局部最大值)
╱╲ ╱╲
Y=4 ╱ ╲━━━━━━╱ ╲ 扫描线 4
╱ ╲ ╱ ╲
Y=3╱ ╲╱ ╲ 扫描线 3 (交点处理)
╲ ╱╲ ╱
Y=2 ╲ ╱ ╲ ╱ 扫描线 2
╲ ╱ ╲ ╱
Y=1 ╲╱ ╲╱ 扫描线 1 (局部极小值)
Clipper2 的核心裁剪引擎仍然使用整数坐标,这是一个关键的设计决策:
// 浮点数精度问题示例
double a = 0.1 + 0.2; // 结果可能是 0.30000000000000004
bool equal = (0.1 + 0.2 == 0.3); // 可能为 false
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;
}
}
浮点坐标在内部被缩放为整数处理,结果再缩放回浮点数。
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¹⁸,这个限制是为了防止中间计算溢出。
internal static void CheckPrecision(int precision)
{
if (precision < -8 || precision > 8)
throw new Exception("Error: Precision is out of range.");
}
浮点精度范围为 -8 到 8,表示小数点后的位数(或前的位数)。
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}");
}
}
}
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}");
}
}
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}");
}
}
Clipper2 支持四种布尔运算:
返回两个多边形的公共区域:
Paths64 result = Clipper.Intersect(subject, clip, FillRule.NonZero);
返回两个多边形的合并区域:
Paths64 result = Clipper.Union(subject, clip, FillRule.NonZero);
// 或单个多边形组的自并
Paths64 result = Clipper.Union(subject, FillRule.NonZero);
返回主体多边形减去裁剪多边形的区域:
Paths64 result = Clipper.Difference(subject, clip, FillRule.NonZero);
返回两个多边形的非公共区域:
Paths64 result = Clipper.Xor(subject, clip, FillRule.NonZero);
<PackageReference Include="Clipper2" Version="1.4.0" />
在项目文件中添加:
<PropertyGroup>
<DefineConstants>USINGZ</DefineConstants>
</PropertyGroup>
Clipper2 需要 .NET Standard 2.0 或更高版本,支持:
本章介绍了 Clipper2 C# 源代码的整体结构和设计理念:
在后续章节中,我们将深入分析每个核心数据结构和算法实现的细节。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。