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

推荐订阅源

T
Tenable Blog
D
DataBreaches.Net
S
Secure Thoughts
B
Blog
S
Schneier on Security
Y
Y Combinator Blog
P
Proofpoint News Feed
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cyber Attacks, Cyber Crime and Cyber Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Full Disclosure
Engineering at Meta
Engineering at Meta
L
LangChain Blog
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Hacker News
The Hacker News
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
Cyberwarzone
Cyberwarzone
T
The Exploit Database - CXSecurity.com
雷峰网
雷峰网
博客园 - 司徒正美
Help Net Security
Help Net Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Application and Cybersecurity Blog
Application and Cybersecurity Blog
P
Privacy International News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Vulnerabilities – Threatpost
Cisco Talos Blog
Cisco Talos Blog
L
LINUX DO - 热门话题
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Threat Research - Cisco Blogs
Recent Announcements
Recent Announcements
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
H
Heimdal Security Blog
Jina AI
Jina AI
C
Cisco Blogs
Attack and Defense Labs
Attack and Defense Labs
Microsoft Security Blog
Microsoft Security Blog
L
Lohrmann on Cybersecurity
aimingoo的专栏
aimingoo的专栏
D
Docker
I
Intezer
C
Check Point Blog
Cloudbric
Cloudbric
小众软件
小众软件
V2EX - 技术
V2EX - 技术

博客园 - 我才是银古

第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
第12章:交点计算与处理
我才是银古 · 2026-06-24 · via 博客园 - 我才是银古

第12章:交点计算与处理

12.1 概述

在扫描线向上移动过程中,AEL 中相邻的边可能会交叉。正确计算和处理这些交点是 Clipper 算法的关键部分。本章将深入分析交点的计算、排序和处理机制。

12.2 IntersectNode 结构

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

12.3 ProcessIntersections 主方法

private bool ProcessIntersections(cInt topY)
{
    if (m_ActiveEdges == null) return true;
    
    try 
    {
        BuildIntersectList(topY);
        
        if (m_IntersectList.Count == 0) return true;
        
        if (m_IntersectList.Count == 1 || FixupIntersectionOrder()) 
            ProcessIntersectList();
        else 
            return false;
    }
    catch 
    {
        m_SortedEdges = null;
        m_IntersectList.Clear();
        throw new ClipperException("ProcessIntersections error");
    }
    
    m_SortedEdges = null;
    return true;
}

12.3.1 处理流程

ProcessIntersections(topY)
        │
        ▼
BuildIntersectList(topY)
        │
        ▼
   交点列表是否为空?
        │
  是    │    否
 ───────┴───────
   │           │
   ▼           ▼
返回true   FixupIntersectionOrder()
               │
               ▼
        ProcessIntersectList()
               │
               ▼
          返回 true

12.4 BuildIntersectList

构建交点列表:

private void BuildIntersectList(cInt topY)
{
    if (m_ActiveEdges == null) return;

    // 准备排序:复制 AEL 到 SEL
    TEdge e = m_ActiveEdges;
    m_SortedEdges = e;
    while (e != null)
    {
        e.PrevInSEL = e.PrevInAEL;
        e.NextInSEL = e.NextInAEL;
        e.Curr.X = TopX(e, topY);  // 计算在 topY 处的 X 坐标
        e = e.NextInAEL;
    }

    // 冒泡排序检测交点
    bool isModified = true;
    while (isModified && m_SortedEdges != null)
    {
        isModified = false;
        e = m_SortedEdges;
        
        while (e.NextInSEL != null)
        {
            TEdge eNext = e.NextInSEL;
            IntPoint pt;
            
            if (e.Curr.X > eNext.Curr.X)
            {
                // e 和 eNext 在 topY 处交叉了
                IntersectPoint(e, eNext, out pt);
                
                // 确保交点在有效范围内
                if (pt.Y < topY)
                    pt = new IntPoint(TopX(e, topY), topY);
                
                // 创建交点节点
                IntersectNode newNode = new IntersectNode();
                newNode.Edge1 = e;
                newNode.Edge2 = eNext;
                newNode.Pt = pt;
                m_IntersectList.Add(newNode);

                // 交换位置
                SwapPositionsInSEL(e, eNext);
                isModified = true;
            }
            else
                e = eNext;
        }
        
        if (e.PrevInSEL != null) 
            e.PrevInSEL.NextInSEL = null;
        else 
            break;
    }
    m_SortedEdges = null;
}

