第20章:实际应用与最佳实践
20.1 概述
本章将介绍 Clipper 在实际项目中的应用场景、性能优化技巧和最佳实践。
20.2 典型应用场景
20.2.1 CAD/CAM 应用
刀具补偿:
// CNC 刀具补偿
Path toolPath = GetOriginalPath();
double toolRadius = 5.0; // 刀具半径
ClipperOffset co = new ClipperOffset();
co.AddPath(toolPath, JoinType.jtRound, EndType.etClosedPolygon);
Paths compensated = new Paths();
co.Execute(ref compensated, -toolRadius); // 内缩
// compensated 是补偿后的刀具路径
层叠切割:
// 多层切割路径生成
Path outerBoundary = GetMaterialBoundary();
double stepover = 3.0; // 切削步距
List<Paths> layers = new List<Paths>();
ClipperOffset co = new ClipperOffset();
double offset = 0;
while (true)
{
co.Clear();
co.AddPath(outerBoundary, JoinType.jtRound, EndType.etClosedPolygon);
Paths layer = new Paths();
co.Execute(ref layer, -offset);
if (layer.Count == 0) break;
layers.Add(layer);
offset += stepover;
}
20.2.2 GIS 应用
缓冲区分析:
// 河流缓冲区
Path riverCenterline = LoadRiverPath();
double bufferDistance = 100; // 100米缓冲区
ClipperOffset co = new ClipperOffset();
co.AddPath(riverCenterline, JoinType.jtRound, EndType.etOpenRound);
Paths floodZone = new Paths();
co.Execute(ref floodZone, bufferDistance);
多边形叠加分析:
// 土地利用叠加
Paths landuse = LoadLandusePolygons();
Paths zoning = LoadZoningPolygons();
Clipper c = new Clipper();
c.AddPaths(landuse, PolyType.ptSubject, true);
c.AddPaths(zoning, PolyType.ptClip, true);
Paths intersection = new Paths();
c.Execute(ClipType.ctIntersection, intersection);
20.2.3 图形设计
文字描边:
// 为文字添加轮廓
Path textGlyph = GetTextGlyphPath('A');
ClipperOffset co = new ClipperOffset();
co.AddPath(textGlyph, JoinType.jtRound, EndType.etClosedPolygon);
// 内外轮廓
Paths outerStroke = new Paths();
Paths innerText = new Paths();
co.Execute(ref outerStroke, 3); // 外扩
co.Clear();
co.AddPath(textGlyph, JoinType.jtRound, EndType.etClosedPolygon);
co.Execute(ref innerText, -1); // 内缩(可选的瘦体效果)
20.2.4 游戏开发
碰撞检测:
// 检测两个多边形是否重叠
bool CheckCollision(Path poly1, Path poly2)
{
Clipper c = new Clipper();
c.AddPath(poly1, PolyType.ptSubject, true);
c.AddPath(poly2, PolyType.ptClip, true);
Paths intersection = new Paths();
c.Execute(ClipType.ctIntersection, intersection);
return intersection.Count > 0;
}
// 计算重叠面积
double GetOverlapArea(Path poly1, Path poly2)
{
Clipper c = new Clipper();
c.AddPath(poly1, PolyType.ptSubject, true);
c.AddPath(poly2, PolyType.ptClip, true);
Paths intersection = new Paths();
c.Execute(ClipType.ctIntersection, intersection);
double area = 0;
foreach (var p in intersection)
area += Math.Abs(Clipper.Area(p));
return area;
}
视野计算:
// 视野锥与障碍物的交集
Path viewCone = CreateViewCone(position, direction, angle, distance);
Paths obstacles = GetObstacles();
Clipper c = new Clipper();
c.AddPath(viewCone, PolyType.ptSubject, true);
c.AddPaths(obstacles, PolyType.ptClip, true);
Paths visibleArea = new Paths();
c.Execute(ClipType.ctDifference, visibleArea);
20.3 性能优化
20.3.1 坐标缩放
// 使用整数坐标时的缩放策略
public class ScaledClipper
{
private const double Scale = 1000.0; // 缩放因子
public static IntPoint ToClipperPoint(double x, double y)
{
return new IntPoint((cInt)(x * Scale), (cInt)(y * Scale));
}
public static PointF FromClipperPoint(IntPoint pt)
{
return new PointF((float)(pt.X / Scale), (float)(pt.Y / Scale));
}
public static Path ScalePath(IEnumerable<PointF> points)
{
Path result = new Path();
foreach (var pt in points)
result.Add(ToClipperPoint(pt.X, pt.Y));
return result;
}
public static List<PointF> UnscalePath(Path path)
{
var result = new List<PointF>();
foreach (var pt in path)
result.Add(FromClipperPoint(pt));
return result;
}
}
20.3.2 批量操作
// 批量处理多个布尔运算
public static Paths BatchUnion(List<Path> polygons)
{
if (polygons.Count == 0) return new Paths();
if (polygons.Count == 1) return new Paths { polygons[0] };
// 分治法合并
while (polygons.Count > 1)
{
List<Path> merged = new List<Path>();
Clipper c = new Clipper();
for (int i = 0; i < polygons.Count; i += 2)
{
c.Clear();
c.AddPath(polygons[i], PolyType.ptSubject, true);
if (i + 1 < polygons.Count)
c.AddPath(polygons[i + 1], PolyType.ptClip, true);
Paths result = new Paths();
c.Execute(ClipType.ctUnion, result);
foreach (var p in result)
merged.Add(p);
}
polygons = merged;
}
return new Paths { polygons[0] };
}
20.3.3 预分配内存
// 预分配容量
Path polygon = new Path(expectedPointCount);
Paths result = new Paths(expectedPathCount);
// 复用 Clipper 实例
Clipper clipper = new Clipper();
for (int i = 0; i < iterations; i++)
{
clipper.Clear(); // 清理但保留分配的内存
// ... 执行操作 ...
}
20.3.4 避免高精度模式
// 检查是否需要高精度
bool RequiresHighPrecision(Paths paths)
{
foreach (var path in paths)
foreach (var pt in path)
if (Math.Abs(pt.X) > ClipperBase.loRange ||
Math.Abs(pt.Y) > ClipperBase.loRange)
return true;
return false;
}
// 如果可能,缩放坐标以避免高精度
void NormalizeCoordinates(Paths paths, out double scale)
{
long maxCoord = 1;
foreach (var path in paths)
foreach (var pt in path)
{
maxCoord = Math.Max(maxCoord, Math.Abs(pt.X));
maxCoord = Math.Max(maxCoord, Math.Abs(pt.Y));
}
if (maxCoord > ClipperBase.loRange)
{
scale = (double)ClipperBase.loRange / maxCoord * 0.9;
ScalePaths(paths, scale);
}
else
scale = 1.0;
}
20.4 常见问题与解决方案
20.4.1 自相交处理
// 问题:输入多边形自相交
// 解决:使用 SimplifyPolygon
Path problematic = GetSelfIntersectingPath();
Paths fixed = Clipper.SimplifyPolygon(problematic);
20.4.2 微小间隙
// 问题:微小间隙导致分离
// 解决:先膨胀后收缩
Paths withGaps = GetPathsWithSmallGaps();
ClipperOffset co = new ClipperOffset();
co.AddPaths(withGaps, JoinType.jtRound, EndType.etClosedPolygon);
Paths expanded = new Paths();
co.Execute(ref expanded, 2);
co.Clear();
co.AddPaths(expanded, JoinType.jtRound, EndType.etClosedPolygon);
Paths closed = new Paths();
co.Execute(ref closed, -2);
20.4.3 浮点精度问题
// 问题:浮点坐标导致不一致
// 解决:使用适当的缩放因子
const double Scale = 1e6; // 保留6位小数
Path ScaleUp(IEnumerable<PointD> points)
{
Path result = new Path();
foreach (var pt in points)
result.Add(new IntPoint((long)(pt.X * Scale), (long)(pt.Y * Scale)));
return result;
}
List<PointD> ScaleDown(Path path)
{
var result = new List<PointD>();
foreach (var pt in path)
result.Add(new PointD(pt.X / Scale, pt.Y / Scale));
return result;
}
20.4.4 方向不一致
// 确保所有外轮廓逆时针,孔洞顺时针
void NormalizeOrientation(Paths paths)
{
foreach (var path in paths)
{
double area = Clipper.Area(path);
// 假设第一个是外轮廓
if (path == paths[0])
{
if (area < 0) path.Reverse();
}
else
{
// 孔洞应该顺时针
if (area > 0) path.Reverse();
}
}
}
20.5 与其他库的集成
20.5.1 与 System.Drawing 集成
using System.Drawing;
using System.Drawing.Drawing2D;
public static GraphicsPath ToGraphicsPath(Paths paths)
{
GraphicsPath gp = new GraphicsPath();
foreach (var path in paths)
{
if (path.Count < 3) continue;
PointF[] points = new PointF[path.Count];
for (int i = 0; i < path.Count; i++)
points[i] = new PointF(path[i].X, path[i].Y);
gp.AddPolygon(points);
}
return gp;
}
20.5.2 与 WPF 集成
using System.Windows.Media;
public static PathGeometry ToWpfGeometry(Paths paths)
{
PathGeometry pg = new PathGeometry();
foreach (var path in paths)
{
if (path.Count < 3) continue;
PathFigure figure = new PathFigure();
figure.StartPoint = new Point(path[0].X, path[0].Y);
figure.IsClosed = true;
PolyLineSegment segment = new PolyLineSegment();
for (int i = 1; i < path.Count; i++)
segment.Points.Add(new Point(path[i].X, path[i].Y));
figure.Segments.Add(segment);
pg.Figures.Add(figure);
}
return pg;
}
20.5.3 与 Unity 集成
using UnityEngine;
public static Mesh ToUnityMesh(Paths paths)
{
// 使用三角剖分库(如 Poly2Tri)
List<Vector3> vertices = new List<Vector3>();
List<int> triangles = new List<int>();
// 转换并三角剖分
// ...
Mesh mesh = new Mesh();
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
return mesh;
}
20.6 调试技巧
20.6.1 可视化输出
public static void DebugDrawPaths(Paths paths, string filename)
{
IntRect bounds = ClipperBase.GetBounds(paths);
int margin = 10;
int width = (int)(bounds.right - bounds.left + 2 * margin);
int height = (int)(bounds.bottom - bounds.top + 2 * margin);
using (Bitmap bmp = new Bitmap(width, height))
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.White);
foreach (var path in paths)
{
if (path.Count < 2) continue;
PointF[] pts = new PointF[path.Count];
for (int i = 0; i < path.Count; i++)
pts[i] = new PointF(
path[i].X - bounds.left + margin,
path[i].Y - bounds.top + margin);
g.DrawPolygon(Pens.Blue, pts);
}
bmp.Save(filename);
}
}
20.6.2 数据验证
public static bool ValidatePaths(Paths paths, out string error)
{
error = null;
foreach (var path in paths)
{
// 检查最小顶点数
if (path.Count < 3)
{
error = "Path has fewer than 3 vertices";
return false;
}
// 检查坐标范围
foreach (var pt in path)
{
if (Math.Abs(pt.X) > ClipperBase.hiRange ||
Math.Abs(pt.Y) > ClipperBase.hiRange)
{
error = "Coordinate out of range";
return false;
}
}
// 检查自相交
Paths simplified = Clipper.SimplifyPolygon(path);
if (simplified.Count != 1)
{
error = "Self-intersecting polygon";
return false;
}
}
return true;
}
20.7 本章小结
本章介绍了 Clipper 的实际应用与最佳实践:
- 应用场景:
- CAD/CAM:刀具补偿、层叠切割
- GIS:缓冲区分析、叠加分析
- 图形设计:描边、轮廓
- 游戏开发:碰撞检测、视野计算
- 性能优化:
- 坐标缩放
- 批量处理
- 预分配内存
- 避免高精度模式
- 常见问题:
- 自相交处理
- 微小间隙
- 浮点精度
- 方向一致性
- 库集成:
- System.Drawing
- WPF
- Unity
- 调试技巧:
- 可视化输出
- 数据验证
| 上一章:辅助函数与工具 | 返回目录 |