本文涉及知识点
求PathD及PathsD的面积
数学规则逆时针为外边界,面积为正;顺时针为内边界(孔洞),面积为负数。
两个完全相同的矩形,一个逆时针一个顺时针
csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Clipper2Lib;
csharp
PathD path1 = new PathD(new PointD[]{ new PointD(0,0),new PointD(100,0),new PointD(100,100),new PointD(0,100) });
PathD path2 = new PathD();
path2.AddRange(path1);
path2.Reverse();
double area1 = Clipper.Area(path1);
double area2 = Clipper.Area(path2);
Console.WriteLine("面积一:" + area1);
Console.WriteLine("面积二:" + area2);
运行结果符合预期。
面积一:10000
面积二:-10000
csharp
PathsD ps = new PathsD();
ps.Add(path1);
ps.Add(path2);
double area3 = Clipper.Area(ps);
Console.WriteLine("面积三:" + area3);
运行结果符合预期:
面积三:0
两个逆时针矩形不重叠
csharp
PathD path1 = CreatePath(100, 100, 10);
PathD path2 = CreatePath(200, 100, 10);
double area1 = Clipper.Area(path1);
double area2 = Clipper.Area(path2);
Console.WriteLine("面积一:" + area1);
Console.WriteLine("面积二:" + area2);
PathsD ps = new PathsD();
ps.Add(path1);
ps.Add(path2);
double area3 = Clipper.Area(ps);
Console.WriteLine("面积三:" + area3);
结果是两者之和:
面积一:400
面积二:400
面积三:800
两个完全相同的矩形(端点重合,方向相同)
csharp
PathD path1 = CreatePath(100, 100, 10);
PathD path2 = CreatePath(100, 100, 10);
double area1 = Clipper.Area(path1);
double area2 = Clipper.Area(path2);
Console.WriteLine("面积一:" + area1);
Console.WriteLine("面积二:" + area2);
PathsD ps = new PathsD();
ps.Add(path1);
ps.Add(path2);
double area3 = Clipper.Area(ps);
Console.WriteLine("面积三:" + area3);
重叠部分重复计算:
面积一:400
面积二:400
面积三:800
不重叠矩形,一个顺时针一个逆时针
csharp
PathD path1 = CreatePath(100, 100, 10);
PathD path2 = CreatePath(200, 100, 10);
path2.Reverse();
double area1 = Clipper.Area(path1);
double area2 = Clipper.Area(path2);
Console.WriteLine("面积一:" + area1);
Console.WriteLine("面积二:" + area2);
PathsD ps = new PathsD();
ps.Add(path1);
ps.Add(path2);
double area3 = Clipper.Area(ps);
Console.WriteLine("面积三:" + area3);
面积一:400
面积二:-400
面积三:0
结论
Paths面积就是各Path面积之和。
缠绕数(Winding Number)
以区域(子区域)内任意一点为起点,任意方向做射线。边界线从右向左穿过射线:缠绕数 +1,边界线从左向右穿过射线:缠绕数 -1,缠绕数 = 所有穿越的代数和。为了实现方便,一般用水平向右的射线。
逆势针矩形
任意边界方向都在对应射线的的逆时针平面,故WN+1。

顺时针矩形
任何边界方向都在对应射线的顺时针平面,故WN-1。

凹多边形
向左的射线和一条边界相交,边界在射线的逆时针半平面。故WN+1。
向右的射线和五条边界相交,3条边界在射线的逆时针半平面,2条在射线的顺时针半平面,故WN+3-2=1。

