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

推荐订阅源

The Last Watchdog
The Last Watchdog
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 热门话题
G
GRAHAM CLULEY
S
Schneier on Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
SegmentFault 最新的问题
IT之家
IT之家
阮一峰的网络日志
阮一峰的网络日志
Recorded Future
Recorded Future
I
Intezer
云风的 BLOG
云风的 BLOG
博客园 - Franky
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
T
Tenable Blog
The Hacker News
The Hacker News
T
The Blog of Author Tim Ferriss
Attack and Defense Labs
Attack and Defense Labs
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
News and Events Feed by Topic
有赞技术团队
有赞技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
N
News and Events Feed by Topic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Secure Thoughts
The Register - Security
The Register - Security
B
Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
The Cloudflare Blog
Webroot Blog
Webroot Blog
W
WeLiveSecurity
H
Heimdal Security Blog
博客园 - 三生石上(FineUI控件)
V
Vulnerabilities – Threatpost
G
Google Developers Blog
O
OpenAI News
V
V2EX
罗磊的独立博客
博客园_首页
N
News | PayPal Newsroom
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
TaoSecurity Blog
TaoSecurity Blog
Cloudbric
Cloudbric
H
Hacker News: Front Page
博客园 - 叶小钗
T
Tor Project blog
AI
AI

博客园 - 我才是银古

第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 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第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. 辅助工具:距离计算、四舍五入、交换等

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


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