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

推荐订阅源

K
Kaspersky official blog
云风的 BLOG
云风的 BLOG
IT之家
IT之家
T
The Blog of Author Tim Ferriss
C
Check Point Blog
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
T
Tailwind CSS Blog
G
Google Developers Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
F
Fortinet All Blogs
D
DataBreaches.Net
The Register - Security
The Register - Security
L
LINUX DO - 最新话题
W
WeLiveSecurity
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V2EX - 技术
V2EX - 技术
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
小众软件
小众软件
F
Full Disclosure
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
I
InfoQ
S
Secure Thoughts
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
AI
AI
Recent Announcements
Recent Announcements
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
CXSECURITY Database RSS Feed - CXSecurity.com
V
Vulnerabilities – Threatpost
NISL@THU
NISL@THU
SecWiki News
SecWiki News
Cisco Talos Blog
Cisco Talos Blog
H
Heimdal Security Blog
Y
Y Combinator Blog
N
News | PayPal Newsroom
P
Privacy International News Feed
美团技术团队
Attack and Defense Labs
Attack and Defense Labs
D
Docker
PCI Perspectives
PCI Perspectives
Webroot Blog
Webroot Blog
A
About on SuperTechFans
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
MongoDB | Blog
MongoDB | Blog
T
Threat Research - Cisco Blogs

博客园 - 我才是银古

第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
第9章:Clipper 类结构与初始化
我才是银古 · 2026-06-24 · via 博客园 - 我才是银古

第9章:Clipper 类结构与初始化

9.1 概述

Clipper 类继承自 ClipperBase,是用户直接使用的主要类。它实现了多边形布尔运算的核心算法,包括交集、并集、差集和异或运算。

9.2 类定义

public class Clipper : ClipperBase
{
    // 初始化选项常量
    public const int ioReverseSolution = 1;
    public const int ioStrictlySimple = 2;
    public const int ioPreserveCollinear = 4;

    // 私有成员
    private ClipType m_ClipType;
    private Maxima m_Maxima;
    private TEdge m_SortedEdges;
    private List<IntersectNode> m_IntersectList;
    IComparer<IntersectNode> m_IntersectNodeComparer;
    private bool m_ExecuteLocked;
    private PolyFillType m_ClipFillType;
    private PolyFillType m_SubjFillType;
    private List<Join> m_Joins;
    private List<Join> m_GhostJoins;
    private bool m_UsingPolyTree;
    
#if use_xyz
    public delegate void ZFillCallback(IntPoint bot1, IntPoint top1, 
        IntPoint bot2, IntPoint top2, ref IntPoint pt);
    public ZFillCallback ZFillFunction { get; set; }
#endif

    // 公共属性
    public bool ReverseSolution { get; set; }
    public bool StrictlySimple { get; set; }
}

9.3 成员变量详解

9.3.1 运算状态

成员 类型 说明
m_ClipType ClipType 当前裁剪操作类型
m_ClipFillType PolyFillType 裁剪多边形的填充规则
m_SubjFillType PolyFillType 主体多边形的填充规则
m_ExecuteLocked bool 防止重入执行
m_UsingPolyTree bool 是否使用 PolyTree 输出

9.3.2 数据结构

成员 类型 说明
m_Maxima Maxima 局部极大值链表
m_SortedEdges TEdge 排序边列表头
m_IntersectList List<IntersectNode> 交点列表
m_Joins List<Join> 连接点列表
m_GhostJoins List<Join> 幽灵连接点(水平边)

9.3.3 辅助结构

Maxima 结构

internal class Maxima
{
    internal cInt X;        // 极大值的 X 坐标
    internal Maxima Next;   // 下一个极大值
    internal Maxima Prev;   // 上一个极大值
}

IntersectNode 结构

public class IntersectNode
{
    internal TEdge Edge1;   // 参与交点的第一条边
    internal TEdge Edge2;   // 参与交点的第二条边
    internal IntPoint Pt;   // 交点坐标
}

Join 结构

internal class Join
{
    internal OutPt OutPt1;  // 第一个输出点
    internal OutPt OutPt2;  // 第二个输出点
    internal IntPoint OffPt; // 偏移点(用于判断连接方向)
}

9.4 构造函数

public Clipper(int InitOptions = 0) : base()
{
    m_Scanbeam = null;
    m_Maxima = null;
    m_ActiveEdges = null;
    m_SortedEdges = null;
    m_IntersectList = new List<IntersectNode>();
    m_IntersectNodeComparer = new MyIntersectNodeSort();
    m_ExecuteLocked = false;
    m_UsingPolyTree = false;
    m_PolyOuts = new List<OutRec>();
    m_Joins = new List<Join>();
    m_GhostJoins = new List<Join>();
    
    // 解析初始化选项
    ReverseSolution = (ioReverseSolution & InitOptions) != 0;
    StrictlySimple = (ioStrictlySimple & InitOptions) != 0;
    PreserveCollinear = (ioPreserveCollinear & InitOptions) != 0;
    
#if use_xyz
    ZFillFunction = null;
#endif
}

