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

推荐订阅源

F
Fortinet All Blogs
爱范儿
爱范儿
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
J
Java Code Geeks
宝玉的分享
宝玉的分享
Jina AI
Jina AI
B
Blog
N
Netflix TechBlog - Medium
Recent Announcements
Recent Announcements
aimingoo的专栏
aimingoo的专栏
腾讯CDC
C
Check Point Blog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
博客园 - Franky
罗磊的独立博客
B
Blog RSS Feed
WordPress大学
WordPress大学
小众软件
小众软件
博客园 - 叶小钗
M
MIT News - Artificial intelligence
GbyAI
GbyAI

博客园 - 我才是银古

第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
第19章:辅助函数与工具方法
我才是银古 · 2026-06-24 · via 博客园 - 我才是银古

第19章:辅助函数与工具方法

19.1 概述

Clipper 库提供了许多辅助函数和工具方法,用于简化多边形操作和处理。本章将分析这些实用功能。

19.2 SimplifyPolygon/SimplifyPolygons

简化多边形,消除自相交:

public static Paths SimplifyPolygon(Path poly, 
    PolyFillType fillType = PolyFillType.pftEvenOdd)
{
    Paths result = new Paths();
    Clipper c = new Clipper();
    c.StrictlySimple = true;
    c.AddPath(poly, PolyType.ptSubject, true);
    c.Execute(ClipType.ctUnion, result, fillType, fillType);
    return result;
}

public static Paths SimplifyPolygons(Paths polys,
    PolyFillType fillType = PolyFillType.pftEvenOdd)
{
    Paths result = new Paths();
    Clipper c = new Clipper();
    c.StrictlySimple = true;
    c.AddPaths(polys, PolyType.ptSubject, true);
    c.Execute(ClipType.ctUnion, result, fillType, fillType);
    return result;
}

19.2.1 使用示例

// 自相交的8字形
Path figure8 = new Path();
figure8.Add(new IntPoint(0, 0));
figure8.Add(new IntPoint(100, 100));
figure8.Add(new IntPoint(100, 0));
figure8.Add(new IntPoint(0, 100));

// 简化为两个独立的三角形
Paths simplified = Clipper.SimplifyPolygon(figure8);
// 结果:两个三角形

19.3 CleanPolygon/CleanPolygons

清理多边形,移除冗余顶点:

public static Path CleanPolygon(Path path, double distance = 1.415)
{
    int cnt = path.Count;
    if (cnt == 0) return new Path();

    OutPt[] outPts = new OutPt[cnt];
    for (int i = 0; i < cnt; ++i) 
        outPts[i] = new OutPt();

    // 建立链表
    for (int i = 0; i < cnt; ++i)
    {
        outPts[i].Pt = path[i];
        outPts[i].Next = outPts[(i + 1) % cnt];
        outPts[i].Next.Prev = outPts[i];
        outPts[i].Idx = 0;
    }

    double distSqrd = distance * distance;
    OutPt op = outPts[0];
    
    while (op.Idx == 0 && op.Next != op.Prev)
    {
        if (PointsAreClose(op.Pt, op.Prev.Pt, distSqrd))
        {
            op = ExcludeOp(op);
            cnt--;
        }
        else if (PointsAreClose(op.Prev.Pt, op.Next.Pt, distSqrd))
        {
            ExcludeOp(op.Next);
            op = ExcludeOp(op);
            cnt -= 2;
        }
        else if (SlopesNearCollinear(op.Prev.Pt, op.Pt, op.Next.Pt, distSqrd))
        {
            op = ExcludeOp(op);
            cnt--;
        }
        else
        {
            op.Idx = 1;
            op = op.Next;
        }
    }

    if (cnt < 3) cnt = 0;
    Path result = new Path(cnt);
    for (int i = 0; i < cnt; ++i)
    {
        result.Add(op.Pt);
        op = op.Next;
    }
    return result;
}

19.3.1 清理规则

  1. 重复点:距离小于阈值的连续点
  2. 尖刺:前后点距离太近
  3. 共线点:在直线上的中间点

19.3.2 使用示例

Path noisy = new Path();
noisy.Add(new IntPoint(0, 0));
noisy.Add(new IntPoint(1, 0));  // 太近
noisy.Add(new IntPoint(100, 0));
noisy.Add(new IntPoint(100, 100));
noisy.Add(new IntPoint(50, 50));  // 共线的中间点
noisy.Add(new IntPoint(0, 0));

