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

推荐订阅源

S
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
A
Arctic Wolf
Security Latest
Security Latest
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Troy Hunt's Blog
Latest news
Latest news
Help Net Security
Help Net Security
S
Security Affairs
Webroot Blog
Webroot Blog
The Hacker News
The Hacker News
AI
AI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tor Project blog
Forbes - Security
Forbes - Security
Google DeepMind News
Google DeepMind News
AWS News Blog
AWS News Blog
Attack and Defense Labs
Attack and Defense Labs
P
Proofpoint News Feed
www.infosecurity-magazine.com
www.infosecurity-magazine.com
H
Help Net Security
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
Google Online Security Blog
Google Online Security Blog
MongoDB | Blog
MongoDB | Blog
Cyberwarzone
Cyberwarzone
The Last Watchdog
The Last Watchdog
S
Securelist
N
News and Events Feed by Topic
S
Secure Thoughts
F
Fortinet All Blogs
博客园_首页
C
Cybersecurity and Infrastructure Security Agency CISA
量子位
M
MIT News - Artificial intelligence
F
Full Disclosure
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Microsoft Security Blog
Microsoft Security Blog
I
InfoQ
P
Privacy International News Feed
L
LangChain Blog
Know Your Adversary
Know Your Adversary
C
CERT Recently Published Vulnerability Notes

博客园 - 我才是银古

第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
第14章:输出多边形构建
我才是银古 · 2026-06-24 · via 博客园 - 我才是银古

第14章:输出多边形构建

14.1 概述

在 Clipper 处理过程中,输出多边形通过 OutRec 和 OutPt 数据结构逐步构建。本章将详细分析这些结构及其相关方法。

14.2 OutRec 结构

internal class OutRec
{
    internal int Idx;              // 在 m_PolyOuts 中的索引
    internal bool IsHole;          // 是否为孔洞
    internal bool IsOpen;          // 是否为开放路径
    internal OutRec FirstLeft;     // 第一个左侧轮廓(父轮廓)
    internal OutPt Pts;            // 输出点链表头
    internal OutPt BottomPt;       // 最底部的点
    internal PolyNode PolyNode;    // 对应的 PolyNode(用于 PolyTree)
}

14.3 OutPt 结构

internal class OutPt
{
    internal int Idx;      // 所属 OutRec 的索引
    internal IntPoint Pt;  // 点坐标
    internal OutPt Next;   // 下一个点
    internal OutPt Prev;   // 上一个点
}

14.3.1 OutPt 链表结构

环形双向链表:

     ┌──────────────────────────┐
     │                          │
     ▼                          │
  OutPt1 ←──→ OutPt2 ←──→ OutPt3 ─┘
     ▲           │
     └───────────┘
         Pts(入口点)

14.4 CreateOutRec

创建新的输出记录:

internal OutRec CreateOutRec()
{
    OutRec result = new OutRec();
    result.Idx = Unassigned;
    result.IsHole = false;
    result.IsOpen = false;
    result.FirstLeft = null;
    result.Pts = null;
    result.BottomPt = null;
    result.PolyNode = null;
    m_PolyOuts.Add(result);
    result.Idx = m_PolyOuts.Count - 1;
    return result;
}

14.5 AddOutPt

向输出多边形添加点:

internal OutPt AddOutPt(TEdge e, IntPoint pt)
{
    if (e.OutIdx < 0)
    {
        // 创建新的输出记录
        OutRec outRec = CreateOutRec();
        outRec.IsOpen = (e.WindDelta == 0);
        OutPt newOp = new OutPt();
        outRec.Pts = newOp;
        newOp.Idx = outRec.Idx;
        newOp.Pt = pt;
        newOp.Next = newOp;
        newOp.Prev = newOp;
        if (!outRec.IsOpen)
            SetHoleState(e, outRec);
        e.OutIdx = outRec.Idx;
        return newOp;
    }
    else
    {
        // 添加到已有输出记录
        OutRec outRec = m_PolyOuts[e.OutIdx];
        OutPt op = outRec.Pts;
        
        bool ToFront = (e.Side == EdgeSide.esLeft);
        
        if (ToFront && pt == op.Pt) 
            return op;
        else if (!ToFront && pt == op.Prev.Pt) 
            return op.Prev;

        OutPt newOp = new OutPt();
        newOp.Idx = outRec.Idx;
        newOp.Pt = pt;
        newOp.Next = op;
        newOp.Prev = op.Prev;
        newOp.Prev.Next = newOp;
        op.Prev = newOp;
        if (ToFront) outRec.Pts = newOp;
        return newOp;
    }
}

14.5.1 添加点的两种情况

情况1: 新建输出记录
  边 e 没有关联的 OutRec
  → 创建新 OutRec
  → 创建第一个 OutPt
  → 形成单点的循环链表

情况2: 添加到已有记录
  边 e 已有关联的 OutRec
  → 根据边的 Side 决定添加到头部还是尾部
  → 插入新 OutPt 到链表

14.5.2 ToFront 判断