9.4.1 初始化选项

选项 说明
ioReverseSolution 1 反转结果多边形方向
ioStrictlySimple 2 确保结果是简单多边形
ioPreserveCollinear 4 保留共线顶点

使用示例

// 默认选项
Clipper c1 = new Clipper();

// 反转结果
Clipper c2 = new Clipper(Clipper.ioReverseSolution);

// 组合多个选项
Clipper c3 = new Clipper(
    Clipper.ioStrictlySimple | 
    Clipper.ioPreserveCollinear
);

9.5 Execute 方法

Clipper 提供了四个 Execute 重载方法:

9.5.1 返回 Paths(简化版)

public bool Execute(ClipType clipType, Paths solution, 
    PolyFillType FillType = PolyFillType.pftEvenOdd)
{
    return Execute(clipType, solution, FillType, FillType);
}

9.5.2 返回 Paths(完整版)

public bool Execute(ClipType clipType, Paths solution,
    PolyFillType subjFillType, PolyFillType clipFillType)
{
    if (m_ExecuteLocked) return false;
    if (m_HasOpenPaths) 
        throw new ClipperException(
            "Error: PolyTree struct is needed for open path clipping.");

    m_ExecuteLocked = true;
    solution.Clear();
    m_SubjFillType = subjFillType;
    m_ClipFillType = clipFillType;
    m_ClipType = clipType;
    m_UsingPolyTree = false;
    
    bool succeeded;
    try
    {
        succeeded = ExecuteInternal();
        if (succeeded) BuildResult(solution);
    }
    finally
    {
        DisposeAllPolyPts();
        m_ExecuteLocked = false;
    }
    return succeeded;
}

9.5.3 返回 PolyTree(简化版)

public bool Execute(ClipType clipType, PolyTree polytree,
    PolyFillType FillType = PolyFillType.pftEvenOdd)
{
    return Execute(clipType, polytree, FillType, FillType);
}

9.5.4 返回 PolyTree(完整版)

public bool Execute(ClipType clipType, PolyTree polytree,
    PolyFillType subjFillType, PolyFillType clipFillType)
{
    if (m_ExecuteLocked) return false;
    m_ExecuteLocked = true;
    m_SubjFillType = subjFillType;
    m_ClipFillType = clipFillType;
    m_ClipType = clipType;
    m_UsingPolyTree = true;
    
    bool succeeded;
    try
    {
        succeeded = ExecuteInternal();
        if (succeeded) BuildResult2(polytree);
    }
    finally
    {
        DisposeAllPolyPts();
        m_ExecuteLocked = false;
    }
    return succeeded;
}

9.6 ExecuteInternal 核心方法

private bool ExecuteInternal()
{
    try
    {
        Reset();
        m_SortedEdges = null;
        m_Maxima = null;

        cInt botY, topY;
        if (!PopScanbeam(out botY)) return false;
        
        InsertLocalMinimaIntoAEL(botY);
        
        while (PopScanbeam(out topY) || LocalMinimaPending())
        {
            ProcessHorizontals();
            m_GhostJoins.Clear();
            
            if (!ProcessIntersections(topY)) return false;
            
            ProcessEdgesAtTopOfScanbeam(topY);
            botY = topY;
            InsertLocalMinimaIntoAEL(botY);
        }

        // 修正方向
        foreach (OutRec outRec in m_PolyOuts)
        {
            if (outRec.Pts == null || outRec.IsOpen) continue;
            if ((outRec.IsHole ^ ReverseSolution) == (Area(outRec) > 0))
                ReversePolyPtLinks(outRec.Pts);
        }

        // 处理连接
        JoinCommonEdges();

        // 清理输出
        foreach (OutRec outRec in m_PolyOuts)
        {
            if (outRec.Pts == null) continue;
            else if (outRec.IsOpen)
                FixupOutPolyline(outRec);
            else
                FixupOutPolygon(outRec);
        }

        // 处理严格简单模式
        if (StrictlySimple) DoSimplePolygons();
        
        return true;
    }
    finally 
    {
        m_Joins.Clear();
        m_GhostJoins.Clear();          
    }
}

9.6.1 执行流程图

ExecuteInternal()
       │
       ▼
    Reset()
       │
       ▼
PopScanbeam(botY)
       │
       ▼
InsertLocalMinimaIntoAEL(botY)
       │
       ▼