Path cleaned = Clipper.CleanPolygon(noisy, 2.0);
// 结果:移除冗余点后的简化多边形

19.4 MinkowskiSum/MinkowskiDiff

闵可夫斯基和与差:

public static Paths MinkowskiSum(Path pattern, Path path, bool pathIsClosed)
{
    Paths paths = Minkowski(pattern, path, true, pathIsClosed);
    Clipper c = new Clipper();
    c.AddPaths(paths, PolyType.ptSubject, true);
    c.Execute(ClipType.ctUnion, paths, 
        PolyFillType.pftNonZero, PolyFillType.pftNonZero);
    return paths;
}

public static Paths MinkowskiDiff(Path poly1, Path poly2)
{
    Paths paths = Minkowski(poly1, poly2, false, true);
    Clipper c = new Clipper();
    c.AddPaths(paths, PolyType.ptSubject, true);
    c.Execute(ClipType.ctUnion, paths, 
        PolyFillType.pftNonZero, PolyFillType.pftNonZero);
    return paths;
}

19.4.1 Minkowski 核心方法

private static Paths Minkowski(Path pattern, Path path, bool IsSum, bool IsClosed)
{
    int delta = (IsClosed ? 1 : 0);
    int polyCnt = pattern.Count;
    int pathCnt = path.Count;
    Paths result = new Paths(pathCnt);
    
    if (IsSum)
        for (int i = 0; i < pathCnt; i++)
        {
            Path p = new Path(polyCnt);
            foreach (IntPoint ip in pattern)
                p.Add(new IntPoint(path[i].X + ip.X, path[i].Y + ip.Y));
            result.Add(p);
        }
    else
        for (int i = 0; i < pathCnt; i++)
        {
            Path p = new Path(polyCnt);
            foreach (IntPoint ip in pattern)
                p.Add(new IntPoint(path[i].X - ip.X, path[i].Y - ip.Y));
            result.Add(p);
        }

    // 构建四边形连接
    Paths quads = new Paths((pathCnt + delta) * (polyCnt + 1));
    for (int i = 0; i < pathCnt - 1 + delta; i++)
        for (int j = 0; j < polyCnt; j++)
        {
            Path quad = new Path(4);
            quad.Add(result[i % pathCnt][j % polyCnt]);
            quad.Add(result[(i + 1) % pathCnt][j % polyCnt]);
            quad.Add(result[(i + 1) % pathCnt][(j + 1) % polyCnt]);
            quad.Add(result[i % pathCnt][(j + 1) % polyCnt]);
            if (!Orientation(quad)) quad.Reverse();
            quads.Add(quad);
        }
    return quads;
}

19.4.2 闵可夫斯基和的应用

// 圆形膨胀效果
Path circle = CreateCircle(0, 0, 10, 16);
Path shape = CreateSquare(50, 50, 100);

Paths expanded = Clipper.MinkowskiSum(circle, shape, true);
// 结果:圆角正方形

19.5 Area 方法

计算有符号面积:

public static double Area(Path poly)
{
    int cnt = (int)poly.Count;
    if (cnt < 3) return 0;
    double a = 0;
    for (int i = 0, j = cnt - 1; i < cnt; ++i)
    {
        a += ((double)poly[j].X + poly[i].X) * 
             ((double)poly[j].Y - poly[i].Y);
        j = i;
    }
    return -a * 0.5;
}

19.5.1 鞋带公式

面积 = (1/2) * |Σ(x[i]*y[i+1] - x[i+1]*y[i])|

简化形式(代码使用):
面积 = -(1/2) * Σ((x[j]+x[i])*(y[j]-y[i]))

19.5.2 使用示例

Path triangle = new Path();
triangle.Add(new IntPoint(0, 0));
triangle.Add(new IntPoint(100, 0));
triangle.Add(new IntPoint(50, 100));

double area = Clipper.Area(triangle);
// 结果:5000(正值=逆时针)

triangle.Reverse();
area = Clipper.Area(triangle);
// 结果:-5000(负值=顺时针)

19.6 Orientation 方法

判断多边形方向:

public static bool Orientation(Path poly)
{
    return Area(poly) >= 0;
}
  • true:逆时针(外轮廓)
  • false:顺时针(孔洞)

19.7 ReversePath/ReversePaths

反转路径方向:

public static void ReversePath(Path p)
{
    p.Reverse();
}

public static void ReversePaths(Paths polys)
{
    foreach (var poly in polys) 
    { 
        poly.Reverse(); 
    }
}

19.8 PointInPolygon

点在多边形内判断:

