ClipperLib库使用说明

前言

Clipper库是目前计算机图形届广为使用的图形处理库,可以用于解决平面二维图形的多边形简化、布尔运算和偏置处理,在CAD、加工路径与3D打印方面都有着比较重要的应用。

本文使用Love2.io驱动。

本文源文件来自Clipper库,英文版文档参见ClipperLib Overview

下载地址:https://download.csdn.net/download/hanfeidyx/11268643

概述

简介

Clipper Library(以下简称为Clipper库或者ClipperLib或Clipper)提供了对线段和多边形的裁剪(clipping)以及偏置(offseting)功能。

和其他裁剪库相比,Clipper具有以下特征:

  1. 它能够接受各类多边形输入,包含自交的多边形;
  2. 它支持多种填充原则(奇偶填充、非零填充、正填充、负填充);
  3. 它相较于其他库来说效率极高;
  4. 它数值稳定(鲁棒性强);
  5. 它支持多边形和线段的偏置;
  6. 它对于商用以及免费软件来说都是免费使用的。

术语:

  1. 裁剪(Clipping):通常是指在二维平面上把一个图形在指定的矩形框以外的部分去除掉。在更广义的角度,指定的裁剪范围不一定要是一个矩形,可以是各种各样的多边形,甚至是多个多边形;同样的,我们一般裁剪指的是形态学上的“求交”,但是在Clipper库中裁剪可以实现4种布尔运算(求交、求和、求异、求异或);
  2. 路径(Path):是指一些列有序的点的集合,用来定义一个轮廓(contour),这个轮廓既可以是代指一条线/开放路径(line/open path),也可以代指一个多边形/闭合轮廓(polygon/closed path)。
  3. 线(Line):与Polyline同义,代指一个具有两个以上点的开放路径。
  4. 轮廓(Contour):与路径同义;
  5. 孔洞/内轮廓(Hole):代指一个多边形内部表示该部分不属于该多边形的闭合区域;一个“内轮廓(Hole Polygon)”就是一个代指孔的外围点集的封闭路径;
  6. 孔洞/内轮廓(Hole):代指一个多边形内部表示该部分不属于该多边形的闭合区域;一个“内轮廓(Hole Polygon)”就是一个代指孔的外围点集的封闭路径;
  7. 多边形填充规则(Polygon Filling Rule):即在一系列的闭合路径中,来定义哪些属于“内部”,哪些属于“外部”

参考内容:

The Library is based on but significantly extends Bala Vatti’s polygon clipping algorithm as described in “A generic solution to polygon clipping”, Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.

A section in “Computer graphics and geometric modeling: implementation and algorithms” by By Max K. Agoston (Springer, 2005) discussing Vatti Polygon Clipping was also helpful in creating the initial Clipper implementation.

The paper titled “Polygon Offsetting by Computing Winding Numbers” by Chen & McMains (Paper no. DETC2005-85513, ASME 2005. Pages 565-575) contains helpful discussion on the complexities of polygon offsetting together with some solutions.

结构

预定义宏(Defines)

编译器预定义以下类型的宏:

  1. use_int32:当启用32位精度,效率提升,但是使用范围缩小;
  2. use_xyz:添加一个Z位数据,对性能影响很小;
  3. use_lines:启用开放路径的裁剪;如果该功能关闭(默认开启),大约有一个小于5%的性能的提升;
  4. use_deprecated:确保代码与6.0版本以前的内容相兼容;该功能未来会删除;

取整(Rounding)

通过使用一个整形数据,Clipper库已经能够避免因为数值稳定性造成的重大错误;关于数值取整问题和它们可能的解决办法将要在下面进行讨论;

首先需要强调,取整导致点会对他们的本来的理论坐标形成一定程度的偏移;但是,结果的不精确性是可以通过正确的缩放来进行避免的;

Clipper库自身支持可以缩放到一个相对较高精度,它所支持的整形坐标值范围在±0x3FFFFFFFFFFFFFFF (± 4.6e+18)之间。

另一个使用离散数据(相较于使用浮动类型数据)的隐患在于可能在极少数的情况下会造成小的自交情况。在没有缩放的左侧图片中(这里单位为1像素),两个多边形的相交部分被用亮绿色标出;

 

一个30倍放大的交点部分下部图片显示图该图其实有很小程度的自交情况。三个黑点标明了实际的交点情况(通过展示它们小数点部分);红色点显示在取整之后交点的情况。你很容易就会观察到取整让方向变反并且引起了一定程度的小的自交;

 

