第3章_布尔运算操作
第三章 布尔运算操作(C#版)
3.1 引言
布尔运算是Clipper2最核心的功能之一,它允许我们对多边形进行交集、并集、差集和异或等操作。这些操作在计算机图形学、CAD设计、GIS分析等领域有着广泛的应用。本章将深入介绍Clipper2 C#版本中布尔运算的原理、使用方法和最佳实践。
3.2 布尔运算的基本概念
3.2.1 什么是布尔运算
布尔运算源自数学中的集合论,用于组合两个或多个几何形状。在二维几何中,布尔运算主要包括四种基本操作:
交集(Intersection)
交集运算返回两个多边形的重叠区域。只有同时属于两个输入多边形的点才会出现在结果中。
数学表示:A ∩ B
应用场景:
- 计算两个区域的公共部分
- 视野裁剪
- 碰撞检测的精确区域
并集(Union)
并集运算将两个多边形合并为一个,得到它们的完整覆盖区域。
数学表示:A ∪ B
应用场景:
- 合并相邻区域
- 创建复合形状
- 地块合并
差集(Difference)
差集运算从第一个多边形中减去第二个多边形的区域。
数学表示:A - B 或 A \ B
应用场景:
- 从形状中切除区域
- 创建孔洞
- 遮罩效果
异或(XOR)
异或运算返回两个多边形不重叠的区域,即只属于其中一个多边形的部分。
数学表示:A ⊕ B 或 (A ∪ B) - (A ∩ B)
应用场景:
- 检测变化区域
- 创建环形效果
- 对称差运算
3.2.2 主体与裁剪
在Clipper2中,参与布尔运算的多边形被分为两类:
主体(Subject):布尔运算的主要对象,差集运算中被减去的一方
裁剪(Clip):用于与主体进行运算的多边形,差集运算中作为"刀"的一方
using Clipper2Lib;
class SubjectClipExample
{
static void Main()
{
Paths64 subject = new Paths64(); // 主体多边形
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
Paths64 clip = new Paths64(); // 裁剪多边形
clip.Add(Clipper.MakePath(new long[] { 50, 50, 150, 50, 150, 150, 50, 150 }));
// 对于交集、并集、异或,subject和clip的顺序不影响结果
// 对于差集,subject - clip 和 clip - subject 的结果不同
}
}
3.2.3 填充规则的作用
填充规则决定了多边形的哪些区域被认为是"内部"。这对于处理自交多边形和复杂的嵌套结构至关重要。
Clipper2支持四种填充规则:
public enum FillRule
{
EvenOdd, // 偶奇规则
NonZero, // 非零规则
Positive, // 正数规则
Negative // 负数规则
}
详细的填充规则说明请参见第一章。
3.3 使用简化API进行布尔运算
Clipper2提供了一组简化的静态函数,可以用一行代码完成布尔运算。
3.3.1 交集运算
using System;
using Clipper2Lib;
class IntersectionExample
{
static void Main()
{
// 创建两个相交的正方形
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 50, 50, 150, 50, 150, 150, 50, 150 }));
// 执行交集运算
Paths64 result = Clipper.Intersect(subject, clip, FillRule.NonZero);
// result 包含一个50x50的正方形:(50,50) → (100,50) → (100,100) → (50,100)
Console.WriteLine($"交集结果包含 {result.Count} 个多边形");
foreach (Path64 path in result)
{
Console.Write("顶点: ");
foreach (Point64 pt in path)
{
Console.Write($"({pt.X},{pt.Y}) ");
}
Console.WriteLine();
}
}
}
3.3.2 并集运算
using Clipper2Lib;
class UnionExample
{
static void Main()
{
// 合并两个正方形
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 50, 50, 150, 50, 150, 150, 50, 150 }));
Paths64 result = Clipper.Union(subject, clip, FillRule.NonZero);
// result 包含一个L形多边形,覆盖两个正方形的区域
Console.WriteLine($"并集结果包含 {result.Count} 个多边形");
}
}
**单一集合的并集**
对于只有主体多边形(没有裁剪多边形)的情况,Union函数可以用于:
- 合并重叠的多边形
- 简化自交多边形
- 分解复杂多边形为简单多边形
```csharp
using Clipper2Lib;
class UnionSingleSetExample
{
static void Main()
{
// 合并多个重叠的多边形
Paths64 circles = new Paths64();
circles.Add(Clipper.MakePath(new long[] { 0, 0, 50, 0, 50, 50, 0, 50 }));
circles.Add(Clipper.MakePath(new long[] { 40, 0, 90, 0, 90, 50, 40, 50 }));
circles.Add(Clipper.MakePath(new long[] { 80, 0, 130, 0, 130, 50, 80, 50 }));
// 使用单参数版本的Union
Paths64 merged = Clipper.Union(circles, FillRule.NonZero);
Console.WriteLine($"合并后结果包含 {merged.Count} 个多边形");
}
}
3.3.3 差集运算
using Clipper2Lib;
class DifferenceExample
{
static void Main()
{
// 从大正方形中减去小正方形
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 25, 25, 75, 25, 75, 75, 25, 75 }));
Paths64 result = Clipper.Difference(subject, clip, FillRule.NonZero);
// result 包含一个带有中心孔洞的正方形
Console.WriteLine($"差集结果包含 {result.Count} 个路径");
// 注意:差集运算的顺序很重要
// subject - clip ≠ clip - subject
Paths64 result2 = Clipper.Difference(clip, subject, FillRule.NonZero);
// result2 是空的,因为clip完全在subject内部
Console.WriteLine($"反向差集结果包含 {result2.Count} 个路径");
}
}
3.3.4 异或运算
using Clipper2Lib;
class XorExample
{
static void Main()
{
// 两个部分重叠的正方形进行异或运算
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 50, 50, 150, 50, 150, 150, 50, 150 }));
Paths64 result = Clipper.Xor(subject, clip, FillRule.NonZero);
// result 包含两个L形区域
Console.WriteLine($"异或结果包含 {result.Count} 个多边形");
}
}
3.3.5 使用浮点数坐标
对于浮点数坐标,可以使用PathsD类型:
using Clipper2Lib;
class FloatCoordinatesExample
{
static void Main()
{
// 使用浮点数坐标
PathsD subject = new PathsD();
subject.Add(Clipper.MakePath(new double[] { 0.0, 0.0, 10.5, 0.0, 10.5, 10.5, 0.0, 10.5 }));
PathsD clip = new PathsD();
clip.Add(Clipper.MakePath(new double[] { 5.25, 5.25, 15.75, 5.25, 15.75, 15.75, 5.25, 15.75 }));
// 第四个参数指定精度(小数位数)
PathsD result = Clipper.Intersect(subject, clip, FillRule.NonZero, 2);
Console.WriteLine($"结果包含 {result.Count} 个多边形");
}
}
3.4 使用Clipper类进行高级控制
对于需要更多控制的场景,可以使用Clipper64或ClipperD类。
3.4.1 Clipper64类
using System;
using Clipper2Lib;
class Clipper64Example
{
static void Main()
{
Clipper64 clipper = new Clipper64();
// 添加主体多边形
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
clipper.AddSubject(subject);
// 添加裁剪多边形
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 50, 50, 150, 50, 150, 150, 50, 150 }));
clipper.AddClip(clip);
// 执行布尔运算
Paths64 result = new Paths64();
clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
Console.WriteLine($"结果包含 {result.Count} 个多边形");
}
}
3.4.2 ClipType枚举
public enum ClipType
{
None,
Intersection,
Union,
Difference,
Xor
}
3.4.3 添加开放路径
除了闭合的多边形,Clipper2还支持开放的折线:
using Clipper2Lib;
class OpenPathExample
{
static void Main()
{
Clipper64 clipper = new Clipper64();
// 添加闭合的主体多边形
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
clipper.AddSubject(subject);
// 添加开放的主体折线
Paths64 subjectOpen = new Paths64();
subjectOpen.Add(Clipper.MakePath(new long[] { 0, 50, 150, 50 })); // 一条水平线
clipper.AddOpenSubject(subjectOpen);
// 添加裁剪多边形
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 25, 25, 75, 25, 75, 75, 25, 75 }));
clipper.AddClip(clip);
// 执行运算并分别获取闭合和开放结果
Paths64 closedResult = new Paths64();
Paths64 openResult = new Paths64();
clipper.Execute(ClipType.Difference, FillRule.NonZero, closedResult, openResult);
// closedResult 包含裁剪后的多边形
// openResult 包含裁剪后的折线
Console.WriteLine($"闭合路径结果: {closedResult.Count} 个");
Console.WriteLine($"开放路径结果: {openResult.Count} 个");
}
}
3.4.4 使用PolyTree输出
当需要保留多边形的层次结构时,使用PolyTree输出:
using Clipper2Lib;
class PolyTreeOutputExample
{
static void Main()
{
Clipper64 clipper = new Clipper64();
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
clipper.AddSubject(subject);
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 25, 25, 75, 25, 75, 75, 25, 75 }));
clipper.AddClip(clip);
// 使用PolyTree输出
PolyTree64 tree = new PolyTree64();
Paths64 openPaths = new Paths64();
clipper.Execute(ClipType.Intersection, FillRule.NonZero, tree, openPaths);
// 遍历PolyTree获取层次结构
for (int i = 0; i < tree.Count; i++)
{
PolyPath64 child = tree[i];
if (!child.IsHole)
{
// 这是一个外边界多边形
Path64 exterior = child.Polygon;
Console.WriteLine($"外边界多边形顶点数: {exterior.Count}");
// 获取其孔洞
for (int j = 0; j < child.Count; j++)
{
Path64 holePath = child[j].Polygon;
Console.WriteLine($" 孔洞顶点数: {holePath.Count}");
}
}
}
}
}
3.4.5 复用Clipper对象
在循环中复用Clipper对象可以提高性能:
using Clipper2Lib;
class ReuseClipperExample
{
static void Main()
{
Clipper64 clipper = new Clipper64();
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 50, 50, 150, 50, 150, 150, 50, 150 }));
for (int i = 0; i < 1000; i++)
{
// 清空之前的数据,但保留已分配的内存
clipper.Clear();
// 添加新的多边形
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { i, 0, i + 100, 0, i + 100, 100, i, 100 }));
clipper.AddSubject(subject);
clipper.AddClip(clip);
// 执行运算
Paths64 result = new Paths64();
clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
// 处理结果...
}
}
}
3.5 处理复杂多边形
3.5.1 带孔洞的多边形
using Clipper2Lib;
class PolygonWithHoleExample
{
static void Main()
{
// 创建一个带孔洞的多边形
Paths64 subject = new Paths64();
// 外边界(逆时针方向)
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
// 孔洞(顺时针方向)
subject.Add(Clipper.MakePath(new long[] { 25, 25, 25, 75, 75, 75, 75, 25 }));
// 裁剪多边形
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 50, 0, 150, 0, 150, 100, 50, 100 }));
// 执行交集运算
Paths64 result = Clipper.Intersect(subject, clip, FillRule.NonZero);
// 结果会正确处理孔洞
Console.WriteLine($"结果包含 {result.Count} 个路径");
}
}
3.5.2 自交多边形
自交多边形是指边界与自身相交的多边形。不同的填充规则会产生不同的结果:
using Clipper2Lib;
class SelfIntersectingExample
{
static void Main()
{
// 创建一个8字形的自交多边形
Paths64 figure8 = new Paths64();
figure8.Add(Clipper.MakePath(new long[] { 0, 0, 100, 100, 100, 0, 0, 100 }));
// 使用NonZero规则 - 两个半部分都被填充
Paths64 resultNonZero = Clipper.Union(figure8, FillRule.NonZero);
Console.WriteLine($"NonZero规则结果: {resultNonZero.Count} 个多边形");
// 使用EvenOdd规则 - 中间交叉点处会有孔洞
Paths64 resultEvenOdd = Clipper.Union(figure8, FillRule.EvenOdd);
Console.WriteLine($"EvenOdd规则结果: {resultEvenOdd.Count} 个多边形");
}
}
3.5.3 多个分离的多边形
using Clipper2Lib;
class MultiplePolygonsExample
{
static void Main()
{
// 主体包含多个分离的多边形
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 50, 0, 50, 50, 0, 50 }));
subject.Add(Clipper.MakePath(new long[] { 100, 0, 150, 0, 150, 50, 100, 50 }));
subject.Add(Clipper.MakePath(new long[] { 200, 0, 250, 0, 250, 50, 200, 50 }));
// 裁剪多边形跨越多个主体
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 25, -25, 225, -25, 225, 75, 25, 75 }));
// 交集运算会正确处理每个主体
Paths64 result = Clipper.Intersect(subject, clip, FillRule.NonZero);
// result 包含3个多边形
Console.WriteLine($"结果包含 {result.Count} 个多边形");
}
}
3.5.4 重叠的边
当两个多边形有共享的边时,Clipper2能够正确处理:
using Clipper2Lib;
class OverlappingEdgesExample
{
static void Main()
{
// 两个共享一条边的正方形
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 100, 0, 200, 0, 200, 100, 100, 100 }));
// clip的左边与subject的右边重合
// 并集运算会正确合并它们
Paths64 result = Clipper.Union(subject, clip, FillRule.NonZero);
// result 是一个200x100的矩形
Console.WriteLine($"结果包含 {result.Count} 个多边形");
}
}
3.6 布尔运算的性能优化
3.6.1 减少顶点数量
顶点数量是影响性能的主要因素。可以使用路径简化来减少顶点:
using Clipper2Lib;
class SimplifyExample
{
static void Main()
{
Paths64 complexPaths = new Paths64();
// ... 添加复杂路径
Paths64 clip = new Paths64();
clip.Add(Clipper.MakePath(new long[] { 50, 50, 150, 50, 150, 150, 50, 150 }));
// 在布尔运算前简化路径
PathsD complexPathsD = Clipper.Paths64ToPathsD(complexPaths);
PathsD simplified = Clipper.SimplifyPaths(complexPathsD, 2.0);
Paths64 simplifiedPaths = Clipper.PathsDToPaths64(simplified);
Paths64 result = Clipper.Intersect(simplifiedPaths, clip, FillRule.NonZero);
}
}
3.6.2 使用矩形裁剪
当裁剪区域是矩形时,使用专门的RectClip函数:
using Clipper2Lib;
class RectClipPerformanceExample
{
static void Main()
{
Paths64 subject = new Paths64();
subject.Add(Clipper.MakePath(new long[] { 0, 0, 200, 0, 200, 200, 0, 200 }));
// 常规布尔运算裁剪
Paths64 clipPolygon = new Paths64();
clipPolygon.Add(Clipper.MakePath(new long[] { 50, 50, 150, 50, 150, 150, 50, 150 }));
Paths64 result1 = Clipper.Intersect(subject, clipPolygon, FillRule.NonZero);
// 使用优化的矩形裁剪
Rect64 clipRect = new Rect64(50, 50, 150, 150);
Paths64 result2 = Clipper.RectClip(clipRect, subject);
// result2 与 result1 相同,但速度更快
}
}
3.6.3 批量处理
对于大量相同裁剪区域的操作,可以复用Clipper对象:
using Clipper2Lib;
class BatchProcessingExample
{
static void ProcessBatch(List<Paths64> subjects, Paths64 clip)
{
Clipper64 clipper = new Clipper64();
foreach (Paths64 subject in subjects)
{
clipper.Clear();
clipper.AddClip(clip);
clipper.AddSubject(subject);
Paths64 result = new Paths64();
clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
// 处理结果...
}
}
}
3.6.4 多线程处理
Clipper2的Clipper对象不是线程安全的,但可以在不同线程中使用不同的Clipper实例:
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Clipper2Lib;
class MultiThreadExample
{
static void ProcessInParallel(List<Paths64> allSubjects, Paths64 clip)
{
int processorCount = Environment.ProcessorCount;
Parallel.ForEach(allSubjects, subject =>
{
// 每个任务有自己的Clipper实例
Clipper64 clipper = new Clipper64();
clipper.AddSubject(subject);
clipper.AddClip(clip);
Paths64 result = new Paths64();
clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
// 处理结果...
});
}
}
3.7 布尔运算的常见问题
3.7.1 结果为空
问题:执行布尔运算后结果为空。
可能的原因:
- 两个多边形不相交(对于交集运算)
- 多边形的方向不正确
- 坐标精度问题
解决方法:
using Clipper2Lib;
class EmptyResultTroubleshooting
{
static void DiagnoseEmptyResult(Paths64 subject, Paths64 clip)
{
// 检查多边形是否有效
bool valid = subject.Count > 0 && clip.Count > 0;
Console.WriteLine($"多边形有效: {valid}");
// 检查边界框是否相交
Rect64 subjectBounds = Clipper.GetBounds(subject);
Rect64 clipBounds = Clipper.GetBounds(clip);
bool mayIntersect = !(subjectBounds.right < clipBounds.left ||
clipBounds.right < subjectBounds.left ||
subjectBounds.bottom < clipBounds.top ||
clipBounds.bottom < subjectBounds.top);
Console.WriteLine($"边界框可能相交: {mayIntersect}");
// 确保多边形方向正确
for (int i = 0; i < subject.Count; i++)
{
bool isPositive = Clipper.IsPositive(subject[i]);
Console.WriteLine($"Subject[{i}] 是正方向: {isPositive}");
}
}
}
3.7.2 意外的孔洞
问题:并集运算后出现意外的孔洞。
原因:填充规则与多边形方向不匹配。
解决方法:
using Clipper2Lib;
class UnexpectedHolesSolution
{
static Paths64 UnionWithCorrectOrientation(Paths64 paths)
{
// 使用EvenOdd规则时,方向不重要
Paths64 result1 = Clipper.Union(paths, FillRule.EvenOdd);
// 或者使用NonZero规则并确保方向一致
Paths64 correctedPaths = new Paths64();
foreach (Path64 path in paths)
{
if (!Clipper.IsPositive(path))
{
correctedPaths.Add(Clipper.ReversePath(path));
}
else
{
correctedPaths.Add(path);
}
}
Paths64 result2 = Clipper.Union(correctedPaths, FillRule.NonZero);
return result2;
}
}
3.7.3 自交多边形的处理
问题:自交多边形产生意外的结果。
解决方法:
using Clipper2Lib;
class SelfIntersectionHandling
{
static Paths64 SimplifySelfIntersecting(Paths64 selfIntersecting)
{
// 使用Union简化自交多边形
Paths64 simplified = Clipper.Union(selfIntersecting, FillRule.NonZero);
return simplified;
}
3.7.4 精度损失
问题:浮点数转换导致精度损失。
解决方法:
using Clipper2Lib;
class PrecisionHandling
{
static void HandlePrecision()
{
// 使用足够大的缩放因子
double scale = 1000000.0; // 6位小数精度
PathsD subjectD = new PathsD();
subjectD.Add(Clipper.MakePath(new double[] { 0.0, 0.0, 10.123456, 0.0, 10.123456, 10.123456, 0.0, 10.123456 }));
PathsD clipD = new PathsD();
clipD.Add(Clipper.MakePath(new double[] { 5.0, 5.0, 15.0, 5.0, 15.0, 15.0, 5.0, 15.0 }));
// 手动缩放
Paths64 scaledSubject = Clipper.ScalePaths64(subjectD, scale);
Paths64 scaledClip = Clipper.ScalePaths64(clipD, scale);
Paths64 result = Clipper.Intersect(scaledSubject, scaledClip, FillRule.NonZero);
// 缩放回原始尺寸
PathsD resultD = Clipper.ScalePathsD(result, 1.0 / scale);
}
}
3.8 实际应用示例
3.8.1 创建复合形状
using Clipper2Lib;
class CompositeShapeExample
{
static Path64 MakeCircle(Point64 center, int radius, int segments = 32)
{
Path64 circle = new Path64();
for (int i = 0; i < segments; i++)
{
double angle = 2 * Math.PI * i / segments;
circle.Add(new Point64(
center.X + (long)(radius * Math.Cos(angle)),
center.Y + (long)(radius * Math.Sin(angle))
));
}
return circle;
}
static void Main()
{
// 创建圆形
Paths64 circle = new Paths64();
circle.Add(MakeCircle(new Point64(100, 100), 80));
// 创建内部小圆
Paths64 innerCircle = new Paths64();
innerCircle.Add(MakeCircle(new Point64(100, 100), 40));
Paths64 result = Clipper.Difference(circle, innerCircle, FillRule.NonZero);
// result 是一个圆环
Console.WriteLine($"圆环包含 {result.Count} 个路径");
}
}
3.8.2 区域合并
using Clipper2Lib;
class RegionMergeExample
{
static void Main()
{
// 合并多个重叠的区域
Paths64 regions = new Paths64();
regions.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
regions.Add(Clipper.MakePath(new long[] { 80, 0, 180, 0, 180, 100, 80, 100 }));
regions.Add(Clipper.MakePath(new long[] { 160, 0, 260, 0, 260, 100, 160, 100 }));
Paths64 merged = Clipper.Union(regions, FillRule.NonZero);
// merged 是一个连续的区域
Console.WriteLine($"合并后: {merged.Count} 个多边形");
}
}
3.8.3 遮罩裁剪
using Clipper2Lib;
class MaskClipExample
{
static void Main()
{
// 使用遮罩裁剪图像区域
Paths64 image = new Paths64();
image.Add(Clipper.MakePath(new long[] { 0, 0, 800, 0, 800, 600, 0, 600 }));
Paths64 mask = new Paths64();
mask.Add(Clipper.MakePath(new long[] { 100, 100, 700, 100, 700, 500, 100, 500 }));
// 添加一个孔洞
mask.Add(Clipper.MakePath(new long[] { 200, 200, 200, 400, 600, 400, 600, 200 }));
Paths64 result = Clipper.Intersect(image, mask, FillRule.NonZero);
// result 是带有中心孔洞的遮罩区域
Console.WriteLine($"遮罩裁剪结果: {result.Count} 个路径");
}
}
3.8.4 地块拆分
using Clipper2Lib;
class ParcelSplitExample
{
static void Main()
{
// 用一条线将地块拆分为两部分
Paths64 parcel = new Paths64();
parcel.Add(Clipper.MakePath(new long[] { 0, 0, 100, 0, 100, 100, 0, 100 }));
// 创建一个非常薄的矩形作为分割线
Paths64 splitLine = new Paths64();
splitLine.Add(Clipper.MakePath(new long[] { 50, -10, 51, -10, 51, 110, 50, 110 }));
// 左半部分裁剪区域
Paths64 leftClip = new Paths64();
leftClip.Add(Clipper.MakePath(new long[] { -10, -10, 50, -10, 50, 110, -10, 110 }));
// 右半部分裁剪区域
Paths64 rightClip = new Paths64();
rightClip.Add(Clipper.MakePath(new long[] { 51, -10, 110, -10, 110, 110, 51, 110 }));
Paths64 left = Clipper.Intersect(parcel, leftClip, FillRule.NonZero);
Paths64 right = Clipper.Intersect(parcel, rightClip, FillRule.NonZero);
Console.WriteLine($"左半部分: {left.Count} 个多边形");
Console.WriteLine($"右半部分: {right.Count} 个多边形");
}
}
3.8.5 碰撞区域计算
using Clipper2Lib;
class CollisionDetectionExample
{
static Paths64 GetObjectBounds(Point64 position, int size)
{
Paths64 bounds = new Paths64();
bounds.Add(Clipper.MakePath(new long[] {
position.X, position.Y,
position.X + size, position.Y,
position.X + size, position.Y + size,
position.X, position.Y + size
}));
return bounds;
}
static void Main()
{
// 计算两个移动物体的碰撞区域
Paths64 objectA = GetObjectBounds(new Point64(0, 0), 100);
Paths64 objectB = GetObjectBounds(new Point64(50, 50), 100);
Paths64 collision = Clipper.Intersect(objectA, objectB, FillRule.NonZero);
if (collision.Count > 0)
{
// 发生碰撞
double collisionArea = Clipper.Area(collision);
Console.WriteLine($"碰撞面积: {collisionArea}");
// 根据碰撞面积决定反应
}
else
{
Console.WriteLine("未发生碰撞");
}
}
}
3.9 与其他库的比较
3.9.1 与NetTopologySuite的比较
| 特性 | Clipper2 | NetTopologySuite |
|---|---|---|
| 精度 | 整数运算,精确 | 浮点运算,可能有误差 |
| 性能 | 非常快 | 较快 |
| 鲁棒性 | 极佳 | 良好 |
| 拓扑处理 | 需要后处理 | 内置 |
| 曲线支持 | 无 | 有 |
| .NET集成 | NuGet包 | NuGet包 |
3.9.2 与其他C#几何库的比较
| 特性 | Clipper2 | 其他库 |
|---|---|---|
| 学习曲线 | 简单 | 各异 |
| 依赖 | 无 | 各异 |
| 精度 | 64位整数 | 各异 |
| 功能范围 | 2D多边形裁剪 | 综合几何功能 |
| 许可证 | Boost | 各异 |
3.10 算法原理简介
Clipper2使用改进的Vatti算法进行布尔运算。这里简要介绍其工作原理。
3.10.1 扫描线算法
Vatti算法是一种扫描线算法。它通过一条水平扫描线从下向上扫过所有的多边形,在扫描过程中维护当前扫描线与多边形边的交点。
y
↑
| ┌────────┐
| │ │
扫描线 ───┼─────┼────────┼───→
| │ ∩ │
| ┌──┴────────┴──┐
| │ │
└──┴──────────────┴────→ x
3.10.2 事件处理
算法在以下事件点进行处理:
- 顶点事件:扫描线遇到多边形顶点
- 交点事件:两条边相交
3.10.3 活动边列表
算法维护一个"活动边列表"(Active Edge List),包含当前与扫描线相交的所有边。随着扫描线移动,边被添加或移除。
// 伪代码示意
while (eventQueue.Count > 0)
{
Event evt = eventQueue.Dequeue();
if (evt.Type == EventType.Vertex)
{
// 处理顶点事件
UpdateActiveEdges(evt.Vertex);
}
else if (evt.Type == EventType.Intersection)
{
// 处理交点事件
SwapEdges(evt.Edge1, evt.Edge2);
}
// 根据填充规则和裁剪类型决定输出
DetermineOutput(activeEdges, clipType, fillRule);
}
3.11 本章小结
本章我们深入学习了Clipper2 C#版本的布尔运算功能:
- 基本概念:交集、并集、差集、异或四种运算类型
- 简化API:Clipper.Intersect、Clipper.Union、Clipper.Difference、Clipper.Xor静态方法
- Clipper类:Clipper64和ClipperD提供更多控制选项
- 复杂多边形处理:孔洞、自交、重叠边的处理
- 性能优化:路径简化、矩形裁剪、批量处理、多线程
- 常见问题:结果为空、意外孔洞、自交处理、精度损失
- 实际应用:复合形状、区域合并、遮罩裁剪、碰撞检测等
在下一章中,我们将学习Clipper2的另一个核心功能——多边形偏移操作。

浙公网安备 33010602011771号