public static int PointInPolygon(IntPoint pt, Path path)
{
    // 返回值:
    // 0 = 外部
    // 1 = 内部
    // -1 = 在边界上
    
    int result = 0, cnt = path.Count;
    if (cnt < 3) return 0;
    
    IntPoint ip = path[0];
    for (int i = 1; i <= cnt; ++i)
    {
        IntPoint ipNext = (i == cnt ? path[0] : path[i]);
        
        // 检查点是否在边界上
        if (ipNext.Y == pt.Y)
        {
            if ((ipNext.X == pt.X) || (ip.Y == pt.Y &&
                ((ipNext.X > pt.X) == (ip.X < pt.X)))) 
                return -1;
        }
        
        // 射线检测
        if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y))
        {
            if (ip.X >= pt.X)
            {
                if (ipNext.X > pt.X) result = 1 - result;
                else
                {
                    double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
                               (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
                    if (d == 0) return -1;
                    else if ((d > 0) == (ipNext.Y > ip.Y)) 
                        result = 1 - result;
                }
            }
            else
            {
                if (ipNext.X > pt.X)
                {
                    double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
                               (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
                    if (d == 0) return -1;
                    else if ((d > 0) == (ipNext.Y > ip.Y)) 
                        result = 1 - result;
                }
            }
        }
        ip = ipNext;
    }
    return result;
}

19.9 DistanceSqrd 和 DistanceFromLineSqrd

距离计算辅助函数:

private static double DistanceSqrd(IntPoint pt1, IntPoint pt2)
{
    double dx = ((double)pt1.X - pt2.X);
    double dy = ((double)pt1.Y - pt2.Y);
    return (dx * dx + dy * dy);
}

private static double DistanceFromLineSqrd(IntPoint pt, IntPoint ln1, IntPoint ln2)
{
    // 点到线段的距离的平方
    double A = ln1.Y - ln2.Y;
    double B = ln2.X - ln1.X;
    double C = A * ln1.X + B * ln1.Y;
    C = A * pt.X + B * pt.Y - C;
    return (C * C) / (A * A + B * B);
}

19.10 Round 函数

安全的四舍五入:

internal static cInt Round(double value)
{
    return value < 0 ? (cInt)(value - 0.5) : (cInt)(value + 0.5);
}

19.11 Swap 方法

交换两个值:

private static void Swap(ref cInt val1, ref cInt val2)
{
    cInt tmp = val1;
    val1 = val2;
    val2 = tmp;
}

19.12 near_zero

浮点数零值判断:

internal static bool near_zero(double val)
{
    return (val > -tolerance) && (val < tolerance);
}

19.13 使用示例集合

19.13.1 完整的工作流程

// 1. 创建多边形
Path polygon = new Path();
polygon.Add(new IntPoint(0, 0));
polygon.Add(new IntPoint(100, 0));
polygon.Add(new IntPoint(100, 100));
polygon.Add(new IntPoint(0, 100));

// 2. 清理
polygon = Clipper.CleanPolygon(polygon);

// 3. 检查方向
if (!Clipper.Orientation(polygon))
    polygon.Reverse();

// 4. 计算面积
double area = Clipper.Area(polygon);
Console.WriteLine($"面积: {area}");

// 5. 点在多边形内测试
IntPoint testPt = new IntPoint(50, 50);
int result = Clipper.PointInPolygon(testPt, polygon);
Console.WriteLine($"点在内部: {result == 1}");

19.13.2 批量处理

// 处理多个多边形
Paths polygons = LoadPolygons();

// 清理所有
for (int i = 0; i < polygons.Count; i++)
    polygons[i] = Clipper.CleanPolygon(polygons[i]);

// 简化(消除自相交)
polygons = Clipper.SimplifyPolygons(polygons);

// 计算总面积
double totalArea = 0;
foreach (var poly in polygons)
    totalArea += Math.Abs(Clipper.Area(poly));

19.14 本章小结

本章介绍了 Clipper 库的辅助函数和工具方法:

  1. 简化函数:SimplifyPolygon 消除自相交
  2. 清理函数:CleanPolygon 移除冗余顶点
  3. 闵可夫斯基运算:MinkowskiSum/MinkowskiDiff
  4. 几何计算:Area、Orientation、PointInPolygon
  5. 辅助工具:距离计算、四舍五入、交换等

这些工具方法在实际应用中非常有用,可以简化多边形处理流程。


上一章:偏移算法实现 | 返回目录 | 下一章:实际应用与最佳实践