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

推荐订阅源

A
About on SuperTechFans
Cloudbric
Cloudbric
C
CERT Recently Published Vulnerability Notes
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
C
Cisco Blogs
T
Tenable Blog
P
Privacy International News Feed
T
The Exploit Database - CXSecurity.com
I
Intezer
AWS News Blog
AWS News Blog
IT之家
IT之家
博客园 - 司徒正美
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Spread Privacy
Spread Privacy
S
SegmentFault 最新的问题
博客园 - Franky
人人都是产品经理
人人都是产品经理
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
V
Visual Studio Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
H
Hacker News: Front Page
Latest news
Latest news
Scott Helme
Scott Helme
腾讯CDC
宝玉的分享
宝玉的分享
大猫的无限游戏
大猫的无限游戏
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
Arctic Wolf
S
Securelist
雷峰网
雷峰网
The GitHub Blog
The GitHub Blog
Project Zero
Project Zero
Google DeepMind News
Google DeepMind News
P
Palo Alto Networks Blog
F
Fortinet All Blogs
Schneier on Security
Schneier on Security
云风的 BLOG
云风的 BLOG
Security Archives - TechRepublic
Security Archives - TechRepublic
The Last Watchdog
The Last Watchdog
WordPress大学
WordPress大学
MongoDB | Blog
MongoDB | Blog
L
LINUX DO - 最新话题
S
Schneier on Security
NISL@THU
NISL@THU
Jina AI
Jina AI
M
MIT News - Artificial intelligence

博客园 - 我才是银古

第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章:二次开发实战与综合案例 第17章:与 Qt VTK Python pythonOCC 生态集成 第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章:实际应用与最佳实践 第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
第18章:Minkowski 和与差
我才是银古 · 2026-04-11 · via 博客园 - 我才是银古

第18章:Minkowski 和与差

18.1 概述

Minkowski 和与差是两种重要的几何运算,在碰撞检测、运动规划、膨胀/腐蚀等领域有广泛应用。Clipper2 提供了这些运算的实现。

18.2 数学定义

18.2.1 Minkowski 和

给定两个点集 A 和 B,它们的 Minkowski 和定义为:

A ⊕ B = { a + b | a ∈ A, b ∈ B }

即 A 中的每个点与 B 中的每个点相加得到的所有点的集合。

18.2.2 Minkowski 差

A ⊖ B = { a - b | a ∈ A, b ∈ B }
     = A ⊕ (-B)

即 A 与 B 的反射的 Minkowski 和。

18.2.3 几何意义

Minkowski 和的几何意义:
将形状 B 的中心沿着形状 A 的边界移动,B 扫过的区域

       A              B             A ⊕ B
    ┌─────┐         ┌──┐         ┌───────┐
    │     │    ⊕    │  │    =    │       │
    │     │         └──┘         │       │
    └─────┘                      │       │
                                 └───────┘
                                 (圆角化)

18.3 Clipper2 中的实现

18.3.1 MinkowskiSum 方法

public static Paths64 MinkowskiSum(Path64 pattern, Path64 path, bool isClosed)
{
    return Minkowski(pattern, path, true, isClosed);
}

public static PathsD MinkowskiSum(PathD pattern, PathD path, bool isClosed, 
    int precision = 2)
{
    InternalClipper.CheckPrecision(precision);
    double scale = Math.Pow(10, precision);
    
    Path64 pattern64 = Clipper.ScalePath64(pattern, scale);
    Path64 path64 = Clipper.ScalePath64(path, scale);
    
    Paths64 result64 = Minkowski(pattern64, path64, true, isClosed);
    
    return Clipper.ScalePathsD(result64, 1.0 / scale);
}

18.3.2 MinkowskiDiff 方法

public static Paths64 MinkowskiDiff(Path64 pattern, Path64 path, bool isClosed)
{
    return Minkowski(pattern, path, false, isClosed);
}

18.3.3 Minkowski 核心实现

private static Paths64 Minkowski(Path64 pattern, Path64 path, 
    bool isSum, bool isClosed)
{
    int patternCnt = pattern.Count;
    int pathCnt = path.Count;
    
    if (patternCnt == 0 || pathCnt == 0) return new Paths64();
    
    // 如果是差集,反转 pattern
    Path64 pat = isSum ? pattern : ReversePath(pattern);
    
    // 计算所有边的 Minkowski 结果
    Paths64 result = new Paths64();
    
    if (isClosed)
    {
        // 闭合路径
        for (int i = 0; i < pathCnt; i++)
        {
            Path64 quad = TranslatePath(pat, path[i]);
            result.Add(quad);
        }
    }
    else
    {
        // 开放路径
        for (int i = 0; i < pathCnt - 1; i++)
        {
            Path64 quad = TranslatePath(pat, path[i]);
            result.Add(quad);
        }
        
        // 最后一点
        Path64 lastQuad = TranslatePath(pat, path[pathCnt - 1]);
        result.Add(lastQuad);
    }
    
    // 使用裁剪器合并所有结果
    Clipper64 clipper = new Clipper64();
    clipper.AddSubject(result);
    
    Paths64 solution = new Paths64();
    clipper.Execute(ClipType.Union, FillRule.NonZero, solution);
    
    return solution;
}

