第13章:ClipperD 浮点裁剪类
13.1 概述
ClipperD 是 Clipper2 提供的浮点坐标裁剪类。它内部将浮点坐标缩放为整数进行计算,然后将结果转换回浮点数。这种设计既保留了整数运算的精确性,又提供了浮点接口的便利性。
13.2 类定义
13.2.1 类声明
public class ClipperD : ClipperBase
{
private readonly double _scale;
private readonly double _invScale;
#if USINGZ
private ZCallbackD? _zCallback;
public ZCallbackD? ZCallback
{
get => _zCallback;
set => _zCallback = value;
}
#endif
public ClipperD(int roundingDecimalPrecision = 2)
{
InternalClipper.CheckPrecision(roundingDecimalPrecision);
_scale = Math.Pow(10, roundingDecimalPrecision);
_invScale = 1 / _scale;
}
}
13.2.2 精度参数
// roundingDecimalPrecision 范围:-8 到 8
//
// precision = 2: _scale = 100, 精确到小数点后2位
// precision = 3: _scale = 1000, 精确到小数点后3位
// precision = 0: _scale = 1, 整数
// precision = -2: _scale = 0.01, 精确到百位
13.3 缩放原理
13.3.1 坐标转换
浮点坐标 → 整数坐标(乘以 scale)
(12.345, 67.890) → (1234, 6789) [precision=2]
整数坐标 → 浮点坐标(乘以 invScale)
(1234, 6789) → (12.34, 67.89) [precision=2]
13.3.2 Scale 和 InvScale
public ClipperD(int roundingDecimalPrecision = 2)
{
// 检查精度范围
InternalClipper.CheckPrecision(roundingDecimalPrecision);
// 计算缩放因子
_scale = Math.Pow(10, roundingDecimalPrecision);
_invScale = 1 / _scale;
}
13.4 AddPath/AddPaths 方法
13.4.1 添加浮点路径
public void AddPath(PathD path, PathType pathType, bool isOpen = false)
{
AddPaths(new PathsD { path }, pathType, isOpen);
}
public void AddPaths(PathsD paths, PathType pathType, bool isOpen = false)
{
if (paths.Count == 0) return;
// 转换为整数路径
Paths64 paths64 = new Paths64(paths.Count);
foreach (PathD path in paths)
{
Path64 path64 = new Path64(path.Count);
foreach (PointD pt in path)
{
path64.Add(new Point64(pt, _scale));
}
paths64.Add(path64);
}
// 调用基类方法
base.AddPaths(paths64, pathType, isOpen);
}
13.4.2 便捷方法
public void AddSubject(PathD path)
{
AddPath(path, PathType.Subject);
}
public void AddSubject(PathsD paths)
{
AddPaths(paths, PathType.Subject);
}
public void AddOpenSubject(PathD path)
{
AddPath(path, PathType.Subject, true);
}
public void AddClip(PathD path)
{
AddPath(path, PathType.Clip);
}
public void AddClip(PathsD paths)
{
AddPaths(paths, PathType.Clip);
}
13.5 Execute 方法
13.5.1 主要重载
// 输出到 PathsD
public bool Execute(ClipType clipType, FillRule fillRule, PathsD closedSolution)
{
return Execute(clipType, fillRule, closedSolution, new PathsD());
}
// 分离闭合和开放路径
public bool Execute(ClipType clipType, FillRule fillRule,
PathsD closedSolution, PathsD openSolution)
{
closedSolution.Clear();
openSolution.Clear();
try
{
ExecuteInternal(clipType, fillRule);
// 构建整数路径
Paths64 closedPaths64 = new Paths64();
Paths64 openPaths64 = new Paths64();
BuildPaths(closedPaths64, openPaths64);
// 转换为浮点路径
ConvertPaths(closedPaths64, closedSolution);
ConvertPaths(openPaths64, openSolution);
}
catch
{
return false;
}
ClearSolution();
return true;
}
// 输出到 PolyTreeD
public bool Execute(ClipType clipType, FillRule fillRule, PolyTreeD polytree)
{
return Execute(clipType, fillRule, polytree, new PathsD());
}
public bool Execute(ClipType clipType, FillRule fillRule,
PolyTreeD polytree, PathsD openSolution)
{
polytree.Clear();
openSolution.Clear();
try
{
ExecuteInternal(clipType, fillRule);
// 构建整数多边形树
PolyTree64 polytree64 = new PolyTree64();
Paths64 openPaths64 = new Paths64();
BuildTree(polytree64, openPaths64);
// 转换为浮点
ConvertPolyTree(polytree64, polytree);
ConvertPaths(openPaths64, openSolution);
}
catch
{
return false;
}
ClearSolution();
return true;
}
13.5.2 ConvertPaths
private void ConvertPaths(Paths64 paths64, PathsD pathsD)
{
pathsD.Capacity = paths64.Count;
foreach (Path64 path64 in paths64)
{
PathD pathD = new PathD(path64.Count);
foreach (Point64 pt in path64)
{
pathD.Add(new PointD(pt, _invScale));
}
pathsD.Add(pathD);
}
}
13.5.3 ConvertPolyTree
private void ConvertPolyTree(PolyPath64 polytree64, PolyPathD polytreeD)
{
polytreeD.Clear();
polytreeD.Scale = _invScale;
ConvertPolyTreeNode(polytree64, polytreeD);
}
private void ConvertPolyTreeNode(PolyPath64 node64, PolyPathD nodeD)
{
// 转换当前节点的路径
if (node64.Polygon != null)
{
PathD pathD = new PathD(node64.Polygon.Count);
foreach (Point64 pt in node64.Polygon)
{
pathD.Add(new PointD(pt, _invScale));
}
nodeD.Polygon = pathD;
}
// 递归转换子节点
foreach (PolyPath64 child64 in node64)
{
PolyPathD childD = nodeD.AddChild();
ConvertPolyTreeNode(child64, childD);
}
}
13.6 精度控制
13.6.1 精度检查
internal static void CheckPrecision(int precision)
{
if (precision < -8 || precision > 8)
throw new Exception("Error: Precision is out of range.");
}
13.6.2 精度选择建议
| 应用场景 | 建议精度 | 说明 |
|---|---|---|
| 屏幕坐标 | 0-1 | 像素级精度 |
| CAD 应用 | 2-4 | 毫米级精度 |
| 地理坐标 | 6-8 | 经纬度精度 |
| 科学计算 | 8 | 最高精度 |
13.6.3 精度与性能
// 高精度意味着更大的整数值
// precision = 8: _scale = 100000000
//
// 坐标值被放大后可能接近 MaxCoord 限制
// 需要平衡精度和坐标范围
13.7 Z 坐标处理
13.7.1 ZCallbackD 委托
#if USINGZ
public delegate void ZCallbackD(PointD bot1, PointD top1,
PointD bot2, PointD top2, ref PointD intersectPt);
#endif
13.7.2 Z 回调转换
#if USINGZ
private void InternalZCallback(Point64 bot1, Point64 top1,
Point64 bot2, Point64 top2, ref Point64 intersectPt)
{
if (_zCallback == null) return;
// 转换为浮点
PointD bot1D = new PointD(bot1, _invScale);
PointD top1D = new PointD(top1, _invScale);
PointD bot2D = new PointD(bot2, _invScale);
PointD top2D = new PointD(top2, _invScale);
PointD intersectPtD = new PointD(intersectPt, _invScale);
// 调用用户回调
_zCallback(bot1D, top1D, bot2D, top2D, ref intersectPtD);
// Z 值保持整数
intersectPt = new Point64(intersectPt.X, intersectPt.Y, intersectPtD.z);
}
#endif
13.7.3 使用示例
#if USINGZ
ClipperD clipperD = new ClipperD(2);
clipperD.ZCallback = (bot1, top1, bot2, top2, ref PointD pt) =>
{
// 线性插值
if (Math.Abs(top1.y - bot1.y) > 0.0001)
{
double t = (pt.y - bot1.y) / (top1.y - bot1.y);
pt.z = (long)(bot1.z + t * (top1.z - bot1.z));
}
};
#endif
13.8 与 Clipper64 的对比
13.8.1 功能对比
| 特性 | Clipper64 | ClipperD |
|---|---|---|
| 输入类型 | Point64, Path64 | PointD, PathD |
| 输出类型 | Paths64, PolyTree64 | PathsD, PolyTreeD |
| 内部计算 | 整数 | 整数(缩放后) |
| 精度控制 | 无 | 可配置 |
| 性能 | 略快 | 有转换开销 |
13.8.2 选择建议
// 使用 Clipper64 当:
// - 数据本身是整数
// - 需要最高性能
// - 精确控制坐标
// 使用 ClipperD 当:
// - 数据是浮点数
// - 需要小数精度
// - 便利性优先
13.9 使用示例
13.9.1 基本使用
ClipperD clipper = new ClipperD(3); // 3 位小数精度
PathD subject = new PathD {
new PointD(0.0, 0.0),
new PointD(100.5, 0.0),
new PointD(100.5, 100.5),
new PointD(0.0, 100.5)
};
clipper.AddSubject(subject);
PathD clip = new PathD {
new PointD(50.25, 50.25),
new PointD(150.75, 50.25),
new PointD(150.75, 150.75),
new PointD(50.25, 150.75)
};
clipper.AddClip(clip);
PathsD result = new PathsD();
clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
// 结果坐标精确到 0.001
foreach (PathD path in result)
{
foreach (PointD pt in path)
{
Console.WriteLine($"({pt.x:F3}, {pt.y:F3})");
}
}
13.9.2 混合使用
// 可以混合使用静态方法和类实例
// 方法1:使用 ClipperD 类
ClipperD clipper = new ClipperD(2);
clipper.AddSubject(subjectPaths);
clipper.AddClip(clipPaths);
PathsD result1 = new PathsD();
clipper.Execute(ClipType.Union, FillRule.NonZero, result1);
// 方法2:使用静态方法
PathsD result2 = Clipper.Union(subjectPaths, clipPaths,
FillRule.NonZero, precision: 2);
13.10 常见问题
13.10.1 精度丢失
// 问题:输出坐标与输入略有不同
ClipperD clipper = new ClipperD(2); // 2 位小数
PointD input = new PointD(12.345, 67.891);
// 内部:(1234, 6789)
// 输出:(12.34, 67.89) - 第三位小数丢失
// 解决:使用更高精度
ClipperD clipper = new ClipperD(4); // 4 位小数
13.10.2 坐标溢出
// 问题:高精度 + 大坐标可能溢出
ClipperD clipper = new ClipperD(8); // scale = 100000000
PointD bigPt = new PointD(1e12, 1e12);
// 缩放后:1e20 > MaxCoord,溢出!
// 解决:限制坐标范围或降低精度
// 使用精度 6,坐标范围约 ±9e12
13.10.3 性能优化
// 批量处理时,避免重复创建 ClipperD
ClipperD clipper = new ClipperD(2);
foreach (var data in dataList)
{
clipper.Clear();
clipper.AddSubject(data.Subject);
clipper.AddClip(data.Clip);
PathsD result = new PathsD();
clipper.Execute(ClipType.Intersection, FillRule.NonZero, result);
// 处理结果...
}
13.11 内部实现细节
13.11.1 Point64 构造
// PointD 转 Point64
public Point64(PointD pt, double scale)
{
X = (long)Math.Round(pt.x * scale, MidpointRounding.AwayFromZero);
Y = (long)Math.Round(pt.y * scale, MidpointRounding.AwayFromZero);
#if USINGZ
Z = pt.z; // Z 直接复制,不缩放
#endif
}
13.11.2 PointD 构造
// Point64 转 PointD
public PointD(Point64 pt, double scale)
{
x = pt.X * scale; // scale 通常是 invScale
y = pt.Y * scale;
#if USINGZ
z = pt.Z;
#endif
}
13.12 本章小结
ClipperD 提供了浮点坐标的裁剪接口:
- 核心原理:浮点坐标缩放为整数计算
- 精度控制:通过
roundingDecimalPrecision参数 - API 对应:与
Clipper64对应的浮点版本方法 - Z 坐标:支持浮点 Z 回调
- 使用场景:需要浮点精度的应用
选择 ClipperD 还是 Clipper64 取决于数据类型和精度需求。
| 上一章:Clipper64裁剪类详解 | 返回目录 | 下一章:布尔运算执行流程 |