Side == esLeft  → 添加到前面(Pts)
Side == esRight → 添加到后面(Pts.Prev)

      Pts
       │
       ▼
  ●───►●───►●───►●
  │                 │
  └────◄────◄────◄─┘
              ▲
              │
          Pts.Prev

14.6 AddLocalMinPoly

在局部极小值处创建新的输出多边形:

private OutPt AddLocalMinPoly(TEdge e1, TEdge e2, IntPoint pt)
{
    OutPt result;
    TEdge e, prevE;
    
    if (IsHorizontal(e2) || (e1.Dx > e2.Dx))
    {
        result = AddOutPt(e1, pt);
        e2.OutIdx = e1.OutIdx;
        e1.Side = EdgeSide.esLeft;
        e2.Side = EdgeSide.esRight;
        e = e1;
        if (e.PrevInAEL == e2)
            prevE = e2.PrevInAEL; 
        else
            prevE = e.PrevInAEL;
    }
    else
    {
        result = AddOutPt(e2, pt);
        e1.OutIdx = e2.OutIdx;
        e1.Side = EdgeSide.esRight;
        e2.Side = EdgeSide.esLeft;
        e = e2;
        if (e.PrevInAEL == e1)
            prevE = e1.PrevInAEL;
        else
            prevE = e.PrevInAEL;
    }

    if (prevE != null && prevE.OutIdx >= 0 && prevE.Top.Y < pt.Y && e.Top.Y < pt.Y)
    {
        cInt xPrev = TopX(prevE, pt.Y);
        cInt xE = TopX(e, pt.Y);
        if ((xPrev == xE) && (e.WindDelta != 0) && (prevE.WindDelta != 0) &&
            SlopesEqual(new IntPoint(xPrev, pt.Y), prevE.Top, 
                       new IntPoint(xE, pt.Y), e.Top, m_UseFullRange))
        {
            OutPt outPt = AddOutPt(prevE, pt);
            AddJoin(result, outPt, e.Top);
        }
    }
    return result;
}

14.6.1 局部极小值图解

          ╲        ╱
           ╲  pt  ╱
            ╲    ╱
         e1  ╲  ╱ e2
              ╲╱
              极小值点

创建 OutRec:
- e1 设为左边界(Side = esLeft)
- e2 设为右边界(Side = esRight)
- 两边共享同一个 OutIdx

14.7 AddLocalMaxPoly

在局部极大值处关闭输出多边形:

private void AddLocalMaxPoly(TEdge e1, TEdge e2, IntPoint pt)
{
    AddOutPt(e1, pt);
    
    if (e2.WindDelta == 0) 
        AddOutPt(e2, pt);
    
    if (e1.OutIdx == e2.OutIdx)
    {
        e1.OutIdx = Unassigned;
        e2.OutIdx = Unassigned;
    }
    else if (e1.OutIdx < e2.OutIdx) 
        AppendPolygon(e1, e2);
    else 
        AppendPolygon(e2, e1);
}

14.7.1 局部极大值图解

              ╱╲
             ╱  ╲
        e1  ╱    ╲ e2
           ╱  pt  ╲
          ╱        ╲
         极大值点

处理:
- 添加点 pt 到输出
- 如果两边属于同一输出,关闭多边形
- 如果属于不同输出,合并它们

14.8 AppendPolygon

合并两个输出多边形:

private void AppendPolygon(TEdge e1, TEdge e2)
{
    OutRec outRec1 = m_PolyOuts[e1.OutIdx];
    OutRec outRec2 = m_PolyOuts[e2.OutIdx];

    OutRec holeStateRec;
    if (OutRec1RightOfOutRec2(outRec1, outRec2)) 
        holeStateRec = outRec2;
    else if (OutRec1RightOfOutRec2(outRec2, outRec1)) 
        holeStateRec = outRec1;
    else 
        holeStateRec = GetLowermostRec(outRec1, outRec2);

    // 获取两个输出的点
    OutPt p1_lft = outRec1.Pts;
    OutPt p1_rt = p1_lft.Prev;
    OutPt p2_lft = outRec2.Pts;
    OutPt p2_rt = p2_lft.Prev;

    // 根据边的 Side 连接
    if (e1.Side == EdgeSide.esLeft)
    {
        if (e2.Side == EdgeSide.esLeft)
        {
            // 反转 p2 并连接
            ReversePolyPtLinks(p2_lft);
            p2_lft.Next = p1_lft;
            p1_lft.Prev = p2_lft;
            p1_rt.Next = p2_rt;
            p2_rt.Prev = p1_rt;
            outRec1.Pts = p2_rt;
        }
        else
        {
            // 直接连接
            p2_rt.Next = p1_lft;
            p1_lft.Prev = p2_rt;
            p2_lft.Prev = p1_rt;
            p1_rt.Next = p2_lft;
            outRec1.Pts = p2_lft;
        }
    }
    else
    {
        if (e2.Side == EdgeSide.esRight)
        {
            ReversePolyPtLinks(p2_lft);
            p1_rt.Next = p2_rt;
            p2_rt.Prev = p1_rt;
            p2_lft.Next = p1_lft;
            p1_lft.Prev = p2_lft;
        }
        else
        {
            p1_rt.Next = p2_lft;
            p2_lft.Prev = p1_rt;
            p1_lft.Prev = p2_rt;
            p2_rt.Next = p1_lft;
        }
    }

    outRec1.BottomPt = null;
    
    // 更新孔洞状态
    if (holeStateRec == outRec2)
    {
        if (outRec2.FirstLeft != outRec1)
            outRec1.FirstLeft = outRec2.FirstLeft;
        outRec1.IsHole = outRec2.IsHole;
    }
    
    // 清理 outRec2
    outRec2.Pts = null;
    outRec2.BottomPt = null;
    outRec2.FirstLeft = outRec1;

    int OKIdx = e1.OutIdx;
    int ObsoleteIdx = e2.OutIdx;

    e1.OutIdx = Unassigned;
    e2.OutIdx = Unassigned;

    // 更新所有引用 ObsoleteIdx 的边
    TEdge e = m_ActiveEdges;
    while (e != null)
    {
        if (e.OutIdx == ObsoleteIdx)
        {
            e.OutIdx = OKIdx;
            e.Side = e1.Side;
        }
        e = e.NextInAEL;
    }
    
    outRec2.Idx = outRec1.Idx;
}

