前言
Clipper库是目前计算机图形届广为使用的图形处理库,可以用于解决平面二维图形的多边形简化、布尔运算和偏置处理,在CAD、加工路径与3D打印方面都有着比较重要的应用。
本文使用Love2.io驱动。
本文源文件来自Clipper库,英文版文档参见ClipperLib Overview。
下载地址:https://download.csdn.net/download/hanfeidyx/11268643
概述
简介
Clipper Library(以下简称为Clipper库或者ClipperLib或Clipper)提供了对线段和多边形的裁剪(clipping)以及偏置(offseting)功能。
和其他裁剪库相比,Clipper具有以下特征:
- 它能够接受各类多边形输入,包含自交的多边形;
- 它支持多种填充原则(奇偶填充、非零填充、正填充、负填充);
- 它相较于其他库来说效率极高;
- 它数值稳定(鲁棒性强);
- 它支持多边形和线段的偏置;
- 它对于商用以及免费软件来说都是免费使用的。
术语:
- 裁剪(Clipping):通常是指在二维平面上把一个图形在指定的矩形框以外的部分去除掉。在更广义的角度,指定的裁剪范围不一定要是一个矩形,可以是各种各样的多边形,甚至是多个多边形;同样的,我们一般裁剪指的是形态学上的“求交”,但是在Clipper库中裁剪可以实现4种布尔运算(求交、求和、求异、求异或);
- 路径(Path):是指一些列有序的点的集合,用来定义一个轮廓(contour),这个轮廓既可以是代指一条线/开放路径(line/open path),也可以代指一个多边形/闭合轮廓(polygon/closed path)。
- 线(Line):与Polyline同义,代指一个具有两个以上点的开放路径。
- 轮廓(Contour):与路径同义;
- 孔洞/内轮廓(Hole):代指一个多边形内部表示该部分不属于该多边形的闭合区域;一个“内轮廓(Hole Polygon)”就是一个代指孔的外围点集的封闭路径;
- 孔洞/内轮廓(Hole):代指一个多边形内部表示该部分不属于该多边形的闭合区域;一个“内轮廓(Hole Polygon)”就是一个代指孔的外围点集的封闭路径;
- 多边形填充规则(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)
编译器预定义以下类型的宏:
- use_int32:当启用32位精度,效率提升,但是使用范围缩小;
- use_xyz:添加一个Z位数据,对性能影响很小;
- use_lines:启用开放路径的裁剪;如果该功能关闭(默认开启),大约有一个小于5%的性能的提升;
- 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()函数的结果(包围盒);