尽管这些小的自交是不常见的,如果这些被认为是有必要被考虑的,最好使用CleanPolygon的属性来对这种情况进行清除;(将Clipper对象的StrictlySimple属性设定为true同样会对这类自交产生影响但是小的多余的人为引起的错误方向的多边形仍然是不可避免的)

在最后的例子中,左侧的简单多边形仍然有轻微的自交。但是,在裁剪算法中处理时,算法将其当做仅仅是“触碰到”,而不是自交到右侧线段上去(虽然仅仅只有小于1个像素的部分);既然这样的自交一般不会被检测到,裁剪算法(例如求并运算)仍然会包含这样的小的自交。将Clipper库的StrictlySimple属性设定为true可以避免这一类情况;

 

 

数据类型

cInt

Del.»

{$IFDEF use_int32}

 cInt = Int32;

{$ELSE}

  cInt = Int64;

{$ENDIF}

 

C++ »

#ifdef use_int32

  typedef int cInt;

#else

  typedef signed long long cInt;

#endif

 

C#  »

#if use_int32

  using cInt = Int32;

#else

  using cInt = Int64;

#endif

 

cInt是Clipper库用来表示点坐标数据使用的。目前Clipper库使用整形数据代替浮点数据来保证数据的鲁棒性。(在一开始的版本中曾经使用Clipper库中的浮点数据,但是和明显浮点数的不精确性引起了一些偶然性错误);

默认的cInt代表了一个有符号64位整形数据并且整个多边形的坐标范围可以达到±9.2e+18的范围内。这容纳了整个浮点数坐标可以使其精度得到最完整的保留。但是,如果坐标范围可以被控制在 ± 3.0e+9之内,那么通过避免了大数的运算,可以得到大约百分之十的效率提升。如果预编译器定义了use_int32,则效率可以更高,详情见下方。

IntPoint

Del.» TIntPoint = record X, Y: cInt; end;

C++ » struct IntPoint { cInt X; cInt Y; ... };

C#  » public class IntPoint { public cInt X; { get; set; } public cInt Y; { get; set; } ... };

整形点数据结构用来代表Clipper库中相关的所有点;Clipper库刻意选择了整形存储类型类保证数值稳定。(同上文)

一系列的IntPoint被保存在Path结构中,构成了一个轮廓。

在版本6.0以上,IntPoint现在可以拥有第三个成员“Z”。这可以通过在预编译器中定义“use_xyz”来实现。当Z轴数据也被加入时,它的值也会被考虑进裁剪的行为中去;但是,在那些没有对应的Z值的交点处,该值会被设定为0,除非用户提供了回调函数;

用户如果希望使用浮点数进行Clip,那么必须手动的进行正确的对浮点数进行缩放为IntPoint,再使用Clipper数进行处理;

Path

这个结构包含一系列的整形数据点来定义一个单独的轮廓(可以参考术语部分);路径由两个以上的点数据组成,并且可以是开放的,当它为闭合的时候也可以代表多边形(Polygon);路径的开放与否是由其内容决定的。封闭的路径可能是“外轮廓”或者“内轮廓(内孔)”,这主要是由其方向(顺时针或者逆时针)决定的。

多个路径可以添加入Paths结构体形成一组路径;

Paths

由多个Path组成的基本类型;

可以包含开轮廓或闭合轮廓;

InitOptions

Del.»

Del.» type TInitOption = (ioReverseSolution, ioStrictlySimple, ioPreserveCollinear);

 

C++ » enum InitOptions {

        ioReverseSolution  = 1,

        ioStrictlySimple   = 2,

        ioPreserveCollinear = 4};

 

C#  » public const int ioReverseSolution  = 1;

      public const int ioStrictlySimple   = 2;

      public const int ioPreserveCollinear = 4;

IntRect

Del.»

TIntRect = record left, top, right, bottom: cInt; end;

 

C++ »

struct IntRect { cInt left; cInt top; cInt right; cInt bottom; ... };

 

C#  »

public class IntRect {

  public cInt left; { get; set; }

  public cInt top; { get; set; }

  public cInt right; { get; set; }

  public cInt bottom; { get; set; } ... };

用于接受Clipper库的GetBounds()函数的结果(包围盒);

</

