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

推荐订阅源

T
Threatpost
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
The Blog of Author Tim Ferriss
S
SegmentFault 最新的问题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
T
Tailwind CSS Blog
The Cloudflare Blog
The Last Watchdog
The Last Watchdog
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
TaoSecurity Blog
TaoSecurity Blog
云风的 BLOG
云风的 BLOG
C
Cybersecurity and Infrastructure Security Agency CISA
O
OpenAI News
Recorded Future
Recorded Future
GbyAI
GbyAI
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Y
Y Combinator Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
量子位
博客园 - 叶小钗
V
Vulnerabilities – Threatpost
F
Full Disclosure
Recent Announcements
Recent Announcements
Vercel News
Vercel News
S
Schneier on Security
H
Heimdal Security Blog
Cisco Talos Blog
Cisco Talos Blog
V2EX - 技术
V2EX - 技术
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
G
Google Developers Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
爱范儿
爱范儿
IT之家
IT之家
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
N
Netflix TechBlog - Medium
S
Security @ Cisco Blogs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyberwarzone
Cyberwarzone

博客园 - 我才是银古

第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
第17章:ClipperOffset 偏移类详解
我才是银古 · 2026-06-24 · via 博客园 - 我才是银古

第17章:ClipperOffset 偏移类详解

17.1 概述

ClipperOffset 类用于对多边形进行偏移(扩张或收缩)操作。这在制造业(刀具补偿)、地图制作(缓冲区分析)、图形设计(描边效果)等领域有广泛应用。

17.2 类定义

public class ClipperOffset
{
    private Paths m_destPolys;
    private Path m_srcPoly;
    private Path m_destPoly;
    private List<DoublePoint> m_normals = new List<DoublePoint>();
    private double m_delta, m_sinA, m_sin, m_cos;
    private double m_miterLim, m_StepsPerRad;

    private IntPoint m_lowest;
    private PolyNode m_polyNodes = new PolyNode();

    public double ArcTolerance { get; set; }
    public double MiterLimit { get; set; }

    private const double two_pi = Math.PI * 2;
    private const double def_arc_tolerance = 0.25;
    
    // ...
}

17.3 成员变量详解

成员 类型 说明
m_destPolys Paths 目标多边形集合
m_srcPoly Path 当前处理的源多边形
m_destPoly Path 当前构建的目标多边形
m_normals List<DoublePoint> 边的单位法向量
m_delta double 偏移距离
m_sinA double 当前角度的正弦值
m_sin, m_cos double 圆弧步进的三角函数值
m_miterLim double 斜接限制因子
m_StepsPerRad double 每弧度的步数
m_lowest IntPoint 最低点位置
m_polyNodes PolyNode 输入多边形的节点树
ArcTolerance double 圆弧近似容差
MiterLimit double 斜接限制

17.4 构造函数

public ClipperOffset(
    double miterLimit = 2.0, double arcTolerance = def_arc_tolerance)
{
    MiterLimit = miterLimit;
    ArcTolerance = arcTolerance;
    m_lowest.X = -1;
}

17.4.1 MiterLimit

斜接限制控制尖角的最大延伸长度。

MiterLimit = 距离 / delta

低 MiterLimit (如 2):
    ╲
     ╲▁▁▁  ← 裁切
      ╲
       ╲

高 MiterLimit (如 10):
          ╱
         ╱
        ╱
       ╱
      ╱
     ╱ ← 长尖角

17.4.2 ArcTolerance

圆弧近似的精度控制。

高容差(粗糙):           低容差(平滑):
      ___                      ⌢⌣
    _/   \_                  ⌢    ⌣
   /       \                ⌢      ⌣
  |         |              |        |

17.5 Clear 方法

public void Clear()
{
    m_polyNodes.Childs.Clear();
    m_lowest.X = -1;
}

17.6 AddPath/AddPaths