12.4.1 冒泡排序检测交点

初始 AEL/SEL(按 botY 的 X 排序):
e1(X=10) → e2(X=30) → e3(X=50) → e4(X=70)

在 topY 处的 X 坐标:
e1(X=40) → e2(X=20) → e3(X=60) → e4(X=45)

排序过程(冒泡):
Pass 1: e1 > e2? 40 > 20 → 是,交换并记录交点
        e2(X=20) → e1(X=40) → e3(X=60) → e4(X=45)
        e1 > e3? 40 > 60 → 否
        e3 > e4? 60 > 45 → 是,交换并记录交点
        e2(X=20) → e1(X=40) → e4(X=45) → e3(X=60)

Pass 2: e2 > e1? 20 > 40 → 否
        e1 > e4? 40 > 45 → 否
        完成

交点列表:
- (e1, e2) 交点
- (e3, e4) 交点

12.5 IntersectPoint

计算两条边的交点:

private void IntersectPoint(TEdge edge1, TEdge edge2, out IntPoint ip)
{
    ip = new IntPoint();
    double b1, b2;
    
    // 斜率相同的边(平行或重合)
    if (edge1.Dx == edge2.Dx)
    {
        ip.Y = edge1.Curr.Y;
        ip.X = TopX(edge1, ip.Y);
        return;
    }

    // edge1 垂直
    if (edge1.Delta.X == 0)
    {
        ip.X = edge1.Bot.X;
        if (IsHorizontal(edge2))
        {
            ip.Y = edge2.Bot.Y;
        }
        else
        {
            b2 = edge2.Bot.Y - (edge2.Bot.X / edge2.Dx);
            ip.Y = Round(ip.X / edge2.Dx + b2);
        }
    }
    // edge2 垂直
    else if (edge2.Delta.X == 0)
    {
        ip.X = edge2.Bot.X;
        if (IsHorizontal(edge1))
        {
            ip.Y = edge1.Bot.Y;
        }
        else
        {
            b1 = edge1.Bot.Y - (edge1.Bot.X / edge1.Dx);
            ip.Y = Round(ip.X / edge1.Dx + b1);
        }
    }
    // 一般情况
    else
    {
        b1 = edge1.Bot.X - edge1.Bot.Y * edge1.Dx;
        b2 = edge2.Bot.X - edge2.Bot.Y * edge2.Dx;
        double q = (b2 - b1) / (edge1.Dx - edge2.Dx);
        ip.Y = Round(q);
        
        if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx))
            ip.X = Round(edge1.Dx * q + b1);
        else
            ip.X = Round(edge2.Dx * q + b2);
    }

    // 确保交点在两条边的有效范围内
    if (ip.Y < edge1.Top.Y || ip.Y < edge2.Top.Y)
    {
        if (edge1.Top.Y > edge2.Top.Y)
            ip.Y = edge1.Top.Y;
        else
            ip.Y = edge2.Top.Y;
        
        if (Math.Abs(edge1.Dx) < Math.Abs(edge2.Dx))
            ip.X = TopX(edge1, ip.Y);
        else
            ip.X = TopX(edge2, ip.Y);
    }
    
    // 确保交点不低于当前扫描线
    if (ip.Y > edge1.Curr.Y)
    {
        ip.Y = edge1.Curr.Y;
        if (Math.Abs(edge1.Dx) > Math.Abs(edge2.Dx)) 
            ip.X = TopX(edge2, ip.Y);
        else 
            ip.X = TopX(edge1, ip.Y);
    }
}

12.5.1 数学原理

两条非平行直线的交点计算:

线1: y = (x - b1) / Dx1  →  x = Dx1 * y + b1
线2: y = (x - b2) / Dx2  →  x = Dx2 * y + b2

交点:
Dx1 * y + b1 = Dx2 * y + b2
(Dx1 - Dx2) * y = b2 - b1
y = (b2 - b1) / (Dx1 - Dx2)

其中 b = Bot.X - Bot.Y * Dx

12.6 FixupIntersectionOrder

修正交点顺序以确保边是相邻的:

