package com.sundablog.clipper;
import com.sundablog.clipper.Path.OutRec;
import com.sundablog.clipper.Point.LongPoint;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* Clipper基础抽象类,实现了Clipper接口的核心功能
* 提供了多边形裁剪算法的基础框架和通用方法
*/
public abstract class ClipperBase implements Clipper {
/**
* 局部极小值类,用于表示多边形边缘的局部最低点
*/
protected class LocalMinima {
long y; // 局部极小值的Y坐标
Edge leftBound; // 左边界边
Edge rightBound; // 右边界边
LocalMinima next; // 指向下一个局部极小值
}
/**
* 扫描线类,用于表示扫描线算法中的扫描线位置
*/
protected class Scanbeam {
long y; // 扫描线的Y坐标
Scanbeam next; // 指向下一条扫描线
}
/**
* 局部极大值类,用于表示多边形边缘的局部最高点
*/
protected class Maxima {
long x; // 局部极大值的X坐标
Maxima next; // 指向下一个局部极大值
Maxima prev; // 指向上一个局部极大值
}
/**
* 初始化边的基本信息
* @param e 当前要初始化的边
* @param eNext 下一条边
* @param ePrev 上一条边
* @param pt 当前边的起点
*/
private static void initEdge(Edge e, Edge eNext, Edge ePrev, LongPoint pt) {
e.next = eNext;
e.prev = ePrev;
e.setCurrent(new LongPoint(pt));
e.outIdx = Edge.UNASSIGNED;
}
/**
* 初始化边的高级信息,设置边的上下端点和属性
* @param e 要初始化的边
* @param polyType 多边形类型(主体或裁剪)
*/
private static void initEdge2(Edge e, PolyType polyType) {
if (e.getCurrent().getY() >= e.next.getCurrent().getY()) {
e.setBot(new LongPoint(e.getCurrent()));
e.setTop(new LongPoint(e.next.getCurrent()));
} else {
e.setTop(new LongPoint(e.getCurrent()));
e.setBot(new LongPoint(e.next.getCurrent()));
}
e.updateDeltaX();
e.polyTyp = polyType;
}
/**
* 检查坐标点是否在允许的范围内
* @param Pt 要检查的坐标点
* @throws IllegalStateException 如果坐标超出允许范围
*/
private static void rangeTest(LongPoint Pt) {
if (Pt.getX() > LOW_RANGE || Pt.getY() > LOW_RANGE || -Pt.getX() > LOW_RANGE || -Pt.getY() > LOW_RANGE) {
if (Pt.getX() > HI_RANGE || Pt.getY() > HI_RANGE || -Pt.getX() > HI_RANGE || -Pt.getY() > HI_RANGE) {
throw new IllegalStateException("Coordinate outside allowed range");
}
}
}
/**
* 从双向链表中移除边(但不从内存中删除)
* @param e 要移除的边
* @return 返回下一条边
*/
private static Edge removeEdge(Edge e) {
//removes e from double_linked_list (but without removing from memory)
e.prev.next = e.next;
e.next.prev = e.prev;
final Edge result = e.next;
e.prev = null; //flag as removed (see ClipperBase.Clear)
return result;
}
// 坐标值的低范围限制(防止溢出)
private final static long LOW_RANGE = 0x3FFFFFFF;
// 坐标值的高范围限制
private final static long HI_RANGE = 0x3FFFFFFFFFFFFFFFL;
protected LocalMinima minimaList; // 局部极小值列表
protected LocalMinima currentLM; // 当前处理的局部极小值
protected Scanbeam scanbeam; // 扫描线列表
protected final List<OutRec> polyOuts = new ArrayList<>(); // 输出多边形记录列表
protected Edge activeEdges; // 活动边列表
protected boolean hasOpenPaths; // 是否包含开放路径
protected final boolean preserveCollinear; // 是否保留共线点
private final static Logger LOGGER = Logger.getLogger(Clipper.class.getName()); // 日志记录器
/**
* 构造函数,初始化ClipperBase实例
* @param preserveCollinear 是否保留共线点
*/
protected ClipperBase(boolean preserveCollinear) //constructor (nb: no external instantiation)
{
this.preserveCollinear = preserveCollinear;
minimaList = null;
currentLM = null;
hasOpenPaths = false;
}
/**
* 添加单个路径到裁剪器中
* @param pg 要添加的路径
* @param polyType 路径的多边形类型(主体或裁剪)
* @param Closed 是否为闭合路径
* @return 添加是否成功
*/
@Override
public boolean addPath(Path pg, PolyType polyType, boolean Closed) {
// 检查参数有效性:开放路径只能是主体路径
if (!Closed && polyType == PolyType.CLIP) {
throw new IllegalStateException("AddPath: Open paths must be subject.");
}
// 移除重复的端点(闭合路径)和相邻重复点
int highI = pg.size() - 1;
if (Closed) {
while (highI > 0 && pg.get(highI).equals(pg.get(0))) {
--highI;
}
}
while (highI > 0 && pg.get(highI).equals(pg.get(highI - 1))) {
--highI;
}
// 检查路径是否有效:闭合路径至少需要3个点,开放路径至少需要2个点
if (Closed && highI < 2 || !Closed && highI < 1) {
return false;
}
// 创建边数组
final List<Edge> edges = new ArrayList<>(highI + 1);
for (int i = 0; i <= highI; i++) {
edges.add(new Edge());
}
boolean IsFlat = true; // 标记路径是否完全平坦(所有点在同一水平线上)
// 第一阶段:基本边初始化
edges.get(1).setCurrent(new LongPoint(pg.get(1)));
rangeTest(pg.get(0));
rangeTest(pg.get(highI));
initEdge(edges.get(0), edges.get(1), edges.get(highI), pg.get(0));
initEdge(edges.get(highI), edges.get(0), edges.get(highI - 1), pg.get(highI));
for (int i = highI - 1; i >= 1; --i) {
rangeTest(pg.get(i));
initEdge(edges.get(i), edges.get(i + 1), edges.get(i - 1), pg.get(i));
}
Edge eStart = edges.get(0);
// 第二阶段:移除重复顶点和(对于闭合路径)共线边
Edge e = eStart, eLoopStop = eStart;
for (;;) {
// 允许开放路径的起点和终点相同
if (e.getCurrent().equals(e.next.getCurrent()) && (Closed || !e.next.equals(eStart))) {
if (e == e.next) {
break;
}
if (e == eStart) {
eStart = e.next;
}
e = removeEdge(e);
eLoopStop = e;
continue;
}
if (e.prev == e.next) {
break; // 只有两个顶点
}
// 处理闭合路径中的共线边
else if (Closed && Point.slopesEqual(e.prev.getCurrent(), e.getCurrent(), e.next.getCurrent())
&& (!isPreserveCollinear() || !Point.isPt2BetweenPt1AndPt3(e.prev.getCurrent(), e.getCurrent(), e.next.getCurrent()))) {
// 开放路径允许共线边,但闭合路径默认合并相邻共线边
// 如果启用了PreserveCollinear属性,则只移除重叠的共线边(如尖刺)
if (e == eStart) {
eStart = e.next;
}
e = removeEdge(e);
e = e.prev;
eLoopStop = e;
continue;
}
e = e.next;
if (e == eLoopStop || !Closed && e.next == eStart) {
break;
}
}
// 检查路径是否仍然有效
if (!Closed && e == e.next || Closed && e.prev == e.next) {
return false;
}
// 标记开放路径
if (!Closed) {
hasOpenPaths = true;
eStart.prev.outIdx = Edge.SKIP;
}
// 第三阶段:边的第二阶段初始化
e = eStart;
do {
initEdge2(e, polyType);
e = e.next;
if (IsFlat && e.getCurrent().getY() != eStart.getCurrent().getY()) {
IsFlat = false;
}
} while (e != eStart);
// 第四阶段:将边界添加到局部极小值列表
// 完全平坦的路径需要特殊处理,以避免无限循环等问题
if (IsFlat) {
if (Closed) {
return false;
}
e.prev.outIdx = Edge.SKIP;
final LocalMinima locMin = new LocalMinima();
locMin.next = null;
locMin.y = e.getBot().getY();
locMin.leftBound = null;
locMin.rightBound = e;
locMin.rightBound.side = Edge.Side.RIGHT;
locMin.rightBound.windDelta = 0;
for (;;) {
if (e.getBot().getX() != e.prev.getTop().getX()) {
e.reverseHorizontal();
}
if (e.next.outIdx == Edge.SKIP) break;
e.nextInLML = e.next;
e = e.next;
}
insertLocalMinima(locMin);
return true;
}
boolean leftBoundIsForward;
Edge EMin = null;
// 避免开放路径起点和终点相同时的无限循环
if (e.prev.getBot().equals(e.prev.getTop())) {
e = e.next;
}
// 处理所有局部极小值
for (;;) {
e = e.findNextLocMin();
if (e == EMin) {
break;
} else if (EMin == null) {
EMin = e;
}
// E和E.Prev共享一个局部极小值(水平时左对齐)
// 比较斜率确定哪个开始哪个边界
final LocalMinima locMin = new LocalMinima();
locMin.next = null;
locMin.y = e.getBot().getY();
if (e.deltaX < e.prev.deltaX) {
locMin.leftBound = e.prev;
locMin.rightBound = e;
leftBoundIsForward = false; // Q.nextInLML = Q.prev
} else {
locMin.leftBound = e;
locMin.rightBound = e.prev;
leftBoundIsForward = true; // Q.nextInLML = Q.next
}
locMin.leftBound.side = Edge.Side.LEFT;
locMin.rightBound.side = Edge.Side.RIGHT;
// 设置环绕数变化
if (!Closed) {
locMin.leftBound.windDelta = 0;
} else if (locMin.leftBound.next == locMin.rightBound) {
locMin.leftBound.windDelta = -1;
} else {
locMin.leftBound.windDelta = 1;
}
locMin.rightBound.windDelta = -locMin.leftBound.windDelta;
// 处理边界
e = processBound(locMin.leftBound, leftBoundIsForward);
if (e.outIdx == Edge.SKIP) {
e = processBound(e, leftBoundIsForward);
}
Edge E2 = processBound(locMin.rightBound, !leftBoundIsForward);
if (E2.outIdx == Edge.SKIP) {
E2 = processBound(E2, !leftBoundIsForward);
}
// 处理跳过的边
if (locMin.leftBound.outIdx == Edge.SKIP) {
locMin.leftBound = null;
} else if (locMin.rightBound.outIdx == Edge.SKIP) {
locMin.rightBound = null;
}
insertLocalMinima(locMin);
if (!leftBoundIsForward) {
e = E2;
}
}
return true;
}
/**
* 添加多个路径到裁剪器中
* @param paths 要添加的路径集合
* @param polyType 路径的多边形类型(主体或裁剪)
* @param closed 是否为闭合路径
* @return 至少有一个路径添加成功则返回true
*/
@Override
public boolean addPaths(Paths paths, PolyType polyType, boolean closed) {
boolean result = false;
for (Path path : paths) {
if (addPath(path, polyType, closed)) {
result = true;
}
}
return result;
}
/**
* 清除裁剪器中的所有路径和数据
*/
@Override
public void clear() {
disposeLocalMinimaList();
hasOpenPaths = false;
}
/**
* 释放局部极小值列表资源
*/
private void disposeLocalMinimaList() {
while (minimaList != null) {
final LocalMinima tmpLm = minimaList.next;
minimaList = null;
minimaList = tmpLm;
}
currentLM = null;
}
/**
* 按Y坐标降序插入局部极小值到列表中
* @param newLm 要插入的新局部极小值
*/
private void insertLocalMinima(LocalMinima newLm) {
if (minimaList == null) {
minimaList = newLm;
} else if (newLm.y >= minimaList.y) {
newLm.next = minimaList;
minimaList = newLm;
} else {
LocalMinima tmpLm = minimaList;
while (tmpLm.next != null && newLm.y < tmpLm.next.y) {
tmpLm = tmpLm.next;
}
newLm.next = tmpLm.next;
tmpLm.next = newLm;
}
}
/**
* 检查是否保留共线点
* @return 是否保留共线点
*/
private boolean isPreserveCollinear() {
return preserveCollinear;
}
/**
* 弹出指定Y坐标的局部极小值
* @param y Y坐标
* @param current 当前局部极小值引用数组
* @return 是否成功弹出
*/
protected boolean popLocalMinima(long y, LocalMinima[] current) {
LOGGER.entering(ClipperBase.class.getName(), "popLocalMinima");
current[0] = currentLM;
if (currentLM != null && currentLM.y == y) {
currentLM = currentLM.next;
return true;
}
return false;
}
/**
* 处理边界边,设置下一个局部极小值边链接
* @param e 要处理的边
* @param LeftBoundIsForward 是否为前向左边界
* @return 处理后的边
*/
private Edge processBound(Edge e, boolean LeftBoundIsForward) {
Edge EStart, result = e;
Edge Horz;
// 处理跳过的边
if (result.outIdx == Edge.SKIP) {
e = result;
if (LeftBoundIsForward) {
while (e.getTop().getY() == e.next.getBot().getY()) {
e = e.next;
}
while (e != result && e.deltaX == Edge.HORIZONTAL) {
e = e.prev;
}
} else {
while (e.getTop().getY() == e.prev.getBot().getY()) {
e = e.prev;
}
while (e != result && e.deltaX == Edge.HORIZONTAL) {
e = e.next;
}
}
// 根据处理结果设置返回值
if (e == result) {
if (LeftBoundIsForward) {
result = e.next;
} else {
result = e.prev;
}
} else {
// 创建新的局部极小值
if (LeftBoundIsForward) {
e = result.next;
} else {
e = result.prev;
}
final LocalMinima locMin = new LocalMinima();
locMin.next = null;
locMin.y = e.getBot().getY();
locMin.leftBound = null;
locMin.rightBound = e;
e.windDelta = 0;
result = processBound(e, LeftBoundIsForward);
insertLocalMinima(locMin);
}
return result;
}
// 处理水平边
if (e.deltaX == Edge.HORIZONTAL) {
if (LeftBoundIsForward) {
EStart = e.prev;
} else {
EStart = e.next;
}
// 处理相邻的水平边
if (EStart.deltaX == Edge.HORIZONTAL) {
if (EStart.getBot().getX() != e.getBot().getX() && EStart.getTop().getX() != e.getBot().getX()) {
e.reverseHorizontal();
}
} else if (EStart.getBot().getX() != e.getBot().getX()) {
e.reverseHorizontal();
}
}
EStart = e;
// 处理前向边界
if (LeftBoundIsForward) {
while (result.getTop().getY() == result.next.getBot().getY() && result.next.outIdx != Edge.SKIP) {
result = result.next;
}
// 处理顶部的水平边
if (result.deltaX == Edge.HORIZONTAL && result.next.outIdx != Edge.SKIP) {
Horz = result;
while (Horz.prev.deltaX == Edge.HORIZONTAL) {
Horz = Horz.prev;
}
if (Horz.prev.getTop().getX() > result.next.getTop().getX()) {
result = Horz.prev;
}
}
// 设置下一个局部极小值边链接
while (e != result) {
e.nextInLML = e.next;
if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.prev.getTop().getX()) {
e.reverseHorizontal();
}
e = e.next;
}
if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.prev.getTop().getX()) {
e.reverseHorizontal();
}
result = result.next; // 移动到当前边界之后的边
}
// 处理后向边界
else {
while (result.getTop().getY() == result.prev.getBot().getY() && result.prev.outIdx != Edge.SKIP) {
result = result.prev;
}
// 处理顶部的水平边
if (result.deltaX == Edge.HORIZONTAL && result.prev.outIdx != Edge.SKIP) {
Horz = result;
while (Horz.next.deltaX == Edge.HORIZONTAL) {
Horz = Horz.next;
}
if (Horz.next.getTop().getX() == result.prev.getTop().getX() ||
Horz.next.getTop().getX() > result.prev.getTop().getX()) {
result = Horz.next;
}
}
// 设置下一个局部极小值边链接
while (e != result) {
e.nextInLML = e.prev;
if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.next.getTop().getX()) {
e.reverseHorizontal();
}
e = e.prev;
}
if (e.deltaX == Edge.HORIZONTAL && e != EStart && e.getBot().getX() != e.next.getTop().getX()) {
e.reverseHorizontal();
}
result = result.prev; // 移动到当前边界之后的边
}
return result;
}
/**
* 重置裁剪器状态,准备新的裁剪操作
*/
protected void reset() {
currentLM = minimaList;
if (currentLM == null) {
return; // 没有要处理的内容
}
// 重置所有边
scanbeam = null;
LocalMinima lm = minimaList;
while (lm != null) {
insertScanbeam(lm.y);
Edge e = lm.leftBound;
if (e != null) {
e.setCurrent(new LongPoint(e.getBot()));
e.outIdx = Edge.UNASSIGNED;
}
e = lm.rightBound;
if (e != null) {
e.setCurrent(new LongPoint(e.getBot()));
e.outIdx = Edge.UNASSIGNED;
}
lm = lm.next;
}
activeEdges = null;
}
/**
* 按Y坐标降序插入扫描线到列表中(忽略重复)
* @param y 扫描线的Y坐标
*/
protected void insertScanbeam(long y) {
LOGGER.entering(ClipperBase.class.getName(), "insertScanbeam");
// 单链表:降序排序,忽略重复
if (scanbeam == null) {
scanbeam = new Scanbeam();
scanbeam.next = null;
scanbeam.y = y;
} else if (y > scanbeam.y) {
final Scanbeam newSb = new Scanbeam();
newSb.y = y;
newSb.next = scanbeam;
scanbeam = newSb;
} else {
Scanbeam sb2 = scanbeam;
while (sb2.next != null && (y <= sb2.next.y)) {
sb2 = sb2.next;
}
if (y == sb2.y) {
return; // 忽略重复
}
final Scanbeam newSb = new Scanbeam();
newSb.y = y;
newSb.next = sb2.next;
sb2.next = newSb;
}
}
/**
* 弹出下一条扫描线
* @param y Y坐标数组,用于存储弹出的扫描线Y坐标
* @return 是否成功弹出
*/
protected boolean popScanbeam(long[] y) {
if (scanbeam == null) {
y[0] = 0;
return false;
}
y[0] = scanbeam.y;
scanbeam = scanbeam.next;
return true;
}
/**
* 检查是否有等待处理的局部极小值
* @return 是否有等待处理的局部极小值
*/
protected final boolean localMinimaPending() {
return currentLM != null;
}
/**
* 创建输出记录
* @return 新创建的输出记录
*/
protected OutRec createOutRec() {
OutRec result = new OutRec();
result.Idx = Edge.UNASSIGNED;
result.isHole = false;
result.isOpen = false;
result.firstLeft = null;
result.setPoints(null);
result.bottomPt = null;
result.polyNode = null;
polyOuts.add(result);
result.Idx = polyOuts.size() - 1;
return result;
}
/**
* 释放输出记录资源
* @param index 要释放的输出记录索引
*/
protected void disposeOutRec(int index) {
OutRec outRec = polyOuts.get(index);
outRec.setPoints(null);
outRec = null;
polyOuts.set(index, null);
}
/**
* 更新边到活动边列表(AEL)
* @param e 要更新的边
* @throws IllegalStateException 如果调用无效
*/
protected void updateEdgeIntoAEL(Edge e) {
if (e.nextInLML == null) {
throw new IllegalStateException("UpdateEdgeIntoAEL: invalid call");
}
final Edge aelPrev = e.prevInAEL;
final Edge aelNext = e.nextInAEL;
e.nextInLML.outIdx = e.outIdx;
if (aelPrev != null) {
aelPrev.nextInAEL = e.nextInLML;
} else {
activeEdges = e.nextInLML;
}
if (aelNext != null) {
aelNext.prevInAEL = e.nextInLML;
}
e.nextInLML.side = e.side;
e.nextInLML.windDelta = e.windDelta;
e.nextInLML.windCnt = e.windCnt;
e.nextInLML.windCnt2 = e.windCnt2;
e = e.nextInLML;
e.setCurrent(e.getBot());
e.prevInAEL = aelPrev;
e.nextInAEL = aelNext;
if (e.isHorizontal()) {
insertScanbeam(e.getTop().getY());
}
}
/**
* 在活动边列表中交换两个边的位置
* @param edge1 第一个边
* @param edge2 第二个边
*/
protected void swapPositionsInAEL(Edge edge1, Edge edge2) {
LOGGER.entering(ClipperBase.class.getName(), "swapPositionsInAEL");
// 检查边是否已从AEL中移除
if (edge1.nextInAEL == edge1.prevInAEL || edge2.nextInAEL == edge2.prevInAEL) {
return;
}
// 处理相邻边的情况
if (edge1.nextInAEL == edge2) {
final Edge next = edge2.nextInAEL;
if (next != null) {
next.prevInAEL = edge1;
}
final Edge prev = edge1.prevInAEL;
if (prev != null) {
prev.nextInAEL = edge2;
}
edge2.prevInAEL = prev;
edge2.nextInAEL = edge1;
edge1.prevInAEL = edge2;
edge1.nextInAEL = next;
} else if (edge2.nextInAEL == edge1) {
final Edge next = edge1.nextInAEL;
if (next != null) {
next.prevInAEL = edge2;
}
final Edge prev = edge2.prevInAEL;
if (prev != null) {
prev.nextInAEL = edge1;
}
edge1.prevInAEL = prev;
edge1.nextInAEL = edge2;
edge2.prevInAEL = edge1;
edge2.nextInAEL = next;
} else {
// 处理非相邻边的情况
final Edge next = edge1.nextInAEL;
final Edge prev = edge1.prevInAEL;
edge1.nextInAEL = edge2.nextInAEL;
if (edge1.nextInAEL != null) {
edge1.nextInAEL.prevInAEL = edge1;
}
edge1.prevInAEL = edge2.prevInAEL;
if (edge1.prevInAEL != null) {
edge1.prevInAEL.nextInAEL = edge1;
}
edge2.nextInAEL = next;
if (edge2.nextInAEL != null) {
edge2.nextInAEL.prevInAEL = edge2;
}
edge2.prevInAEL = prev;
if (edge2.prevInAEL != null) {
edge2.prevInAEL.nextInAEL = edge2;
}
}
// 更新活动边列表的头部
if (edge1.prevInAEL == null) {
activeEdges = edge1;
} else if (edge2.prevInAEL == null) {
activeEdges = edge2;
}
LOGGER.exiting(ClipperBase.class.getName(), "swapPositionsInAEL");
}
/**
* 从活动边列表中删除边
* @param e 要删除的边
*/
protected void deleteFromAEL(Edge e) {
LOGGER.entering(ClipperBase.class.getName(), "deleteFromAEL");
Edge aelPrev = e.prevInAEL;
Edge aelNext = e.nextInAEL;
// 检查边是否已被删除
if (aelPrev == null && aelNext == null && (e != activeEdges)) {
return; // already deleted
}
// 更新链表引用
if (aelPrev != null) {
aelPrev.nextInAEL = aelNext;
} else {
activeEdges = aelNext;
}
if (aelNext != null) {
aelNext.prevInAEL = aelPrev;
}
// 清除边的AEL引用
e.nextInAEL = null;
e.prevInAEL = null;
LOGGER.exiting(ClipperBase.class.getName(), "deleteFromAEL");
}
}
最后修改:2025 年 12 月 01 日
© 允许规范转载