<think>好的,我需要帮助用户了解在C#中使用Clipper2进行折线段分割多边形的操作。首先,我应该先介绍一下Clipper2的基本情况,说明它是一个功能强大的多边形裁剪和几何运算,支持复杂的布尔操作,比如合并、交集、差集等,并且相比旧版ClipperLib有性能优化和功能增强。 接下来,用户提到的“折线段分割多边形”具体是什么操作呢?可能需要解释这是指用一条折线(由多个线段组成的路径)将一个多边形分割成多个子多边形的过程。这一步需要明确应用场景,比如在GIS处理、游戏开发中的地形编辑或CAD设计中的几何操作。 然后,我需要整理使用Clipper2实现这一操作的步骤。首先,用户需要安装Clipper2的NuGet包,确保开发环境配置正确。然后,创建多边形和折线段的路径数据,这里要注意坐标系的处理,可能需要将坐标转换为Clipper2使用的整数坐标,通常需要缩放因子来处理浮点数。 接下来,使用Clipper2的API进行分割操作。这里可能需要使用ClipperD类(如果处理的是双精度浮点数),然后通过添加主体多边形和剪裁路径(即折线段)到Clipper对象中。关键点在于选择合适的ClipType,比如ctDifference或ctUnion,但分割可能需要更细致的操作,可能需要将折线转换为闭合路径或者进行其他处理。 然后执行Execute方法获取结果,这里可能需要处理返回的多个多边形,判断哪些是分割后的部分。需要注意,折线段可能不会自动闭合,所以可能需要将折线转换为一个开口的剪裁路径,或者将折线闭合成一个多边形后再进行差集操作,具体取决于Clipper2的支持情况。 可能还需要考虑折线段是否完全穿过多边形,否则分割可能不成功。此外,Clipper2在处理开放路径时的支持情况需要查阅文档,可能需要特殊处理,比如将折线转换为一个非常细长的多边形来进行切割。 在代码示例部分,我需要提供一个简单的示例,演示如何定义多边形和折线,如何配置ClipperD对象,执行操作并处理结果。需要注意的是,Clipper2的坐标可能需要使用PointD结构,并且可能需要缩放处理以避免整数精度问题。同时,处理后的结果需要转换回原始坐标。 最后,要提醒用户注意事项,比如折线段必须完全穿过多边形才能有效分割,可能需要预处理折线,或者在分割后处理生成的多边形。此外,性能方面,处理复杂几何时要注意优化。 需要验证这些步骤是否正确,可能参考Clipper2的官方文档或示例,确保使用的API方法正确,比如AddPath和AddClip的使用,以及ClipType的选择是否正确。如果有不确定的地方,建议用户查阅官方文档或社区资源,确保解决方案的准确性。</think>在C#中使用Clipper2实现折线段分割多边形的操作,主要涉及几何布尔运算。以下是分步说明及关键代码示例: 1. **Clipper2基础概念** - Clipper2是ClipperLib的升级版,支持多边形裁剪、偏移和布尔运算 - 支持开放路径(折线段)与闭合路径(多边形)的交互操作 - 使用整数坐标(Clipper64)或浮点坐标(ClipperD) 2. **折线段分割原理** ```csharp // 创建多边形(顺时针闭合路径) PathD subject = new PathD(); subject.Add(new PointD(100, 100)); subject.Add(new PointD(500, 100)); subject.Add(new PointD(500, 500)); subject.Add(new PointD(100, 500)); // 创建折线段(开放路径) PathD clip = new PathD(); clip.Add(new PointD(50, 250)); // 起点在多边形外 clip.Add(new PointD(300, 250)); clip.Add(new PointD(550, 450)); // 终点在多边形外 ``` 3. **执行分割操作** ```csharp ClipperD cpr = new ClipperD(); cpr.AddPath(subject, PathType.Subject); cpr.AddPath(clip, PathType.Clip); PolyTreeD solution = new PolyTreeD(); cpr.Execute(ClipType.Difference, FillRule.EvenOdd, solution); ``` 4. **关键参数说明** - `ClipType.Difference`:获取多边形被折线切割后的剩余部分 - `FillRule.EvenOdd`:默认的填充规则 - 结果通过`PolyTreeD`结构返回,需递归遍历获取所有子多边形 5. **处理结果** ```csharp List<PathD> results = Clipper.PolyTreeToPaths(solution); foreach (PathD path in results) { // 每个path代表分割后的子多边形 Console.WriteLine($"分割得到{path.Count}边形"); } ``` **注意事项**: 1. 折线段必须与多边形边界有交叉才能有效分割 2. 使用浮点版本(`ClipperD`)时需注意精度问题 3. 复杂形状建议先进行路径简化: ```csharp PathsD simplified = Clipper.SimplifyPaths(subject, 0.25); ``` 4. 可通过设置Delta调整切割线宽度: ```csharp cpr.Delta = 0.1; // 设置切割线"宽度" ``` 完整示例需配合NuGet包`Clipper2`使用。实际效果取决于折线与多边形的交叉情况,建议通过可视化验证分割结果。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值