┌──────────────────────────────┐
│  while (有扫描线或待处理LM)  │
│         │                    │
│         ▼                    │
│  ProcessHorizontals()        │
│         │                    │
│         ▼                    │
│  ProcessIntersections(topY)  │
│         │                    │
│         ▼                    │
│  ProcessEdgesAtTopOfScanbeam │
│         │                    │
│         ▼                    │
│  InsertLocalMinimaIntoAEL    │
│         │                    │
│         └────────────────────┘
       │
       ▼
   方向修正
       │
       ▼
 JoinCommonEdges()
       │
       ▼
   输出清理
       │
       ▼
 DoSimplePolygons() (可选)
       │
       ▼
    返回结果

9.7 公共属性

9.7.1 ReverseSolution

public bool ReverseSolution { get; set; }

功能:反转结果多边形的方向。

默认行为

  • 外轮廓:逆时针(正面积)
  • 孔洞:顺时针(负面积)

启用后

  • 外轮廓:顺时针
  • 孔洞:逆时针

9.7.2 StrictlySimple

public bool StrictlySimple { get; set; }

功能:确保输出是严格简单的多边形(没有自交点)。

注意:启用此选项可能增加处理时间和输出多边形数量。

9.7.3 PreserveCollinear

public bool PreserveCollinear { get; set; }  // 继承自 ClipperBase

功能:保留共线顶点。

默认行为:移除共线顶点以简化输出。

9.8 Z 坐标处理(use_xyz)

#if use_xyz
public delegate void ZFillCallback(IntPoint bot1, IntPoint top1, 
    IntPoint bot2, IntPoint top2, ref IntPoint pt);
public ZFillCallback ZFillFunction { get; set; }
#endif

用途:当两条边相交创建新点时,通过回调函数计算 Z 坐标。

使用示例

#if use_xyz
// 插值计算 Z
clipper.ZFillFunction = (bot1, top1, bot2, top2, ref IntPoint pt) =>
{
    // 基于两条边的端点插值计算交点的 Z
    double ratio1 = (double)(pt.Y - bot1.Y) / (top1.Y - bot1.Y);
    double z1 = bot1.Z + ratio1 * (top1.Z - bot1.Z);
    
    double ratio2 = (double)(pt.Y - bot2.Y) / (top2.Y - bot2.Y);
    double z2 = bot2.Z + ratio2 * (top2.Z - bot2.Z);
    
    pt.Z = (cInt)((z1 + z2) / 2);  // 平均值
};
#endif

9.9 使用示例

9.9.1 基本使用

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

Path clip = new Path();
clip.Add(new IntPoint(50, 50));
clip.Add(new IntPoint(150, 50));
clip.Add(new IntPoint(150, 150));
clip.Add(new IntPoint(50, 150));

// 执行裁剪
Clipper clipper = new Clipper();
clipper.AddPath(subject, PolyType.ptSubject, true);
clipper.AddPath(clip, PolyType.ptClip, true);

Paths solution = new Paths();
bool success = clipper.Execute(ClipType.ctIntersection, solution);

9.9.2 使用 PolyTree 获取层次结构

Clipper clipper = new Clipper();
clipper.AddPaths(subjects, PolyType.ptSubject, true);
clipper.AddPaths(clips, PolyType.ptClip, true);

PolyTree tree = new PolyTree();
clipper.Execute(ClipType.ctUnion, tree);

// 遍历结果
void TraverseTree(PolyNode node, int depth)
{
    for (int i = 0; i < node.ChildCount; i++)
    {
        PolyNode child = node.Childs[i];
        string type = child.IsHole ? "孔洞" : "外轮廓";
        Console.WriteLine($"{new string(' ', depth * 2)}{type}: {child.Contour.Count}点");
        TraverseTree(child, depth + 1);
    }
}

TraverseTree(tree, 0);

9.9.3 复用 Clipper 实例

Clipper clipper = new Clipper();

// 第一次操作
clipper.AddPaths(paths1, PolyType.ptSubject, true);
Paths result1 = new Paths();
clipper.Execute(ClipType.ctUnion, result1);

// 清理并重用
clipper.Clear();
clipper.AddPaths(paths2, PolyType.ptSubject, true);
clipper.AddPaths(paths3, PolyType.ptClip, true);
Paths result2 = new Paths();
clipper.Execute(ClipType.ctDifference, result2);

9.10 本章小结

本章详细分析了 Clipper 类的结构:

  1. 类设计

    • 继承自 ClipperBase
    • 通过构造函数参数设置选项
    • 支持多种 Execute 重载
  2. 核心成员

    • 运算状态(ClipType、FillType等)
    • 数据结构(Maxima、IntersectList、Joins等)
    • 属性选项(ReverseSolution、StrictlySimple等)
  3. Execute 方法

    • 支持 Paths 和 PolyTree 两种输出
    • 可指定不同的填充规则
    • 通过 ExecuteInternal 执行核心算法
  4. 可选功能

    • Z 坐标处理(use_xyz)
    • 严格简单多边形
    • 保留共线顶点

理解 Clipper 类的结构是正确使用该库的基础。


上一章:局部极小值与扫描线 | 返回目录 | 下一章:布尔运算执行流程