18.4 TranslatePath

18.4.1 实现

private static Path64 TranslatePath(Path64 path, Point64 delta)
{
    Path64 result = new Path64(path.Count);
    
    foreach (Point64 pt in path)
    {
        result.Add(new Point64(pt.X + delta.X, pt.Y + delta.Y));
    }
    
    return result;
}

18.4.2 作用示意

原始 pattern:        平移到 path[i]:
    ○──○                    ○──○
    │  │      + (dx, dy) =  │  │
    ○──○                    ○──○
                              ↑
                        位于 path[i] 位置

18.5 详细算法

18.5.1 凸多边形 Minkowski 和

对于凸多边形,有更高效的算法:

private static Path64 ConvexMinkowskiSum(Path64 a, Path64 b)
{
    // 确保都是逆时针
    if (!IsPositive(a)) a = ReversePath(a);
    if (!IsPositive(b)) b = ReversePath(b);
    
    // 合并边的旋转角
    int i = IndexOfLowestPoint(a);
    int j = IndexOfLowestPoint(b);
    
    int lenA = a.Count;
    int lenB = b.Count;
    
    Path64 result = new Path64(lenA + lenB);
    
    int iEnd = i + lenA;
    int jEnd = j + lenB;
    
    while (i < iEnd || j < jEnd)
    {
        // 添加当前点
        result.Add(new Point64(
            a[i % lenA].X + b[j % lenB].X,
            a[i % lenA].Y + b[j % lenB].Y
        ));
        
        // 比较边的角度,选择较小的前进
        double angleA = EdgeAngle(a, i % lenA);
        double angleB = EdgeAngle(b, j % lenB);
        
        if (angleA < angleB)
            i++;
        else if (angleB < angleA)
            j++;
        else
        {
            i++;
            j++;
        }
    }
    
    return result;
}

18.5.2 通用算法

对于非凸多边形,使用分解方法:

private static Paths64 GeneralMinkowskiSum(Path64 pattern, Path64 path)
{
    Paths64 result = new Paths64();
    
    int pathLen = path.Count;
    int patternLen = pattern.Count;
    
    // 对于路径的每条边
    for (int i = 0; i < pathLen; i++)
    {
        int j = (i + 1) % pathLen;
        
        // 创建边对应的四边形
        Path64 quad = new Path64(patternLen * 2);
        
        // 沿着 pattern 平移
        for (int k = 0; k < patternLen; k++)
        {
            quad.Add(new Point64(
                path[i].X + pattern[k].X,
                path[i].Y + pattern[k].Y
            ));
        }
        
        for (int k = patternLen - 1; k >= 0; k--)
        {
            quad.Add(new Point64(
                path[j].X + pattern[k].X,
                path[j].Y + pattern[k].Y
            ));
        }
        
        result.Add(quad);
    }
    
    // 合并所有四边形
    return Clipper.Union(result, FillRule.NonZero);
}

18.6 应用场景

18.6.1 碰撞检测

// 检测两个多边形是否碰撞
bool CheckCollision(Path64 polyA, Path64 polyB)
{
    // 计算 Minkowski 差
    Paths64 diff = Clipper.MinkowskiDiff(polyA, polyB, true);
    
    // 如果原点在差集内,则碰撞
    Point64 origin = new Point64(0, 0);
    
    foreach (Path64 path in diff)
    {
        if (Clipper.PointInPolygon(origin, path) != 
            PointInPolygonResult.IsOutside)
        {
            return true;  // 碰撞
        }
    }
    
    return false;  // 无碰撞
}

18.6.2 机器人运动规划

// 计算机器人可以移动的空间
Paths64 ComputeConfigurationSpace(Path64 robot, Paths64 obstacles)
{
    // 机器人围绕参考点(通常是中心)
    Path64 robotCentered = CenterPath(robot);
    
    Paths64 expandedObstacles = new Paths64();
    
    foreach (Path64 obstacle in obstacles)
    {
        // 每个障碍物膨胀为 Minkowski 和
        Paths64 expanded = Clipper.MinkowskiSum(
            robotCentered, obstacle, true);
        expandedObstacles.AddRange(expanded);
    }
    
    // 合并所有膨胀后的障碍物
    return Clipper.Union(expandedObstacles, FillRule.NonZero);
}

