第11章:OutRec 与 OutPt 输出结构
11.1 概述
当 Clipper2 的扫描线算法处理多边形时,需要构建输出多边形。OutRec(输出记录)和 OutPt(输出点)是用于存储和构建输出多边形的核心数据结构。
11.2 OutPt 类
11.2.1 类定义
internal class OutPt
{
public Point64 pt; // 点坐标
public OutPt? next; // 下一个输出点
public OutPt? prev; // 上一个输出点
public OutRec outrec; // 所属的输出记录
public Joiner? joiner; // 连接器(用于处理自相交)
}
11.2.2 双向循环链表
与 Vertex 类似,OutPt 形成双向循环链表:
op1 ←→ op2 ←→ op3 ←→ op4 ←→ op1
│ │
└─────────────────────────┘
11.2.3 OutPt vs Vertex
| 特性 | Vertex | OutPt |
|---|---|---|
| 用途 | 输入多边形 | 输出多边形 |
| 内容 | pt, flags | pt, outrec, joiner |
| 创建时机 | 预处理阶段 | 扫描线处理中 |
| 数量 | 与输入相同 | 可能更多(交点) |
11.3 OutRec 类
11.3.1 类定义
internal class OutRec
{
public int idx; // 索引
public OutRec? owner; // 所有者(外轮廓)
public Active? frontEdge; // 前边
public Active? backEdge; // 后边
public OutPt? pts; // 输出点链表
public PolyPath64? polypath; // 多边形树路径
public Rect64 bounds; // 边界矩形
public Path64? path; // 最终路径
public bool isOpen; // 是否开放路径
public List<OutRec?>? splits; // 分割记录
public OutRec? recursiveSplit; // 递归分割
}
11.3.2 成员详解
| 成员 | 类型 | 说明 |
|---|---|---|
idx |
int |
在 outrecList 中的索引 |
owner |
OutRec? |
父轮廓(包含此轮廓的外轮廓) |
frontEdge |
Active? |
当前构建的前边 |
backEdge |
Active? |
当前构建的后边 |
pts |
OutPt? |
输出点链表的头 |
bounds |
Rect64 |
计算后的边界框 |
isOpen |
bool |
是否为开放路径 |
11.3.3 Owner 关系
外轮廓 (OutRec A, owner = null)
├── 孔洞 (OutRec B, owner = A)
│ └── 岛屿 (OutRec C, owner = B)
└── 孔洞 (OutRec D, owner = A)
11.4 创建 OutRec
11.4.1 CreateOutRec 方法
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private OutRec CreateOutRec()
{
OutRec result = _outRecPool.Get();
result.idx = _outrecList.Count;
result.owner = null;
result.frontEdge = null;
result.backEdge = null;
result.pts = null;
result.polypath = null;
result.isOpen = false;
result.splits = null;
result.recursiveSplit = null;
_outrecList.Add(result);
return result;
}
11.4.2 对象池使用
// 获取
OutRec outrec = _outRecPool.Get();
// 归还
_outRecPool.Return(outrec);
11.5 创建 OutPt
11.5.1 AddOutPt 方法
private OutPt AddOutPt(Active ae, Point64 pt)
{
OutPt newOp = _outPtPool.Get();
newOp.pt = pt;
newOp.joiner = null;
newOp.outrec = ae.outrec!;
OutPt? op = ae.outrec!.pts;
if (op == null)
{
// 第一个点
newOp.next = newOp;
newOp.prev = newOp;
ae.outrec.pts = newOp;
}
else if (ae == ae.outrec.frontEdge)
{
// 添加到前端
newOp.next = op;
newOp.prev = op.prev;
op.prev!.next = newOp;
op.prev = newOp;
ae.outrec.pts = newOp;
}
else
{
// 添加到后端
newOp.prev = op;
newOp.next = op.next;
op.next!.prev = newOp;
op.next = newOp;
}
return newOp;
}
11.5.2 插入位置示意
前端插入 (frontEdge):
新点 → pts → op2 → op3 → ...
后端插入 (backEdge):
pts → 新点 → op2 → op3 → ...
11.6 构建输出多边形
11.6.1 StartOutRec 方法
private void StartOutRec(Active ae1, Active ae2)
{
OutRec outrec = CreateOutRec();
// 设置边关联
ae1.outrec = outrec;
ae2.outrec = outrec;
SetOwner(outrec, ae1, ae2);
// 确定前后边
if (ae1.curX <= ae2.curX)
{
outrec.frontEdge = ae1;
outrec.backEdge = ae2;
}
else
{
outrec.frontEdge = ae2;
outrec.backEdge = ae1;
}
}
11.6.2 AddLocalMinPoly
private OutPt? AddLocalMinPoly(Active ae1, Active ae2, Point64 pt,
bool isNew = false)
{
OutRec outrec;
if (isNew)
{
outrec = CreateOutRec();
outrec.isOpen = IsOpen(ae1);
// 设置边
if (ae1.curX <= ae2.curX)
{
outrec.frontEdge = ae1;
outrec.backEdge = ae2;
}
else
{
outrec.frontEdge = ae2;
outrec.backEdge = ae1;
}
ae1.outrec = outrec;
ae2.outrec = outrec;
}
else
{
outrec = ae1.outrec!;
}
OutPt op = AddOutPt(ae1, pt);
if (!isNew)
{
// 处理合并情况
AddOutPt(ae2, pt);
}
return op;
}
11.6.3 AddLocalMaxPoly
private void AddLocalMaxPoly(Active ae1, Active ae2, Point64 pt)
{
AddOutPt(ae1, pt);
if (ae1.outrec == ae2.outrec)
{
// 同一个 OutRec,闭合多边形
ae1.outrec!.frontEdge = null;
ae1.outrec.backEdge = null;
ae1.outrec = null;
ae2.outrec = null;
}
else
{
// 合并两个 OutRec
MergeOutRecs(ae1, ae2);
}
}
11.7 Owner 设置
11.7.1 SetOwner 方法
private void SetOwner(OutRec outrec, Active ae1, Active ae2)
{
// 查找有效的 owner
OutRec? owner = null;
// 尝试从 ae1 的左侧边找 owner
Active? ae = ae1.prevInAEL;
while (ae != null)
{
if (ae.outrec != null && !ae.outrec.isOpen)
{
owner = ae.outrec;
break;
}
ae = ae.prevInAEL;
}
outrec.owner = GetRealOwner(owner);
}
11.7.2 确定嵌套关系
private OutRec? GetRealOwner(OutRec? outrec)
{
while (outrec != null && outrec.pts == null)
outrec = outrec.owner;
return outrec;
}
11.8 OutRec 合并
11.8.1 MergeOutRecs 方法
private void MergeOutRecs(Active ae1, Active ae2)
{
OutRec? outrec1 = ae1.outrec;
OutRec? outrec2 = ae2.outrec;
if (outrec1 == null || outrec2 == null) return;
if (outrec1 == outrec2) return;
// 确定哪个是 owner
OutRec? keepRec, discardRec;
if (outrec1.idx < outrec2.idx)
{
keepRec = outrec1;
discardRec = outrec2;
}
else
{
keepRec = outrec2;
discardRec = outrec1;
}
// 合并点链表
MergePaths(keepRec, discardRec);
// 更新引用
discardRec.pts = null;
discardRec.owner = keepRec;
ae2.outrec = keepRec;
}
11.8.2 MergePaths 合并点链表
private void MergePaths(OutRec keep, OutRec discard)
{
OutPt op1 = keep.pts!;
OutPt op2 = discard.pts!;
// 连接两个环
OutPt op1Last = op1.prev!;
OutPt op2Last = op2.prev!;
op1Last.next = op2;
op2.prev = op1Last;
op2Last.next = op1;
op1.prev = op2Last;
// 更新所有点的 outrec
OutPt op = op2;
do
{
op.outrec = keep;
op = op.next!;
} while (op != op2);
}
11.9 边界计算
11.9.1 GetBounds 方法
internal static Rect64 GetBounds(OutPt op)
{
Rect64 result = new Rect64(
long.MaxValue, long.MaxValue,
long.MinValue, long.MinValue);
OutPt op2 = op;
do
{
if (op2.pt.X < result.left) result.left = op2.pt.X;
if (op2.pt.X > result.right) result.right = op2.pt.X;
if (op2.pt.Y < result.top) result.top = op2.pt.Y;
if (op2.pt.Y > result.bottom) result.bottom = op2.pt.Y;
op2 = op2.next!;
} while (op2 != op);
return result;
}
11.10 Joiner 连接器
11.10.1 Joiner 类
internal class Joiner
{
public int idx;
public OutPt op1;
public OutPt? op2;
public Joiner? next1;
public Joiner? next2;
}
11.10.2 用途
Joiner 用于处理自相交多边形的连接:
●───────●
╱ ╲ ╱
╱ ╲ ╱
╱ ╲ ╱
●───────●──────●
交点需要 Joiner 处理
11.10.3 创建 Joiner
private void AddJoin(OutPt op1, OutPt op2)
{
Joiner joiner = new Joiner
{
idx = _joinerList.Count,
op1 = op1,
op2 = op2,
next1 = op1.joiner,
next2 = op2.joiner
};
op1.joiner = joiner;
op2.joiner = joiner;
_joinerList.Add(joiner);
}
11.11 路径构建
11.11.1 BuildPath 方法
private Path64? BuildPath(OutPt op, bool reverse, bool isOpen)
{
int cnt = CountOutPts(op);
if (cnt < 2) return null;
if (!isOpen && cnt < 3) return null;
Path64 result = new Path64(cnt);
OutPt op2 = op;
if (reverse)
{
do
{
result.Add(op2.pt);
op2 = op2.prev!;
} while (op2 != op);
}
else
{
do
{
result.Add(op2.pt);
op2 = op2.next!;
} while (op2 != op);
}
return result;
}
11.11.2 CountOutPts
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CountOutPts(OutPt op)
{
int result = 0;
OutPt op2 = op;
do
{
result++;
op2 = op2.next!;
} while (op2 != op);
return result;
}
11.12 清理输出点
11.12.1 CleanCollinear 方法
private void CleanCollinear(OutRec outrec)
{
OutPt? op = outrec.pts;
if (op == null) return;
OutPt startOp = op;
OutPt? op2;
do
{
op2 = op;
// 跳过重复点
while (op2.next != startOp && op2.pt == op2.next!.pt)
{
DisposeOutPt(op2.next);
}
// 移除共线点
if (op2.prev != op2.next &&
InternalClipper.IsCollinear(op2.prev!.pt, op2.pt, op2.next!.pt))
{
if (op2 == outrec.pts)
outrec.pts = op2.prev;
DisposeOutPt(op2);
op2 = op2.prev;
if (op2 == outrec.pts) startOp = op2;
}
else
{
op2 = op2.next;
}
} while (op2 != startOp);
}
11.12.2 DisposeOutPt
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DisposeOutPt(OutPt op)
{
op.prev!.next = op.next;
op.next!.prev = op.prev;
_outPtPool.Return(op);
}
11.13 分割处理
11.13.1 splits 列表
public List<OutRec?>? splits;
当一个 OutRec 因为自相交而分割时,splits 记录子记录。
11.13.2 CheckSplitOwner
private void CheckSplitOwner(OutRec outrec)
{
if (outrec.splits == null) return;
foreach (OutRec? split in outrec.splits)
{
if (split != null && split.pts != null)
{
// 处理分割后的多边形
ProcessSplit(split);
}
}
}
11.14 本章小结
OutRec 和 OutPt 是 Clipper2 输出多边形的核心结构:
- OutPt:
- 输出点,形成双向循环链表
- 包含坐标、链接和所属 OutRec
- 使用对象池管理
- OutRec:
- 输出记录,表示一个输出多边形
- 维护点链表和边界信息
- owner 字段建立嵌套关系
- 关键操作:
- 创建和添加输出点
- 合并和分割 OutRec
- 清理共线点
- 构建最终路径
- Joiner:
- 处理自相交情况
- 连接需要合并的点
理解输出结构是理解 Clipper2 如何生成最终结果的关键。
| 上一章:Vertex与LocalMinima | 返回目录 | 下一章:Clipper64裁剪类详解 |