private bool FixupIntersectionOrder()
{
    // 前提:交点按 Y 降序排列(最底部的在前)
    m_IntersectList.Sort(m_IntersectNodeComparer);

    CopyAELToSEL();
    int cnt = m_IntersectList.Count;
    
    for (int i = 0; i < cnt; i++)
    {
        if (!EdgesAdjacent(m_IntersectList[i]))
        {
            // 交点的边不相邻,需要调整顺序
            int j = i + 1;
            while (j < cnt && !EdgesAdjacent(m_IntersectList[j])) 
                j++;
            
            if (j == cnt) return false;

            // 交换位置
            IntersectNode tmp = m_IntersectList[i];
            m_IntersectList[i] = m_IntersectList[j];
            m_IntersectList[j] = tmp;
        }
        
        SwapPositionsInSEL(m_IntersectList[i].Edge1, m_IntersectList[i].Edge2);
    }
    
    return true;
}

12.6.1 EdgesAdjacent

private bool EdgesAdjacent(IntersectNode inode)
{
    return (inode.Edge1.NextInSEL == inode.Edge2) ||
           (inode.Edge1.PrevInSEL == inode.Edge2);
}

12.6.2 IntersectNodeSort

private static int IntersectNodeSort(IntersectNode node1, IntersectNode node2)
{
    // 按 Y 降序排列(先处理较低的交点)
    return (int)(node2.Pt.Y - node1.Pt.Y); 
}

12.7 ProcessIntersectList

处理所有交点:

private void ProcessIntersectList()
{
    for (int i = 0; i < m_IntersectList.Count; i++)
    {
        IntersectNode iNode = m_IntersectList[i];
        {
            IntersectEdges(iNode.Edge1, iNode.Edge2, iNode.Pt);
            SwapPositionsInAEL(iNode.Edge1, iNode.Edge2);
        }
    }
    m_IntersectList.Clear();
}

12.7.1 处理单个交点

Before:
扫描线 ─────────────────────────
          │         │
         e1        e2
          \       /
           \     /
            \   /
             \ /
              × ← 交点

After:
          │         │
         e2        e1   ← AEL 中交换了位置
          /       \
         /         \

12.8 IntersectEdges

处理两条边的交点(核心方法):