18.6.3 形态学操作

// 膨胀操作
Paths64 Dilate(Path64 shape, Path64 structuringElement)
{
    // 膨胀 = Minkowski 和
    return Clipper.MinkowskiSum(structuringElement, shape, true);
}

// 腐蚀操作
Paths64 Erode(Path64 shape, Path64 structuringElement)
{
    // 腐蚀 = Minkowski 差(的边界内部)
    // 需要更复杂的处理...
    Path64 reflected = ReflectPath(structuringElement);
    Paths64 diff = Clipper.MinkowskiDiff(shape, reflected, true);
    return diff;
}

18.7 性能优化

18.7.1 简化 pattern

// 减少 pattern 的点数可以提高性能
Path64 SimplifyPattern(Path64 pattern, double tolerance)
{
    return Clipper.SimplifyPath(pattern, tolerance);
}

18.7.2 凸壳优化

// 如果只需要外轮廓,可以使用凸壳
Path64 ConvexHullMinkowski(Path64 pattern, Path64 path)
{
    // 对于凸多边形,Minkowski 和的结果也是凸的
    Path64 hullA = Clipper.ConvexHull(pattern);
    Path64 hullB = Clipper.ConvexHull(path);
    
    return ConvexMinkowskiSum(hullA, hullB);
}

18.7.3 分而治之

// 对于大型路径,可以分段处理
Paths64 MinkowskiSumLarge(Path64 pattern, Path64 path)
{
    const int chunkSize = 100;
    
    if (path.Count <= chunkSize)
    {
        return Clipper.MinkowskiSum(pattern, path, true);
    }
    
    Paths64 result = new Paths64();
    
    for (int i = 0; i < path.Count; i += chunkSize)
    {
        int end = Math.Min(i + chunkSize + 1, path.Count);
        Path64 chunk = path.GetRange(i, end - i);
        
        Paths64 chunkResult = Clipper.MinkowskiSum(pattern, chunk, false);
        result.AddRange(chunkResult);
    }
    
    return Clipper.Union(result, FillRule.NonZero);
}

18.8 使用示例

18.8.1 基本 Minkowski 和

// 正方形 pattern
Path64 square = new Path64 {
    new Point64(-10, -10),
    new Point64(10, -10),
    new Point64(10, 10),
    new Point64(-10, 10)
};

// 三角形路径
Path64 triangle = new Path64 {
    new Point64(0, 0),
    new Point64(100, 0),
    new Point64(50, 100)
};

// 计算 Minkowski 和
Paths64 result = Clipper.MinkowskiSum(square, triangle, true);

// 结果是三角形"膨胀"了正方形的大小

18.8.2 Minkowski 差用于碰撞

Path64 movingObject = CreateRectangle(0, 0, 20, 20);
Path64 obstacle = CreateRectangle(50, 50, 30, 30);

// 计算 Minkowski 差
Paths64 diff = Clipper.MinkowskiDiff(obstacle, movingObject, true);

// 检查移动目标位置是否碰撞
Point64 targetPosition = new Point64(40, 40);
bool willCollide = IsPointInPaths(targetPosition, diff);

18.8.3 浮点版本

PathD circleApprox = CreateCircleApprox(0, 0, 5.0, 32);
PathD complexPath = LoadPathFromFile("path.dat");

// 使用浮点计算
PathsD result = Clipper.MinkowskiSum(circleApprox, complexPath, true, 3);

18.9 注意事项

18.9.1 路径方向

// Minkowski 和要求路径是逆时针的
// 确保方向正确
if (!Clipper.IsPositive(pattern))
    pattern = Clipper.ReversePath(pattern);

if (!Clipper.IsPositive(path))
    path = Clipper.ReversePath(path);

18.9.2 自相交处理

// Minkowski 和可能产生自相交
// 结果通过 Union 自动清理
Paths64 raw = MinkowskiSumRaw(pattern, path);
Paths64 clean = Clipper.Union(raw, FillRule.NonZero);

18.9.3 性能考量

时间复杂度:O(n * m + k log k)
- n = pattern 点数
- m = path 点数
- k = 结果点数

空间复杂度:O(n * m)

18.10 本章小结

Minkowski 和与差是强大的几何运算:

  1. Minkowski 和:形状膨胀、扫描区域
  2. Minkowski 差:碰撞检测、穿透深度
  3. 应用广泛:机器人、游戏、CAD
  4. 实现方式:分解为平移 + 并集
  5. 优化方法:凸壳、分段处理

正确使用这些运算可以解决许多实际问题。


上一章:RectClip矩形裁剪优化 | 返回目录 | 下一章:PolyTree多边形树结构