向右水平的射线
令射线方向 v 1 , 边界方向 v 2 ,如果 v 2 在 v 1 的逆时针半平面,则 v 1 × v 2 > 0 令射线方向v_1,边界方向v_2,如果v2在v1的逆时针半平面,则v_1 \times v_2 > 0 令射线方向v1,边界方向v2,如果v2在v1的逆时针半平面,则v1×v2>0,假定射线水平向右,及 v 1 = ( 1 , 0 ) ,则 v 1 × v 2 = v 2 . y v_1=(1,0),则v_1 \times v_2 = v_2.y v1=(1,0),则v1×v2=v2.y。
FillType 与布尔运算
csharp
public enum FillRule
{
EvenOdd = 0,
NonZero = 1,
Positive = 2,
Negative = 3
}
EvenOdd (奇偶):WN为奇数是内部。和Path是逆时针还是顺时针无关。假定将某个Path改变方向,x个交点由-1变成1,y个交点由1变成-1。则WN变化为:2x-2y,是偶数,故WN的奇偶性不变。
NonZero(非0):WN不是0是内部。和Path的方向有关。逆时针矩形内部有另外一个逆时针矩形,内部矩形WN=2。如果将任何一个矩形反向,则内部矩形的WN为0。
Positive(正数):WN>0时,是内部。
Negative(负数):WN<0时,是内部。
运算类型决定最终保留哪些区域。例如:
Intersection(交集):保留 WC = +2 的区域。
Union(并集):保留 WC > 0 的区域。
Difference(差集):保留 WC = +1 的区域。
XOR(异或):保留 WC=1 的区域。
交集并集异或都内部WC都是1,外包都是0。
差集,被减数内部是1,减数内部是-1,被减数减数外部是0。
一个正方形的并
path1逆时针,path2 顺时针。Paths哪些块是内部,由FillType和WN决定。
奇偶
csharp
public Form1()
{
InitializeComponent();
PathD path1 = CreatePath(0, 0, 100);
PathD path2 = CreatePath(0, 0, 10);
path2.Reverse();
FillRule fr = FillRule.EvenOdd;
var ps1 = Clipper.Union(new PathsD(new PathD[] {path1 }),fr );
var ps2 = Clipper.Union(new PathsD(new PathD[] { path2 }), fr);
double area3 = Clipper.Area(ps1);
double area4 = Clipper.Area(ps2);
Console.WriteLine("FillRule:" + fr);
Console.WriteLine("面积三:" + area3);
Console.WriteLine("面积四:" + area4);
}
//创建正方形,点集合逆时针顺序
PathD CreatePath(double xCenter,double yCenter,double halfLen)
{
PointD[] pts = {new PointD(xCenter-halfLen,yCenter-halfLen), new PointD(xCenter + halfLen, yCenter - halfLen),
new PointD(xCenter + halfLen, yCenter + halfLen), new PointD(xCenter - halfLen, yCenter + halfLen) };
return new PathD(pts);
}
运行结果:
FillRule:EvenOdd
面积三:40000
面积四:400
path1的WN=1,path2的WN=-1,都是奇数,故都是内部。
非0
只修改一行代码
csharp
FillRule fr = FillRule.NonZero;
FillRule:NonZero
面积三:40000
面积四:400
± 1 \pm 1 ±1都非0,故都是内部。
正数
csharp
FillRule fr = FillRule.Positive;
FillRule:Positive
面积三:40000
面积四:0
负数
csharp
FillRule fr = FillRule.Negative;
FillRule:Negative
面积三:0
面积四:400
1个paths 2个path的并
path1逆时针矩形,path2逆时针矩形,path2完全在path1内部。path2反转后形成path3,即path3顺时针。
path2内部:WN=2,path3内部:WN=0;path1内部非空洞,WN=1。
奇偶
csharp
public Form1()
{
InitializeComponent();
PathD path1 = CreatePath(0, 0, 100);
PathD path2 = CreatePath(0, 0, 10);
PathD path3 = CreatePath(0, 0, 10);
path3.Reverse();
Console.WriteLine("面积一:" + Clipper.Area(path1));
Console.WriteLine("面积二:" + Clipper.Area(path2));
Console.WriteLine("面积三:" + Clipper.Area(path3));
FillRule fr = FillRule.EvenOdd;
var ps12 = Clipper.Union(new PathsD(new PathsD() { path1,path2}), fr);
var ps13 = Clipper.Union(new PathsD(new PathsD() { path1, path3 }), fr);
Console.WriteLine("" + fr);
Console.WriteLine("面积一二:" + Clipper.Area(ps12));
Console.WriteLine("面积一三:" + Clipper.Area(ps13));
}
//创建正方形,点集合逆时针顺序
PathD CreatePath(double xCenter,double yCenter,double halfLen)
{
PointD[] pts = {new PointD(xCenter-halfLen,yCenter-halfLen), new PointD(xCenter + halfLen, yCenter - halfLen),
new PointD(xCenter + halfLen, yCenter + halfLen), new PointD(xCenter - halfLen, yCenter + halfLen) };
return new PathD(pts);
}
面积一:40000
面积二:400
面积三:-400
EvenOdd
面积一二:39600
面积一三:39600
非零
只修改一行。
csharp
FillRule fr = FillRule.NonZero;
面积一:40000
面积二:400
面积三:-400
NonZero
面积一二:40000
面积一三:39600
正数
csharp
FillRule fr = FillRule.Positive;
面积一:40000
面积二:400
面积三:-400
Positive
面积一二:40000
面积一三:39600
负数
csharp
FillRule fr = FillRule.Negative;
面积一:40000
面积二:400
面积三:-400
Negative
面积一二:0
面积一三:0
两个paths的交集
path1,path2的WN=1,path3的WN=-1
奇偶
csharp
PathD path1 = CreatePath(0, 0, 100);
PathD path2 = CreatePath(0, 0, 10);
PathD path3 = CreatePath(0, 0, 10);
path3.Reverse();
Console.WriteLine("面积一:" + Clipper.Area(path1));
Console.WriteLine("面积二:" + Clipper.Area(path2));
Console.WriteLine("面积三:" + Clipper.Area(path3));
FillRule fr = FillRule.EvenOdd;
var ps12 = Clipper.Intersect(new PathsD(new PathsD() { path1}), new PathsD(new PathsD() { path2 }), fr);
var ps13 = Clipper.Intersect(new PathsD(new PathsD() { path1 }), new PathsD(new PathsD() { path3 }), fr);
Console.WriteLine("" + fr);
Console.WriteLine("面积一二:" + Clipper.Area(ps12));
Console.WriteLine("面积一三:" + Clipper.Area(ps13));
面积一:40000
面积二:400
面积三:-400
EvenOdd
面积一二:400
面积一三:400
非0
csharp
FillRule fr = FillRule.NonZero;
面积一:40000
面积二:400
面积三:-400
NonZero
面积一二:400
面积一三:400
正数
面积一:40000
面积二:400
面积三:-400
Positive
面积一二:400
面积一三:0
负数
面积一:40000
面积二:400
面积三:-400
Negative
面积一二:0
面积一三:0
两个paths的差集
奇偶
面积一:40000
面积二:400
面积三:-400
EvenOdd
面积一二:39600
面积一三:39600
非0
面积一:40000
面积二:400
面积三:-400
NonZero
面积一二:39600
面积一三:39600
正数
面积一:40000
面积二:400
面积三:-400
Positive
面积一二:39600
负数
面积一:40000
面积二:400
面积三:-400
Negative
面积一二:0
面积一三:0
两个paths的交集 异或
奇偶
面积一:40000
面积二:400
面积三:-400
EvenOdd
面积一二:39600
面积一三:39600
非0
面积一:40000
面积二:400
面积三:-400
NonZero
面积一二:39600
面积一三:39600
正数
面积一:40000
面积二:400
面积三:-400
Positive
面积一二:39600
面积一三:40000
负数
面积一:40000
面积二:400
面积三:-400
Negative
面积一二:0
面积一三:400
返回值
Clipper的返回值,可以认为是Even-Odd或者Non-Zero中任意一种,即兼容这两种填充方式。
演示工具
演示工具基于Cad,支持Cad2013及更高版本。
亲士库版本:2026.9.4
Cliper库版本:2.0.0.0
如果不会操作,请看视频:https://edu.csdn.net/course/detail/41418
建议用工具尝试以下情况:
四种填充方式
部分多边形相离,部分多边形相切,部分多边形相离。
部分多边形是凸多边形,部分是凹多边形。
由于布尔运算容易理解,故工具只演示一个Path的并。
如果不想可视频或操作,可直接看图片。
奇偶模式的并集


01模式的并集


正数并集


负数并集
并集也可能为空。


上图红色三角形WN是1。黄色三角形是0,穿过三角形是-1,穿过矩形是1。红色矩形是1。全部不是负数。

扩展阅读
| 计算几何为骨,排样优化为魂 |
|---|
| 作品:亲士CAD工具箱 |
| 经典文章推荐:二维排样 |
| 万物皆数学 |
| 查阅鄙人的博文,请点击博文下载学院导航 |
| 活到老,学到老。明朝中后期,大约50%的进士能当上堂官(副部及更高);能当上堂官的举人只有十余人。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。
