第3章_布尔运算操作
第三章 布尔运算操作
3.1 引言
布尔运算是 Clipper1 最核心、最重要的功能。在计算机图形学和几何处理中,布尔运算指的是对两个或多个几何图形进行集合运算,包括交集(Intersection)、并集(Union)、差集(Difference)和异或(XOR)。这些运算在 CAD 设计、GIS 分析、游戏开发、制造业等众多领域都有广泛应用。
Clipper1 实现的布尔运算算法基于改进的 Vatti 算法,具有极高的鲁棒性和可靠性。它能够正确处理各种复杂情况,包括自相交多边形、重叠边、退化情况等。本章将深入讲解如何使用 Clipper1 执行各种布尔运算,并通过大量实例帮助您掌握其应用技巧。
3.2 布尔运算基础
3.2.1 四种基本运算
Clipper1 支持四种基本的布尔运算,通过 ClipType 枚举来指定:
交集(Intersection)- ctIntersection
- 计算两个或多个多边形的重叠区域
- 结果是所有输入多边形共同覆盖的部分
- 应用场景:求两个区域的公共部分、冲突检测等
并集(Union)- ctUnion
- 合并两个或多个多边形
- 结果是所有输入多边形覆盖的总区域
- 应用场景:区域合并、缓冲区合并、简化重叠区域等
差集(Difference)- ctDifference
- 从主题多边形中减去裁剪多边形
- 结果是主题多边形中不与裁剪多边形重叠的部分
- 应用场景:挖孔操作、排除区域、布尔减法等
异或(XOR)- ctXor
- 计算两个多边形不重叠的部分
- 结果是属于其中一个多边形但不同时属于两者的区域
- 应用场景:查找差异区域、对称差集等
3.2.2 Clipper 类的基本使用流程
使用 Clipper 类执行布尔运算的标准流程如下:
using ClipperLib;
using Path = System.Collections.Generic.List<ClipperLib.IntPoint>;
using Paths = System.Collections.Generic.List<System.Collections.Generic.List<ClipperLib.IntPoint>>;
// 1. 创建 Clipper 对象
Clipper clipper = new Clipper();
// 2. 添加主题多边形(Subject)
clipper.AddPath(subjectPath, PolyType.ptSubject, true);
// 或批量添加
clipper.AddPaths(subjectPaths, PolyType.ptSubject, true);
// 3. 添加裁剪多边形(Clip)
clipper.AddPath(clipPath, PolyType.ptClip, true);
// 或批量添加
clipper.AddPaths(clipPaths, PolyType.ptClip, true);
// 4. 执行运算
Paths solution = new Paths();
bool success = clipper.Execute(
ClipType.ctIntersection, // 运算类型
solution, // 输出结果
PolyFillType.pftNonZero, // Subject 填充规则
PolyFillType.pftNonZero // Clip 填充规则
);
// 5. 处理结果
if (success)
{
foreach (var path in solution)
{
// 处理每个结果多边形
}
}
3.2.3 AddPath 和 AddPaths 方法
AddPath 方法签名:
public bool AddPath(Path path, PolyType polyType, bool closed)
参数说明:
path:要添加的路径(点的列表)polyType:路径类型(ptSubject 或 ptClip)closed:是否为封闭路径(true 表示多边形,false 表示开放线段)
AddPaths 方法签名:
public bool AddPaths(Paths paths, PolyType polyType, bool closed)
批量添加多个路径,参数含义相同。
返回值:
- 返回
true表示成功添加 - 返回
false表示路径无效(如顶点数少于 2,或封闭路径顶点数少于 3)
3.2.4 Execute 方法
基本签名(输出为 Paths):
public bool Execute(
ClipType clipType,
Paths solution,
PolyFillType subjFillType = PolyFillType.pftEvenOdd,
PolyFillType clipFillType = PolyFillType.pftEvenOdd
)
层次结构签名(输出为 PolyTree):
public bool Execute(
ClipType clipType,
PolyTree polytree,
PolyFillType subjFillType = PolyFillType.pftEvenOdd,
PolyFillType clipFillType = PolyFillType.pftEvenOdd
)
参数说明:
clipType:布尔运算类型solution/polytree:输出结果的容器subjFillType:主题多边形的填充规则clipFillType:裁剪多边形的填充规则
返回值:
true:运算成功false:运算失败(通常不会发生)
3.3 交集运算(Intersection)
3.3.1 基本概念
交集运算计算两个或多个多边形的共同覆盖区域。结果只包含同时被所有输入多边形覆盖的部分。
3.3.2 简单交集示例
using System;
using System.Collections.Generic;
using ClipperLib;
using Path = System.Collections.Generic.List<ClipperLib.IntPoint>;
using Paths = System.Collections.Generic.List<System.Collections.Generic.List<ClipperLib.IntPoint>>;
class IntersectionExample
{
static void BasicIntersection()
{
const double SCALE = 1000.0;
// 创建第一个正方形 (0,0) 到 (100,100)
Path square1 = new Path
{
new IntPoint(0, 0),
new IntPoint((long)(100 * SCALE), 0),
new IntPoint((long)(100 * SCALE), (long)(100 * SCALE)),
new IntPoint(0, (long)(100 * SCALE))
};
// 创建第二个正方形 (50,50) 到 (150,150)
Path square2 = new Path
{
new IntPoint((long)(50 * SCALE), (long)(50 * SCALE)),
new IntPoint((long)(150 * SCALE), (long)(50 * SCALE)),
new IntPoint((long)(150 * SCALE), (long)(150 * SCALE)),
new IntPoint((long)(50 * SCALE), (long)(150 * SCALE))
};
// 执行交集运算
Clipper clipper = new Clipper();
clipper.AddPath(square1, PolyType.ptSubject, true);
clipper.AddPath(square2, PolyType.ptClip, true);
Paths solution = new Paths();
bool success = clipper.Execute(
ClipType.ctIntersection,
solution,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
if (success && solution.Count > 0)
{
Console.WriteLine($"交集产生 {solution.Count} 个多边形");
foreach (var path in solution)
{
double area = Math.Abs(Clipper.Area(path)) / (SCALE * SCALE);
Console.WriteLine($"多边形面积: {area}");
Console.WriteLine("顶点坐标:");
foreach (var pt in path)
{
Console.WriteLine($" ({pt.X / SCALE}, {pt.Y / SCALE})");
}
}
}
else
{
Console.WriteLine("两个多边形没有交集");
}
}
}
预期输出:
交集产生 1 个多边形
多边形面积: 2500
顶点坐标:
(50, 50)
(100, 50)
(100, 100)
(50, 100)
3.3.3 多个多边形的交集
Clipper1 可以同时计算多个多边形的交集:
static void MultipleIntersection()
{
const double SCALE = 1000.0;
// 创建三个圆形(用多边形近似)
Path circle1 = CreateCircle(50, 50, 40, 32, SCALE);
Path circle2 = CreateCircle(80, 50, 40, 32, SCALE);
Path circle3 = CreateCircle(65, 75, 40, 32, SCALE);
// 将所有圆作为主题多边形添加
Clipper clipper = new Clipper();
clipper.AddPath(circle1, PolyType.ptSubject, true);
clipper.AddPath(circle2, PolyType.ptSubject, true);
clipper.AddPath(circle3, PolyType.ptSubject, true);
// 执行交集运算
Paths solution = new Paths();
clipper.Execute(
ClipType.ctIntersection,
solution,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
Console.WriteLine($"三个圆的公共交集产生 {solution.Count} 个区域");
if (solution.Count > 0)
{
double totalArea = 0;
foreach (var path in solution)
{
double area = Math.Abs(Clipper.Area(path)) / (SCALE * SCALE);
totalArea += area;
}
Console.WriteLine($"总交集面积: {totalArea:F2}");
}
}
static Path CreateCircle(double cx, double cy, double radius, int segments, double scale)
{
Path circle = new Path();
for (int i = 0; i < segments; i++)
{
double angle = 2 * Math.PI * i / segments;
double x = cx + radius * Math.Cos(angle);
double y = cy + radius * Math.Sin(angle);
circle.Add(new IntPoint((long)(x * scale), (long)(y * scale)));
}
return circle;
}
3.3.4 交集的实际应用
应用1:碰撞检测
class CollisionDetector
{
private const double SCALE = 1000.0;
/// <summary>
/// 检测两个游戏对象是否碰撞
/// </summary>
public static bool CheckCollision(Path shape1, Path shape2)
{
Clipper clipper = new Clipper();
clipper.AddPath(shape1, PolyType.ptSubject, true);
clipper.AddPath(shape2, PolyType.ptClip, true);
Paths intersection = new Paths();
clipper.Execute(ClipType.ctIntersection, intersection);
// 如果有交集,说明碰撞了
return intersection.Count > 0;
}
/// <summary>
/// 计算碰撞区域的面积
/// </summary>
public static double GetCollisionArea(Path shape1, Path shape2)
{
Clipper clipper = new Clipper();
clipper.AddPath(shape1, PolyType.ptSubject, true);
clipper.AddPath(shape2, PolyType.ptClip, true);
Paths intersection = new Paths();
clipper.Execute(ClipType.ctIntersection, intersection);
double totalArea = 0;
foreach (var path in intersection)
{
totalArea += Math.Abs(Clipper.Area(path));
}
return totalArea / (SCALE * SCALE);
}
}
应用2:地理信息系统(GIS)中的空间查询
class GISOperations
{
private const double SCALE = 1000000.0; // 更高精度用于地理坐标
/// <summary>
/// 查找在指定区域内的所有地块
/// </summary>
public static List<(int id, Path intersection, double area)> FindParcelsInRegion(
Path queryRegion,
Dictionary<int, Path> parcels)
{
var results = new List<(int id, Path intersection, double area)>();
foreach (var parcel in parcels)
{
Clipper clipper = new Clipper();
clipper.AddPath(queryRegion, PolyType.ptSubject, true);
clipper.AddPath(parcel.Value, PolyType.ptClip, true);
Paths intersection = new Paths();
clipper.Execute(ClipType.ctIntersection, intersection);
if (intersection.Count > 0)
{
double area = 0;
foreach (var path in intersection)
{
area += Math.Abs(Clipper.Area(path));
}
area /= (SCALE * SCALE);
results.Add((parcel.Key, intersection[0], area));
}
}
return results;
}
}
3.4 并集运算(Union)
3.4.1 基本概念
并集运算合并所有输入多边形,生成一个覆盖所有输入区域的结果。重叠部分会自动合并,内部边界会被消除。
3.4.2 基本并集示例
static void BasicUnion()
{
const double SCALE = 1000.0;
// 创建两个相邻的正方形
Path square1 = new Path
{
new IntPoint(0, 0),
new IntPoint((long)(50 * SCALE), 0),
new IntPoint((long)(50 * SCALE), (long)(50 * SCALE)),
new IntPoint(0, (long)(50 * SCALE))
};
Path square2 = new Path
{
new IntPoint((long)(40 * SCALE), 0),
new IntPoint((long)(90 * SCALE), 0),
new IntPoint((long)(90 * SCALE), (long)(50 * SCALE)),
new IntPoint((long)(40 * SCALE), (long)(50 * SCALE))
};
// 执行并集运算
Clipper clipper = new Clipper();
clipper.AddPath(square1, PolyType.ptSubject, true);
clipper.AddPath(square2, PolyType.ptClip, true);
Paths solution = new Paths();
clipper.Execute(
ClipType.ctUnion,
solution,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
Console.WriteLine($"并集产生 {solution.Count} 个多边形");
foreach (var path in solution)
{
double area = Math.Abs(Clipper.Area(path)) / (SCALE * SCALE);
Console.WriteLine($"合并后面积: {area}");
Console.WriteLine($"顶点数: {path.Count}");
}
}
3.4.3 多个多边形的合并
并集运算特别适合合并多个多边形:
static void MergeMultiplePolygons()
{
const double SCALE = 1000.0;
// 创建一组相互重叠的矩形
Paths rectangles = new Paths();
for (int i = 0; i < 10; i++)
{
double x = i * 15.0; // 有重叠
Path rect = new Path
{
new IntPoint((long)(x * SCALE), 0),
new IntPoint((long)((x + 20) * SCALE), 0),
new IntPoint((long)((x + 20) * SCALE), (long)(30 * SCALE)),
new IntPoint((long)(x * SCALE), (long)(30 * SCALE))
};
rectangles.Add(rect);
}
Console.WriteLine($"原始矩形数量: {rectangles.Count}");
// 合并所有矩形
Clipper clipper = new Clipper();
clipper.AddPaths(rectangles, PolyType.ptSubject, true);
Paths merged = new Paths();
clipper.Execute(
ClipType.ctUnion,
merged,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
Console.WriteLine($"合并后多边形数量: {merged.Count}");
foreach (var path in merged)
{
double area = Math.Abs(Clipper.Area(path)) / (SCALE * SCALE);
Console.WriteLine($"面积: {area:F2}, 顶点数: {path.Count}");
}
}
3.4.4 并集的实际应用
应用1:地块合并
class LandMerger
{
private const double SCALE = 1000000.0;
/// <summary>
/// 合并相邻地块
/// </summary>
public static Paths MergeAdjacentParcels(List<Path> parcels)
{
if (parcels.Count == 0)
return new Paths();
Clipper clipper = new Clipper();
// 添加所有地块
foreach (var parcel in parcels)
{
clipper.AddPath(parcel, PolyType.ptSubject, true);
}
// 执行并集运算
Paths merged = new Paths();
clipper.Execute(
ClipType.ctUnion,
merged,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
return merged;
}
/// <summary>
/// 合并后计算总面积和节省的边界长度
/// </summary>
public static (double totalArea, double boundaryReduction) AnalyzeMerge(
List<Path> originalParcels,
Paths mergedParcels)
{
// 计算原始边界总长度
double originalBoundary = 0;
foreach (var parcel in originalParcels)
{
originalBoundary += CalculatePerimeter(parcel);
}
// 计算合并后边界总长度
double mergedBoundary = 0;
foreach (var parcel in mergedParcels)
{
mergedBoundary += CalculatePerimeter(parcel);
}
// 计算总面积
double totalArea = 0;
foreach (var parcel in mergedParcels)
{
totalArea += Math.Abs(Clipper.Area(parcel));
}
totalArea /= (SCALE * SCALE);
double reduction = originalBoundary - mergedBoundary;
return (totalArea, reduction / SCALE);
}
private static double CalculatePerimeter(Path path)
{
if (path.Count < 2) return 0;
double perimeter = 0;
for (int i = 0; i < path.Count; i++)
{
int next = (i + 1) % path.Count;
long dx = path[next].X - path[i].X;
long dy = path[next].Y - path[i].Y;
perimeter += Math.Sqrt(dx * dx + dy * dy);
}
return perimeter;
}
}
应用2:缓冲区合并
class BufferMerger
{
private const double SCALE = 1000.0;
/// <summary>
/// 创建多个点的缓冲区并合并
/// </summary>
public static Paths CreateMergedBuffers(
List<(double x, double y)> points,
double bufferRadius)
{
Paths buffers = new Paths();
// 为每个点创建缓冲区(圆形)
foreach (var point in points)
{
Path circle = CreateCircle(point.x, point.y, bufferRadius, 32, SCALE);
buffers.Add(circle);
}
// 合并所有缓冲区
Clipper clipper = new Clipper();
clipper.AddPaths(buffers, PolyType.ptSubject, true);
Paths merged = new Paths();
clipper.Execute(
ClipType.ctUnion,
merged,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
return merged;
}
private static Path CreateCircle(double cx, double cy, double radius,
int segments, double scale)
{
Path circle = new Path();
for (int i = 0; i < segments; i++)
{
double angle = 2 * Math.PI * i / segments;
double x = cx + radius * Math.Cos(angle);
double y = cy + radius * Math.Sin(angle);
circle.Add(new IntPoint((long)(x * scale), (long)(y * scale)));
}
return circle;
}
}
3.5 差集运算(Difference)
3.5.1 基本概念
差集运算从主题多边形中减去裁剪多边形,结果是主题多边形中不与裁剪多边形重叠的部分。注意:主题和裁剪的角色很重要。
3.5.2 基本差集示例
static void BasicDifference()
{
const double SCALE = 1000.0;
// 创建一个大矩形
Path bigRect = new Path
{
new IntPoint(0, 0),
new IntPoint((long)(100 * SCALE), 0),
new IntPoint((long)(100 * SCALE), (long)(100 * SCALE)),
new IntPoint(0, (long)(100 * SCALE))
};
// 创建一个小圆形(将从大矩形中挖除)
Path smallCircle = CreateCircle(50, 50, 20, 32, SCALE);
// 执行差集运算:大矩形 - 小圆形
Clipper clipper = new Clipper();
clipper.AddPath(bigRect, PolyType.ptSubject, true); // 主题
clipper.AddPath(smallCircle, PolyType.ptClip, true); // 裁剪
Paths solution = new Paths();
clipper.Execute(
ClipType.ctDifference,
solution,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
Console.WriteLine($"差集产生 {solution.Count} 个多边形");
foreach (var path in solution)
{
double area = Math.Abs(Clipper.Area(path)) / (SCALE * SCALE);
Console.WriteLine($"面积: {area:F2}");
Console.WriteLine($"顶点数: {path.Count}");
}
}
static Path CreateCircle(double cx, double cy, double radius, int segments, double scale)
{
Path circle = new Path();
for (int i = 0; i < segments; i++)
{
double angle = 2 * Math.PI * i / segments;
double x = cx + radius * Math.Cos(angle);
double y = cy + radius * Math.Sin(angle);
circle.Add(new IntPoint((long)(x * scale), (long)(y * scale)));
}
return circle;
}
3.5.3 多重挖孔
可以从一个多边形中挖除多个孔洞:
static void MultipleHoles()
{
const double SCALE = 1000.0;
// 创建基础矩形
Path baseRect = new Path
{
new IntPoint(0, 0),
new IntPoint((long)(200 * SCALE), 0),
new IntPoint((long)(200 * SCALE), (long)(200 * SCALE)),
new IntPoint(0, (long)(200 * SCALE))
};
// 创建多个要挖除的圆形孔洞
Paths holes = new Paths();
int[,] holePositions = { { 50, 50 }, { 150, 50 }, { 50, 150 }, { 150, 150 } };
for (int i = 0; i < holePositions.GetLength(0); i++)
{
Path hole = CreateCircle(
holePositions[i, 0],
holePositions[i, 1],
15, 32, SCALE
);
holes.Add(hole);
}
// 执行差集运算
Clipper clipper = new Clipper();
clipper.AddPath(baseRect, PolyType.ptSubject, true);
clipper.AddPaths(holes, PolyType.ptClip, true);
// 使用 PolyTree 以保持层次结构
PolyTree solution = new PolyTree();
clipper.Execute(
ClipType.ctDifference,
solution,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
Console.WriteLine("多重挖孔结果:");
PrintPolyTree(solution, 0);
}
static void PrintPolyTree(PolyNode node, int level)
{
const double SCALE = 1000.0;
string indent = new string(' ', level * 2);
foreach (var child in node.Childs)
{
double area = Math.Abs(Clipper.Area(child.Contour)) / (SCALE * SCALE);
Console.WriteLine($"{indent}{(child.IsHole ? "孔洞" : "外轮廓")}");
Console.WriteLine($"{indent} 面积: {area:F2}");
Console.WriteLine($"{indent} 顶点数: {child.Contour.Count}");
if (child.Childs.Count > 0)
{
PrintPolyTree(child, level + 1);
}
}
}
3.5.4 差集的实际应用
应用1:零件挖孔
class PartManufacturing
{
private const double SCALE = 1000.0;
/// <summary>
/// 在零件上创建孔洞
/// </summary>
public static Path CreatePartWithHoles(
Path partOutline,
List<(double x, double y, double radius)> holes)
{
Clipper clipper = new Clipper();
clipper.AddPath(partOutline, PolyType.ptSubject, true);
// 添加所有孔洞
foreach (var hole in holes)
{
Path holeCircle = CreateCircle(
hole.x, hole.y, hole.radius, 32, SCALE
);
clipper.AddPath(holeCircle, PolyType.ptClip, true);
}
Paths result = new Paths();
clipper.Execute(
ClipType.ctDifference,
result,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
return result.Count > 0 ? result[0] : new Path();
}
/// <summary>
/// 计算材料利用率
/// </summary>
public static double CalculateMaterialUtilization(
Path originalPart,
Path partWithHoles)
{
double originalArea = Math.Abs(Clipper.Area(originalPart));
double finalArea = Math.Abs(Clipper.Area(partWithHoles));
return (finalArea / originalArea) * 100.0;
}
private static Path CreateCircle(double cx, double cy, double radius,
int segments, double scale)
{
Path circle = new Path();
for (int i = 0; i < segments; i++)
{
double angle = 2 * Math.PI * i / segments;
double x = cx + radius * Math.Cos(angle);
double y = cy + radius * Math.Sin(angle);
circle.Add(new IntPoint((long)(x * scale), (long)(y * scale)));
}
return circle;
}
}
应用2:禁入区域计算
class RestrictedAreaCalculator
{
private const double SCALE = 1000000.0;
/// <summary>
/// 计算可用区域(总区域减去禁入区域)
/// </summary>
public static Paths CalculateAvailableArea(
Path totalArea,
List<Path> restrictedAreas)
{
Clipper clipper = new Clipper();
clipper.AddPath(totalArea, PolyType.ptSubject, true);
clipper.AddPaths(restrictedAreas, PolyType.ptClip, true);
Paths available = new Paths();
clipper.Execute(
ClipType.ctDifference,
available,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
return available;
}
/// <summary>
/// 生成报告
/// </summary>
public static void GenerateAreaReport(
Path totalArea,
List<Path> restrictedAreas,
Paths availableAreas)
{
double total = Math.Abs(Clipper.Area(totalArea)) / (SCALE * SCALE);
double restricted = 0;
foreach (var area in restrictedAreas)
{
restricted += Math.Abs(Clipper.Area(area));
}
restricted /= (SCALE * SCALE);
double available = 0;
foreach (var area in availableAreas)
{
available += Math.Abs(Clipper.Area(area));
}
available /= (SCALE * SCALE);
Console.WriteLine("=== 区域分析报告 ===");
Console.WriteLine($"总面积: {total:F2} 平方单位");
Console.WriteLine($"禁入区域面积: {restricted:F2} 平方单位 ({restricted / total * 100:F1}%)");
Console.WriteLine($"可用面积: {available:F2} 平方单位 ({available / total * 100:F1}%)");
Console.WriteLine($"可用区域数量: {availableAreas.Count}");
}
}
3.6 异或运算(XOR)
3.6.1 基本概念
异或运算计算两个多边形不重叠的部分,即属于其中一个但不同时属于两者的区域。结果通常会产生多个独立的多边形。
3.6.2 基本异或示例
static void BasicXor()
{
const double SCALE = 1000.0;
// 创建两个重叠的圆形
Path circle1 = CreateCircle(40, 50, 30, 32, SCALE);
Path circle2 = CreateCircle(60, 50, 30, 32, SCALE);
// 执行异或运算
Clipper clipper = new Clipper();
clipper.AddPath(circle1, PolyType.ptSubject, true);
clipper.AddPath(circle2, PolyType.ptClip, true);
Paths solution = new Paths();
clipper.Execute(
ClipType.ctXor,
solution,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
Console.WriteLine($"异或产生 {solution.Count} 个多边形");
for (int i = 0; i < solution.Count; i++)
{
double area = Math.Abs(Clipper.Area(solution[i])) / (SCALE * SCALE);
Console.WriteLine($"多边形 {i + 1}: 面积 = {area:F2}, 顶点数 = {solution[i].Count}");
}
}
static Path CreateCircle(double cx, double cy, double radius, int segments, double scale)
{
Path circle = new Path();
for (int i = 0; i < segments; i++)
{
double angle = 2 * Math.PI * i / segments;
double x = cx + radius * Math.Cos(angle);
double y = cy + radius * Math.Sin(angle);
circle.Add(new IntPoint((long)(x * scale), (long)(y * scale)));
}
return circle;
}
3.6.3 异或的实际应用
应用:差异区域分析
class DifferenceAnalyzer
{
private const double SCALE = 1000000.0;
/// <summary>
/// 分析两个规划方案的差异
/// </summary>
public static void AnalyzePlanDifferences(
Path plan1,
Path plan2,
string plan1Name,
string plan2Name)
{
// 计算异或(差异区域)
Clipper clipper = new Clipper();
clipper.AddPath(plan1, PolyType.ptSubject, true);
clipper.AddPath(plan2, PolyType.ptClip, true);
Paths differences = new Paths();
clipper.Execute(
ClipType.ctXor,
differences,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
// 计算交集(相同区域)
clipper.Clear();
clipper.AddPath(plan1, PolyType.ptSubject, true);
clipper.AddPath(plan2, PolyType.ptClip, true);
Paths intersection = new Paths();
clipper.Execute(
ClipType.ctIntersection,
intersection,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
// 计算面积
double area1 = Math.Abs(Clipper.Area(plan1)) / (SCALE * SCALE);
double area2 = Math.Abs(Clipper.Area(plan2)) / (SCALE * SCALE);
double diffArea = 0;
foreach (var path in differences)
{
diffArea += Math.Abs(Clipper.Area(path));
}
diffArea /= (SCALE * SCALE);
double commonArea = 0;
foreach (var path in intersection)
{
commonArea += Math.Abs(Clipper.Area(path));
}
commonArea /= (SCALE * SCALE);
// 生成报告
Console.WriteLine("=== 方案差异分析 ===");
Console.WriteLine($"{plan1Name} 面积: {area1:F2}");
Console.WriteLine($"{plan2Name} 面积: {area2:F2}");
Console.WriteLine($"共同区域面积: {commonArea:F2}");
Console.WriteLine($"差异区域面积: {diffArea:F2}");
Console.WriteLine($"相似度: {(commonArea / Math.Max(area1, area2) * 100):F1}%");
Console.WriteLine($"差异区域数量: {differences.Count}");
}
}
3.7 填充规则详解
3.7.1 填充规则的重要性
填充规则决定了如何判断一个区域是多边形的"内部"还是"外部",这对布尔运算的结果有重要影响。
3.7.2 四种填充规则
NonZero(非零规则)
- 最常用的规则
- 基于环绕数判断内外
- 适合大多数场景
EvenOdd(奇偶规则)
- 基于射线交点数的奇偶性
- 对自相交多边形的处理不同
- 适合某些特殊图形
Positive(正数规则)
- 只考虑正环绕数
- 用于特定的几何处理
Negative(负数规则)
- 只考虑负环绕数
- 较少使用
3.7.3 填充规则对比示例
static void CompareFillingRules()
{
const double SCALE = 1000.0;
// 创建一个自相交的多边形(八字形)
Path figureEight = new Path
{
new IntPoint(0, 0),
new IntPoint((long)(100 * SCALE), (long)(100 * SCALE)),
new IntPoint((long)(100 * SCALE), 0),
new IntPoint(0, (long)(100 * SCALE))
};
// 测试不同的填充规则
PolyFillType[] fillTypes = {
PolyFillType.pftNonZero,
PolyFillType.pftEvenOdd,
PolyFillType.pftPositive,
PolyFillType.pftNegative
};
string[] fillNames = { "NonZero", "EvenOdd", "Positive", "Negative" };
for (int i = 0; i < fillTypes.Length; i++)
{
Clipper clipper = new Clipper();
clipper.AddPath(figureEight, PolyType.ptSubject, true);
Paths solution = new Paths();
clipper.Execute(
ClipType.ctUnion, // 使用并集来观察填充规则的效果
solution,
fillTypes[i],
fillTypes[i]
);
Console.WriteLine($"\n填充规则: {fillNames[i]}");
Console.WriteLine($" 产生多边形数量: {solution.Count}");
double totalArea = 0;
foreach (var path in solution)
{
double area = Math.Abs(Clipper.Area(path)) / (SCALE * SCALE);
totalArea += area;
Console.WriteLine($" 多边形面积: {area:F2}");
}
Console.WriteLine($" 总面积: {totalArea:F2}");
}
}
3.8 高级技巧和最佳实践
3.8.1 性能优化
class PerformanceOptimizer
{
/// <summary>
/// 批量处理多个运算
/// </summary>
public static List<Paths> BatchOperations(
List<(Path subject, Path clip, ClipType operation)> operations)
{
var results = new List<Paths>();
// 复用 Clipper 对象
Clipper clipper = new Clipper();
foreach (var op in operations)
{
clipper.Clear(); // 清除之前的数据
clipper.AddPath(op.subject, PolyType.ptSubject, true);
clipper.AddPath(op.clip, PolyType.ptClip, true);
Paths solution = new Paths();
clipper.Execute(
op.operation,
solution,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
results.Add(solution);
}
return results;
}
/// <summary>
/// 预处理:简化输入多边形
/// </summary>
public static Path PreprocessPolygon(Path input, double tolerance = 1.0)
{
// 移除共线点
Path cleaned = new Path(input);
Clipper.CleanPolygon(cleaned, tolerance);
// 简化自相交
Paths simplified = Clipper.SimplifyPolygon(
cleaned,
PolyFillType.pftNonZero
);
return simplified.Count > 0 ? simplified[0] : cleaned;
}
}
3.8.2 错误处理和验证
class ValidationHelper
{
/// <summary>
/// 验证路径有效性
/// </summary>
public static bool IsValidPath(Path path, bool closed)
{
if (path == null)
return false;
// 封闭路径至少需要3个点
if (closed && path.Count < 3)
return false;
// 开放路径至少需要2个点
if (!closed && path.Count < 2)
return false;
return true;
}
/// <summary>
/// 安全执行布尔运算
/// </summary>
public static (bool success, Paths result, string error) SafeExecute(
Path subject,
Path clip,
ClipType operation)
{
try
{
if (!IsValidPath(subject, true))
return (false, null, "主题多边形无效");
if (!IsValidPath(clip, true))
return (false, null, "裁剪多边形无效");
Clipper clipper = new Clipper();
clipper.AddPath(subject, PolyType.ptSubject, true);
clipper.AddPath(clip, PolyType.ptClip, true);
Paths solution = new Paths();
bool success = clipper.Execute(
operation,
solution,
PolyFillType.pftNonZero,
PolyFillType.pftNonZero
);
if (!success)
return (false, null, "运算执行失败");
return (true, solution, null);
}
catch (Exception ex)
{
return (false, null, $"异常: {ex.Message}");
}
}
}
3.8.3 结果后处理
class ResultProcessor
{
/// <summary>
/// 过滤小面积多边形
/// </summary>
public static Paths FilterByArea(Paths input, double minArea, double scale)
{
Paths filtered = new Paths();
double threshold = minArea * scale * scale;
foreach (var path in input)
{
double area = Math.Abs(Clipper.Area(path));
if (area >= threshold)
{
filtered.Add(path);
}
}
return filtered;
}
/// <summary>
/// 按面积排序
/// </summary>
public static Paths SortByArea(Paths input, bool descending = true)
{
var sorted = input.OrderBy(p => Math.Abs(Clipper.Area(p))).ToList();
if (descending)
sorted.Reverse();
return sorted;
}
/// <summary>
/// 清理和优化结果
/// </summary>
public static Paths CleanResult(Paths input, double tolerance = 1.415)
{
Paths cleaned = new Paths();
foreach (var path in input)
{
Path cleanPath = new Path(path);
Clipper.CleanPolygon(cleanPath, tolerance);
if (cleanPath.Count >= 3)
{
cleaned.Add(cleanPath);
}
}
return cleaned;
}
}
3.9 综合实战示例
让我们通过一个完整的实际应用来综合运用本章所学:
using System;
using System.Collections.Generic;
using System.Linq;
using ClipperLib;
using Path = System.Collections.Generic.List<ClipperLib.IntPoint>;
using Paths = System.Collections.Generic.List<System.Collections.Generic.List<ClipperLib.IntPoint>>;
namespace ClipperBooleanDemo
{
/// <summary>
/// 建筑用地规划系统
/// </summary>
class LandPlanningSystem
{
private const double SCALE = 1000000.0;
public static void Main(string[] args)
{
Console.WriteLine("=== 建筑用地规划系统 ===\n");
// 1. 定义总用地范围
Path totalLand = CreateRectangle(0, 0, 200, 150);
Console.WriteLine($"总用地面积: {GetArea(totalLand):F2} 平方米");
// 2. 定义限制区域(道路、河流等)
List<Path> restrictions = new List<Path>
{
CreateRectangle(0, 70, 200, 80), // 横向道路
CreateRectangle(95, 0, 105, 150), // 纵向道路
CreateCircle(50, 40, 15, 32) // 水池
};
double restrictedArea = restrictions.Sum(p => GetArea(p));
Console.WriteLine($"限制区域面积: {restrictedArea:F2} 平方米");
// 3. 计算可用建设区域(总用地 - 限制区域)
Paths availableLand = SubtractAreas(totalLand, restrictions);
double availableArea = availableLand.Sum(p => GetArea(p));
Console.WriteLine($"\n可用建设区域:");
Console.WriteLine($" 区域数量: {availableLand.Count}");
Console.WriteLine($" 总面积: {availableArea:F2} 平方米");
Console.WriteLine($" 利用率: {(availableArea / GetArea(totalLand) * 100):F1}%");
// 4. 规划建筑物
List<Path> buildings = new List<Path>
{
CreateRectangle(10, 10, 40, 60), // 建筑1
CreateRectangle(50, 10, 80, 60), // 建筑2
CreateRectangle(110, 10, 140, 60), // 建筑3
CreateRectangle(150, 10, 180, 60), // 建筑4
CreateRectangle(10, 90, 40, 140), // 建筑5
CreateRectangle(110, 90, 140, 140), // 建筑6
CreateRectangle(150, 90, 180, 140) // 建筑7
};
Console.WriteLine($"\n规划建筑数量: {buildings.Count}");
double buildingArea = buildings.Sum(p => GetArea(p));
Console.WriteLine($"建筑占地面积: {buildingArea:F2} 平方米");
// 5. 检查建筑是否在可用区域内
Console.WriteLine("\n建筑合规性检查:");
int validCount = 0;
for (int i = 0; i < buildings.Count; i++)
{
bool isValid = CheckBuildingCompliance(buildings[i], availableLand);
Console.WriteLine($" 建筑{i + 1}: {(isValid ? "✓ 合规" : "✗ 不合规")}");
if (isValid) validCount++;
}
// 6. 计算容积率
Console.WriteLine($"\n规划指标:");
double floorArea = buildingArea * 5; // 假设5层建筑
double plotRatio = floorArea / GetArea(totalLand);
Console.WriteLine($" 建筑密度: {(buildingArea / GetArea(totalLand) * 100):F1}%");
Console.WriteLine($" 容积率: {plotRatio:F2}");
// 7. 创建绿化带(在可用区域内减去建筑)
Paths greenSpace = CalculateGreenSpace(availableLand, buildings);
double greenArea = greenSpace.Sum(p => GetArea(p));
Console.WriteLine($" 绿化面积: {greenArea:F2} 平方米");
Console.WriteLine($" 绿化率: {(greenArea / GetArea(totalLand) * 100):F1}%");
// 8. 生成建筑物缓冲区(安全距离)
double safetyDistance = 5.0; // 5米安全距离
Paths safetyZones = CreateSafetyBuffers(buildings, safetyDistance);
Console.WriteLine($"\n安全缓冲区:");
Console.WriteLine($" 缓冲距离: {safetyDistance} 米");
// 9. 检查建筑间距
Console.WriteLine("\n建筑间距检查:");
CheckBuildingSpacing(buildings, safetyDistance);
Console.WriteLine("\n按任意键退出...");
Console.ReadKey();
}
// 创建矩形
static Path CreateRectangle(double x1, double y1, double x2, double y2)
{
return new Path
{
new IntPoint((long)(x1 * SCALE), (long)(y1 * SCALE)),
new IntPoint((long)(x2 * SCALE), (long)(y1 * SCALE)),
new IntPoint((long)(x2 * SCALE), (long)(y2 * SCALE)),
new IntPoint((long)(x1 * SCALE), (long)(y2 * SCALE))
};
}
// 创建圆形
static Path CreateCircle(double cx, double cy, double radius, int segments)
{
Path circle = new Path();
for (int i = 0; i < segments; i++)
{
double angle = 2 * Math.PI * i / segments;
double x = cx + radius * Math.Cos(angle);
double y = cy + radius * Math.Sin(angle);
circle.Add(new IntPoint((long)(x * SCALE), (long)(y * SCALE)));
}
return circle;
}
// 计算面积
static double GetArea(Path path)
{
return Math.Abs(Clipper.Area(path)) / (SCALE * SCALE);
}
// 差集运算:从总区域减去限制区域
static Paths SubtractAreas(Path total, List<Path> restrictions)
{
Clipper clipper = new Clipper();
clipper.AddPath(total, PolyType.ptSubject, true);
clipper.AddPaths(restrictions, PolyType.ptClip, true);
Paths result = new Paths();
clipper.Execute(ClipType.ctDifference, result);
return result;
}
// 检查建筑是否在可用区域内
static bool CheckBuildingCompliance(Path building, Paths availableAreas)
{
// 计算建筑与可用区域的交集
Clipper clipper = new Clipper();
clipper.AddPaths(availableAreas, PolyType.ptSubject, true);
clipper.AddPath(building, PolyType.ptClip, true);
Paths intersection = new Paths();
clipper.Execute(ClipType.ctIntersection, intersection);
if (intersection.Count == 0)
return false;
// 检查交集面积是否等于建筑面积(允许小误差)
double buildingArea = GetArea(building);
double intersectionArea = intersection.Sum(p => GetArea(p));
return Math.Abs(buildingArea - intersectionArea) < 0.01;
}
// 计算绿化空间
static Paths CalculateGreenSpace(Paths availableAreas, List<Path> buildings)
{
// 先合并所有建筑
Clipper clipper = new Clipper();
clipper.AddPaths(buildings, PolyType.ptSubject, true);
Paths mergedBuildings = new Paths();
clipper.Execute(ClipType.ctUnion, mergedBuildings);
// 从可用区域减去建筑
clipper.Clear();
clipper.AddPaths(availableAreas, PolyType.ptSubject, true);
clipper.AddPaths(mergedBuildings, PolyType.ptClip, true);
Paths greenSpace = new Paths();
clipper.Execute(ClipType.ctDifference, greenSpace);
return greenSpace;
}
// 创建安全缓冲区
static Paths CreateSafetyBuffers(List<Path> buildings, double distance)
{
ClipperOffset offset = new ClipperOffset();
offset.AddPaths(buildings, JoinType.jtRound, EndType.etClosedPolygon);
Paths buffered = new Paths();
offset.Execute(ref buffered, distance * SCALE);
return buffered;
}
// 检查建筑间距
static void CheckBuildingSpacing(List<Path> buildings, double minDistance)
{
for (int i = 0; i < buildings.Count; i++)
{
for (int j = i + 1; j < buildings.Count; j++)
{
// 创建缓冲区
ClipperOffset offset = new ClipperOffset();
offset.AddPath(buildings[i], JoinType.jtRound, EndType.etClosedPolygon);
Paths buffered = new Paths();
offset.Execute(ref buffered, minDistance * SCALE);
// 检查是否与另一建筑相交
Clipper clipper = new Clipper();
clipper.AddPaths(buffered, PolyType.ptSubject, true);
clipper.AddPath(buildings[j], PolyType.ptClip, true);
Paths intersection = new Paths();
clipper.Execute(ClipType.ctIntersection, intersection);
bool hasConflict = intersection.Count > 0;
if (hasConflict)
{
Console.WriteLine($" 建筑{i + 1} 与 建筑{j + 1}: ✗ 间距不足");
}
}
}
}
}
}
3.10 本章小结
在本章中,我们深入学习了 Clipper1 的布尔运算功能:
- 四种基本运算:交集、并集、差集、异或
- 标准使用流程:创建 Clipper 对象、添加路径、执行运算、处理结果
- 填充规则:NonZero、EvenOdd 及其影响
- 实际应用:碰撞检测、地块合并、零件挖孔、区域分析等
- 高级技巧:性能优化、错误处理、结果后处理
重点掌握:
- 理解每种运算的几何含义
- 正确区分主题和裁剪多边形
- 选择合适的填充规则
- 处理复杂的多层嵌套结构
- 在实际项目中灵活应用
在下一章中,我们将学习 Clipper1 的另一个重要功能:多边形偏移操作,包括膨胀、收缩以及各种连接和端点类型的使用。
3.11 练习题
-
基础练习:创建两个任意多边形,分别执行四种布尔运算,观察结果
-
碰撞检测:实现一个简单的2D游戏碰撞检测系统
-
地图合并:给定多个重叠的行政区划边界,合并为一个完整的区域
-
挖孔操作:在一个复杂多边形上创建多个不规则孔洞
-
差异分析:比较两个版本的设计图,找出修改的部分
-
综合应用:实现一个简单的CAD布尔运算工具,支持多个图形的复杂运算

浙公网安备 33010602011771号