public void AddPath(Path path, JoinType joinType, EndType endType)
{
    int highI = path.Count - 1;
    if (highI < 0) return;
    
    PolyNode newNode = new PolyNode();
    newNode.m_jointype = joinType;
    newNode.m_endtype = endType;

    // 移除重复点
    if (endType == EndType.etClosedLine || endType == EndType.etClosedPolygon)
        while (highI > 0 && path[0] == path[highI]) highI--;
    
    newNode.m_polygon.Capacity = highI + 1;
    newNode.m_polygon.Add(path[0]);
    
    int j = 0, k = 0;
    for (int i = 1; i <= highI; i++)
        if (newNode.m_polygon[j] != path[i])
        {
            j++;
            newNode.m_polygon.Add(path[i]);
            // 跟踪最低点
            if (path[i].Y > newNode.m_polygon[k].Y ||
                (path[i].Y == newNode.m_polygon[k].Y &&
                path[i].X < newNode.m_polygon[k].X)) 
                k = j;
        }
    
    if (endType == EndType.etClosedPolygon && j < 2) return;

    m_polyNodes.AddChild(newNode);

    // 更新全局最低点
    if (endType != EndType.etClosedPolygon) return;
    if (m_lowest.X < 0)
        m_lowest = new IntPoint(m_polyNodes.ChildCount - 1, k);
    else
    {
        IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X].m_polygon[(int)m_lowest.Y];
        if (newNode.m_polygon[k].Y > ip.Y ||
            (newNode.m_polygon[k].Y == ip.Y &&
            newNode.m_polygon[k].X < ip.X))
            m_lowest = new IntPoint(m_polyNodes.ChildCount - 1, k);
    }
}

public void AddPaths(Paths paths, JoinType joinType, EndType endType)
{
    foreach (Path p in paths)
        AddPath(p, joinType, endType);
}

17.7 JoinType 枚举

public enum JoinType { 
    jtSquare,  // 方形连接
    jtRound,   // 圆形连接
    jtMiter    // 斜接连接
}

17.7.1 Square(方形)

原始角:          偏移后:
    ╱              ┌──┐
   ╱               │  │
  ╱                │  │

在拐角处创建 45° 斜切边。

17.7.2 Round(圆形)

原始角:          偏移后:
    ╱              ╭──╮
   ╱               │  │
  ╱                │  │

在拐角处创建圆弧。

17.7.3 Miter(斜接)

原始角:          偏移后:
    ╱                ╱
   ╱              ╱
  ╱            ╱
             ╱

延伸边线直到相交。受 MiterLimit 限制。

17.8 EndType 枚举

public enum EndType { 
    etClosedPolygon,  // 闭合多边形
    etClosedLine,     // 闭合线
    etOpenButt,       // 开放-平头
    etOpenSquare,     // 开放-方头
    etOpenRound       // 开放-圆头
}

17.8.1 ClosedPolygon

输入:              输出:
┌──────┐          ╭────────╮
│      │          │        │
│      │    →     │        │
│      │          │        │
└──────┘          ╰────────╯

偏移闭合多边形的所有边。

17.8.2 ClosedLine

输入:              输出:
┌──────┐          ╭────╮╭────╮
│      │          │    ││    │
│      │    →     │    ││    │
│      │          │    ││    │
└──────┘          ╰────╯╰────╯

偏移闭合线的两侧,但不闭合末端。

17.8.3 OpenButt(平头)

输入:              输出:
────────          ┌────────┐
                  │        │
            →     │        │
                  │        │
                  └────────┘

末端垂直截断。

17.8.4 OpenSquare(方头)

输入:              输出:
────────         ┌──────────┐
                 │          │
           →     │          │
                 │          │
                 └──────────┘

末端延伸 delta 距离后截断。

17.8.5 OpenRound(圆头)

输入:              输出:
────────          ╭──────────╮
                  │          │
            →     │          │
                  │          │
                  ╰──────────╯

末端使用半圆。

17.9 Execute 方法

public void Execute(ref Paths solution, double delta)
{
    solution.Clear();
    FixOrientations();
    DoOffset(delta);
    
    // 使用 Clipper 清理自相交
    Clipper clpr = new Clipper();
    clpr.AddPaths(m_destPolys, PolyType.ptSubject, true);
    
    if (delta > 0)
    {
        clpr.Execute(ClipType.ctUnion, solution, 
            PolyFillType.pftPositive, PolyFillType.pftPositive);
    }
    else
    {
        IntRect r = ClipperBase.GetBounds(m_destPolys);
        Path outer = new Path(4);
        outer.Add(new IntPoint(r.left - 10, r.bottom + 10));
        outer.Add(new IntPoint(r.right + 10, r.bottom + 10));
        outer.Add(new IntPoint(r.right + 10, r.top - 10));
        outer.Add(new IntPoint(r.left - 10, r.top - 10));

        clpr.AddPath(outer, PolyType.ptSubject, true);
        clpr.ReverseSolution = true;
        clpr.Execute(ClipType.ctUnion, solution, 
            PolyFillType.pftNegative, PolyFillType.pftNegative);
        if (solution.Count > 0) solution.RemoveAt(0);
    }
}

