
























本章总结 Clipper2 在实际项目中的应用技巧、性能优化方法和常见问题的解决方案。
| 需求 | 推荐类 |
|---|---|
| 整数坐标布尔运算 | Clipper64 |
| 浮点坐标布尔运算 | ClipperD |
| 快速矩形裁剪 | RectClip64 |
| 路径偏移 | ClipperOffset |
| 简单操作 | 静态方法 Clipper.* |
// 简单操作用静态方法
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);
// 不好:每次创建新实例
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);
}
// 预估结果大小
int estimatedCount = subjects.Sum(p => p.Count);
Paths64 result = new Paths64(estimatedCount);
// 快速跳过不相交的多边形
Rect64 clipBounds = Clipper.GetBounds(clips);
foreach (Path64 subject in subjects)
{
Rect64 subjectBounds = Clipper.GetBounds(subject);
if (!subjectBounds.Intersects(clipBounds))
{
// 完全不相交,跳过
continue;
}
// 执行裁剪
clipper.AddSubject(subject);
}
// 对于大量独立的裁剪操作
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();
// 屏幕坐标(像素)
ClipperD clipper = new ClipperD(0); // 整数精度
// CAD 坐标(毫米,保留 2 位小数)
ClipperD clipper = new ClipperD(2); // 0.01 精度
// 地理坐标(经纬度,保留 6 位小数)
ClipperD clipper = new ClipperD(6); // 0.000001 精度
// 手动缩放可以获得更好的控制
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);
// 检查坐标范围
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);
}
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;
}
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;
}
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);
}
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);
}
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;
}
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;
}
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;
}
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);
}
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>");
}
}
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)))}");
}
[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));
}
// 大批量处理时,定期调用 GC
int batchSize = 1000;
int processed = 0;
foreach (var item in items)
{
ProcessItem(item);
processed++;
if (processed % batchSize == 0)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
// 使用 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,也清理
}
}
使用 Clipper2 的最佳实践:
遵循这些最佳实践可以帮助你在项目中高效、正确地使用 Clipper2。
本教程全面介绍了 Clipper2 C# 源代码的各个方面:
希望本教程能帮助你深入理解 Clipper2 的实现原理,在实际项目中更好地应用这个优秀的几何库。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。