惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
博客园 - 司徒正美
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw
第13章:ClipperD 浮点裁剪类
我才是银古 · 2026-06-23 · via 博客园 - 我才是银古

第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 提供了浮点坐标的裁剪接口:

  1. 核心原理:浮点坐标缩放为整数计算
  2. 精度控制:通过 roundingDecimalPrecision 参数
  3. API 对应:与 Clipper64 对应的浮点版本方法
  4. Z 坐标:支持浮点 Z 回调
  5. 使用场景:需要浮点精度的应用

选择 ClipperD 还是 Clipper64 取决于数据类型和精度需求。


上一章:Clipper64裁剪类详解 | 返回目录 | 下一章:布尔运算执行流程