public void Execute(ref PolyTree solution, double delta)
{
    solution.Clear();
    FixOrientations();
    DoOffset(delta);
    
    // 使用 Clipper 清理
    Clipper clpr = new Clipper();
    clpr.AddPaths(m_destPolys, PolyType.ptSubject, true);
    
    if (delta > 0)
    {
        clpr.Execute(ClipType.ctUnion, solution, 
            PolyFillType.pftPositive, PolyFillType.pftPositive);
    }
    else
    {
        // ... 负偏移处理 ...
    }
}

17.9.1 正偏移 vs 负偏移

正偏移 (delta > 0): 扩张
┌────┐        ╭──────╮
│    │   →    │      │
└────┘        ╰──────╯

负偏移 (delta < 0): 收缩
╭──────╮        ┌────┐
│      │   →    │    │
╰──────╯        └────┘

17.10 FixOrientations

确保多边形方向正确:

private void FixOrientations()
{
    if (m_lowest.X >= 0 && 
        !Clipper.Orientation(m_polyNodes.Childs[(int)m_lowest.X].m_polygon))
    {
        // 最低点的多边形方向错误,反转所有
        for (int i = 0; i < m_polyNodes.ChildCount; i++)
        {
            PolyNode node = m_polyNodes.Childs[i];
            if (node.m_endtype == EndType.etClosedPolygon ||
                (node.m_endtype == EndType.etClosedLine && 
                Clipper.Orientation(node.m_polygon)))
                node.m_polygon.Reverse();
        }
    }
    else
    {
        for (int i = 0; i < m_polyNodes.ChildCount; i++)
        {
            PolyNode node = m_polyNodes.Childs[i];
            if (node.m_endtype == EndType.etClosedLine &&
                !Clipper.Orientation(node.m_polygon))
                node.m_polygon.Reverse();
        }
    }
}

17.11 使用示例

17.11.1 基本偏移

// 创建正方形
Path square = new Path();
square.Add(new IntPoint(0, 0));
square.Add(new IntPoint(100, 0));
square.Add(new IntPoint(100, 100));
square.Add(new IntPoint(0, 100));

// 偏移操作
ClipperOffset co = new ClipperOffset();
co.AddPath(square, JoinType.jtRound, EndType.etClosedPolygon);

Paths solution = new Paths();
co.Execute(ref solution, 10);  // 扩张 10 单位

17.11.2 线段偏移

// 创建线段
Path line = new Path();
line.Add(new IntPoint(0, 0));
line.Add(new IntPoint(100, 0));
line.Add(new IntPoint(100, 100));

// 偏移成带宽度的路径
ClipperOffset co = new ClipperOffset();
co.AddPath(line, JoinType.jtRound, EndType.etOpenRound);

Paths solution = new Paths();
co.Execute(ref solution, 5);  // 宽度为 10

17.11.3 自定义参数

ClipperOffset co = new ClipperOffset(
    miterLimit: 4.0,      // 较大的斜接限制
    arcTolerance: 0.1     // 更平滑的圆弧
);

co.AddPath(polygon, JoinType.jtMiter, EndType.etClosedPolygon);

Paths solution = new Paths();
co.Execute(ref solution, 15);

17.12 本章小结

本章详细分析了 ClipperOffset 类:

  1. 类结构

    • 成员变量管理偏移状态
    • ArcTolerance 和 MiterLimit 控制质量
  2. 连接类型

    • Square:方形裁切
    • Round:圆弧过渡
    • Miter:尖角延伸
  3. 端点类型

    • ClosedPolygon/ClosedLine:闭合路径
    • OpenButt/OpenSquare/OpenRound:开放路径
  4. 执行流程

    • FixOrientations 确保正确方向
    • DoOffset 执行偏移计算
    • 使用 Clipper 清理自相交

上一章:填充规则详解 | 返回目录 | 下一章:偏移算法实现