private void IntersectEdges(TEdge e1, TEdge e2, IntPoint pt)
{
    bool e1Contributing = (e1.OutIdx >= 0);
    bool e2Contributing = (e2.OutIdx >= 0);

#if use_xyz
    SetZ(ref pt, e1, e2);
#endif

#if use_lines
    // 开放路径的交点处理
    if (e1.WindDelta == 0 || e2.WindDelta == 0)
    {
        // ... 开放路径处理逻辑 ...
        return;
    }
#endif

    // 更新缠绕数
    if (e1.PolyTyp == e2.PolyTyp)
    {
        // 同类型多边形的边交叉
        if (IsEvenOddFillType(e1))
        {
            int oldE1WindCnt = e1.WindCnt;
            e1.WindCnt = e2.WindCnt;
            e2.WindCnt = oldE1WindCnt;
        }
        else
        {
            if (e1.WindCnt + e2.WindDelta == 0) 
                e1.WindCnt = -e1.WindCnt;
            else 
                e1.WindCnt += e2.WindDelta;
            
            if (e2.WindCnt - e1.WindDelta == 0) 
                e2.WindCnt = -e2.WindCnt;
            else 
                e2.WindCnt -= e1.WindDelta;
        }
    }
    else
    {
        // 不同类型多边形的边交叉
        if (!IsEvenOddFillType(e2)) 
            e1.WindCnt2 += e2.WindDelta;
        else 
            e1.WindCnt2 = (e1.WindCnt2 == 0) ? 1 : 0;
        
        if (!IsEvenOddFillType(e1)) 
            e2.WindCnt2 -= e1.WindDelta;
        else 
            e2.WindCnt2 = (e2.WindCnt2 == 0) ? 1 : 0;
    }

    // 确定填充类型
    PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
    // ... 设置填充类型 ...

    // 计算有效的缠绕计数
    int e1Wc, e2Wc;
    switch (e1FillType)
    {
        case PolyFillType.pftPositive: e1Wc = e1.WindCnt; break;
        case PolyFillType.pftNegative: e1Wc = -e1.WindCnt; break;
        default: e1Wc = Math.Abs(e1.WindCnt); break;
    }
    switch (e2FillType)
    {
        case PolyFillType.pftPositive: e2Wc = e2.WindCnt; break;
        case PolyFillType.pftNegative: e2Wc = -e2.WindCnt; break;
        default: e2Wc = Math.Abs(e2.WindCnt); break;
    }

    // 处理输出多边形
    if (e1Contributing && e2Contributing)
    {
        if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
            (e1.PolyTyp != e2.PolyTyp && m_ClipType != ClipType.ctXor))
        {
            AddLocalMaxPoly(e1, e2, pt);
        }
        else
        {
            AddOutPt(e1, pt);
            AddOutPt(e2, pt);
            SwapSides(e1, e2);
            SwapPolyIndexes(e1, e2);
        }
    }
    else if (e1Contributing)
    {
        if (e2Wc == 0 || e2Wc == 1)
        {
            AddOutPt(e1, pt);
            SwapSides(e1, e2);
            SwapPolyIndexes(e1, e2);
        }
    }
    else if (e2Contributing)
    {
        if (e1Wc == 0 || e1Wc == 1)
        {
            AddOutPt(e2, pt);
            SwapSides(e1, e2);
            SwapPolyIndexes(e1, e2);
        }
    }
    else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1))
    {
        // 两条边都不在贡献中,可能开始新的输出
        cInt e1Wc2, e2Wc2;
        // ... 计算 WindCnt2 ...

        if (e1.PolyTyp != e2.PolyTyp)
        {
            AddLocalMinPoly(e1, e2, pt);
        }
        else if (e1Wc == 1 && e2Wc == 1)
        {
            switch (m_ClipType)
            {
                case ClipType.ctIntersection:
                    if (e1Wc2 > 0 && e2Wc2 > 0)
                        AddLocalMinPoly(e1, e2, pt);
                    break;
                case ClipType.ctUnion:
                    if (e1Wc2 <= 0 && e2Wc2 <= 0)
                        AddLocalMinPoly(e1, e2, pt);
                    break;
                case ClipType.ctDifference:
                    if (((e1.PolyTyp == PolyType.ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
                        ((e1.PolyTyp == PolyType.ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
                        AddLocalMinPoly(e1, e2, pt);
                    break;
                case ClipType.ctXor:
                    AddLocalMinPoly(e1, e2, pt);
                    break;
            }
        }
        else
            SwapSides(e1, e2);
    }
}

12.9 交点处理的四种情况

12.9.1 两边都在贡献

情况1: 形成局部极大值
      ╲    ╱
       ╲  ╱
        ╲╱  ← 交点(结束输出)
        ╱╲
       ╱  ╲

情况2: 边交换继续贡献
      ╲    ╱
       ╲  ╱
        ×  ← 交点(交换边,继续)
       ╱  ╲
      ╱    ╲

12.9.2 一边在贡献

      ╲    │
       ╲   │
        ×  ← 交点(输出点添加到贡献边)
       ╱   │
      ╱    │

12.9.3 两边都不在贡献

      │    │
      │    │
      ×  ← 交点(可能开始新输出)
      │    │
      │    │

12.10 SwapSides 和 SwapPolyIndexes

private static void SwapSides(TEdge edge1, TEdge edge2)
{
    EdgeSide side = edge1.Side;
    edge1.Side = edge2.Side;
    edge2.Side = side;
}

private static void SwapPolyIndexes(TEdge edge1, TEdge edge2)
{
    int outIdx = edge1.OutIdx;
    edge1.OutIdx = edge2.OutIdx;
    edge2.OutIdx = outIdx;
}

12.11 本章小结

本章详细分析了 Clipper 的交点处理机制:

  1. 交点检测

    • BuildIntersectList 使用冒泡排序检测
    • 比较边在 topY 处的 X 坐标
  2. 交点计算

    • IntersectPoint 处理各种边的情况
    • 处理垂直边、水平边、一般情况
  3. 交点排序

    • 按 Y 坐标降序
    • FixupIntersectionOrder 确保边相邻
  4. 交点处理

    • IntersectEdges 更新缠绕数
    • 根据贡献状态处理输出
  5. 四种情况

    • 两边贡献:极大值或交换
    • 一边贡献:添加输出点
    • 两边不贡献:可能开始新输出

上一章:活动边表管理 | 返回目录 | 下一章:水平边处理