反转点链表的方向:

private void ReversePolyPtLinks(OutPt pp)
{
    if (pp == null) return;
    OutPt pp1;
    OutPt pp2;
    pp1 = pp;
    do
    {
        pp2 = pp1.Next;
        pp1.Next = pp1.Prev;
        pp1.Prev = pp2;
        pp1 = pp2;
    } while (pp1 != pp);
}

14.10 FixupOutPolygon

清理输出多边形(移除重复点和共线边):

private void FixupOutPolygon(OutRec outRec)
{
    OutPt lastOK = null;
    outRec.BottomPt = null;
    OutPt pp = outRec.Pts;
    bool preserveCol = PreserveCollinear || StrictlySimple;
    
    for (;;)
    {
        if (pp.Prev == pp || pp.Prev == pp.Next)
        {
            outRec.Pts = null;
            return;
        }
        
        // 检查重复点和共线边
        if ((pp.Pt == pp.Next.Pt) || (pp.Pt == pp.Prev.Pt) ||
            (SlopesEqual(pp.Prev.Pt, pp.Pt, pp.Next.Pt, m_UseFullRange) &&
            (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp.Prev.Pt, pp.Pt, pp.Next.Pt))))
        {
            lastOK = null;
            // 移除当前点
            pp.Prev.Next = pp.Next;
            pp.Next.Prev = pp.Prev;
            pp = pp.Prev;
        }
        else if (pp == lastOK) 
            break;
        else
        {
            if (lastOK == null) lastOK = pp;
            pp = pp.Next;
        }
    }
    outRec.Pts = pp;
}

14.11 GetOutRec

获取最终的输出记录(处理合并情况):

private OutRec GetOutRec(int idx)
{
    OutRec outrec = m_PolyOuts[idx];
    while (outrec != m_PolyOuts[outrec.Idx])
        outrec = m_PolyOuts[outrec.Idx];
    return outrec;
}

14.12 UpdateOutPtIdxs

更新输出点的索引:

private void UpdateOutPtIdxs(OutRec outrec)
{  
    OutPt op = outrec.Pts;
    do
    {
        op.Idx = outrec.Idx;
        op = op.Prev;
    }
    while (op != outrec.Pts);
}

14.13 PointCount

计算输出多边形的点数:

private int PointCount(OutPt pts)
{
    if (pts == null) return 0;
    int result = 0;
    OutPt p = pts;
    do
    {
        result++;
        p = p.Next;
    }
    while (p != pts);
    return result;
}

14.14 输出构建流程

1. 局部极小值 → AddLocalMinPoly
   创建新的 OutRec 和初始点

2. 边处理 → AddOutPt
   逐步添加点到输出多边形

3. 局部极大值 → AddLocalMaxPoly
   关闭多边形或合并多边形

4. 后处理 → FixupOutPolygon
   清理重复点和共线边

5. 构建结果 → BuildResult/BuildResult2
   转换为最终的 Paths 或 PolyTree

14.15 本章小结

本章详细分析了 Clipper 的输出多边形构建:

  1. 数据结构

    • OutRec:输出记录
    • OutPt:输出点链表
  2. 核心方法

    • AddOutPt:添加点
    • AddLocalMinPoly:极小值创建
    • AddLocalMaxPoly:极大值关闭
    • AppendPolygon:合并多边形
  3. 后处理

    • FixupOutPolygon:清理输出
    • BuildResult:构建最终结果

理解输出构建对于理解 Clipper 的完整工作流程至关重要。


上一章:水平边处理 | 返回目录 | 下一章:孔洞检测与处理