diff --git a/dist/polybool.cjs b/dist/polybool.cjs index e22729b..2072692 100644 --- a/dist/polybool.cjs +++ b/dist/polybool.cjs @@ -1506,7 +1506,6 @@ function joinLines(seg1, seg2, geo) { if (geo.isCollinear(seg1.p0, seg1.p1, seg2.p1)) { return new SegmentLine(seg1.p0, seg2.p1, geo); } - return false; } function joinCurves(seg1, seg2, geo) { if (geo.isCollinear(seg1.p2, seg1.p3, seg2.p1)) { @@ -1531,294 +1530,122 @@ function joinCurves(seg1, seg2, geo) { } } } - return false; } function joinSegments(seg1, seg2, geo) { - if (seg1 === seg2) { - return false; - } - if (seg1 instanceof SegmentLine && seg2 instanceof SegmentLine) { - return joinLines(seg1, seg2, geo); - } - if (seg1 instanceof SegmentCurve && seg2 instanceof SegmentCurve) { - return joinCurves(seg1, seg2, geo); + if (seg1 !== seg2) { + if (seg1 instanceof SegmentLine && seg2 instanceof SegmentLine) { + return joinLines(seg1, seg2, geo); + } + if (seg1 instanceof SegmentCurve && seg2 instanceof SegmentCurve) { + return joinCurves(seg1, seg2, geo); + } } - return false; } function SegmentChainer(segments, geo, log) { const closedChains = []; const openChains = []; const regions = []; for (const segb of segments) { - let seg = segb.data; + const seg = segb.myFill.above ? segb.data : segb.data.reverse(); const closed = segb.closed; const chains = closed ? closedChains : openChains; const pt1 = seg.start(); const pt2 = seg.end(); - const reverseChain = (index) => { - log === null || log === void 0 ? void 0 : log.chainReverse(index, closed); - const newChain = []; - for (const seg of chains[index].segs) { - newChain.unshift(seg.reverse()); - } - chains[index] = { - segs: newChain, - fill: !chains[index].fill, - }; - return newChain; - }; if (seg instanceof SegmentLine && geo.isEqualVec2(pt1, pt2)) { console.warn("PolyBool: Warning: Zero-length segment detected; your epsilon is " + "probably too small or too large"); continue; } - log === null || log === void 0 ? void 0 : log.chainStart({ seg, fill: !!segb.myFill.above }, closed); - // search for two chains that this segment matches - const firstMatch = { - index: 0, - matchesHead: false, - matchesPt1: false, - }; - const secondMatch = { - index: 0, - matchesHead: false, - matchesPt1: false, - }; - let nextMatch = firstMatch; - function setMatch(index, matchesHead, matchesPt1) { - // return true if we've matched twice - if (nextMatch) { - nextMatch.index = index; - nextMatch.matchesHead = matchesHead; - nextMatch.matchesPt1 = matchesPt1; - } - if (nextMatch === firstMatch) { - nextMatch = secondMatch; - return false; - } - nextMatch = null; - return true; // we've matched twice, we're done here - } + log === null || log === void 0 ? void 0 : log.chainStart(seg, closed); + let startMatch; + let endMatch; for (let i = 0; i < chains.length; i++) { - const chain = chains[i].segs; + const chain = chains[i]; const head = chain[0].start(); const tail = chain[chain.length - 1].end(); - if (geo.isEqualVec2(head, pt1)) { - if (setMatch(i, true, true)) { - break; - } + if (startMatch == null && geo.isEqualVec2(tail, pt1)) { + startMatch = i; } - else if (geo.isEqualVec2(head, pt2)) { - if (setMatch(i, true, false)) { - break; - } + if (endMatch == null && geo.isEqualVec2(head, pt2)) { + endMatch = i; } - else if (geo.isEqualVec2(tail, pt1)) { - if (setMatch(i, false, true)) { - break; - } - } - else if (geo.isEqualVec2(tail, pt2)) { - if (setMatch(i, false, false)) { - break; - } + if (startMatch && endMatch) { + break; } } - if (nextMatch === firstMatch) { - // we didn't match anything, so create a new chain - const fill = !!segb.myFill.above; - chains.push({ segs: [seg], fill }); - log === null || log === void 0 ? void 0 : log.chainNew({ seg, fill }, closed); - } - else if (nextMatch === secondMatch) { - // we matched a single chain - const index = firstMatch.index; - log === null || log === void 0 ? void 0 : log.chainMatch(index, closed); - // add the other point to the apporpriate end - const { segs: chain, fill } = chains[index]; - if (firstMatch.matchesHead) { - if (firstMatch.matchesPt1) { - seg = seg.reverse(); - log === null || log === void 0 ? void 0 : log.chainAddHead(index, { seg, fill }, closed); - chain.unshift(seg); - } - else { - log === null || log === void 0 ? void 0 : log.chainAddHead(index, { seg, fill }, closed); - chain.unshift(seg); - } + if (startMatch != null && endMatch != null) { + // otherwise, we matched two chains, so we need to combine those chains together + log === null || log === void 0 ? void 0 : log.chainConnect(startMatch, endMatch, closed); + // index1 gets index2 appended to it, and index2 is removed + const chain1 = chains[startMatch]; + const chain2 = chains[endMatch]; + // add seg to chain1's tail and simplify + const next = chain1[chain1.length - 1]; + const newEnd = joinSegments(next, seg, geo); + if (newEnd != null) { + chain1[chain1.length - 1] = newEnd; + log === null || log === void 0 ? void 0 : log.chainSimplifyTail(startMatch, newEnd, closed); } else { - if (firstMatch.matchesPt1) { - log === null || log === void 0 ? void 0 : log.chainAddTail(index, { seg, fill }, closed); - chain.push(seg); - } - else { - seg = seg.reverse(); - log === null || log === void 0 ? void 0 : log.chainAddTail(index, { seg, fill }, closed); - chain.push(seg); - } + chain1.push(seg); } - // simplify chain - if (firstMatch.matchesHead) { - const next = chain[1]; - const newSeg = joinSegments(seg, next, geo); - if (newSeg) { - chain.shift(); - chain[0] = newSeg; - log === null || log === void 0 ? void 0 : log.chainSimplifyHead(index, { seg: newSeg, fill }, closed); + // simplify chain2's head + const tail = chain1[chain1.length - 1]; + const head = chain2[0]; + const newJoin = joinSegments(tail, head, geo); + if (newJoin != null) { + chain2.shift(); + chain1[chain1.length - 1] = newJoin; + log === null || log === void 0 ? void 0 : log.chainSimplifyJoin(startMatch, endMatch, newJoin, closed); + } + if (startMatch === endMatch) { + if (chain1.length > 0) { + // we have a closed chain! + log === null || log === void 0 ? void 0 : log.chainClose(startMatch, closed); + regions.push(chain1); } } else { - const next = chain[chain.length - 2]; - const newSeg = joinSegments(next, seg, geo); - if (newSeg) { - chain.pop(); - chain[chain.length - 1] = newSeg; - log === null || log === void 0 ? void 0 : log.chainSimplifyTail(index, { seg: newSeg, fill }, closed); - } + log === null || log === void 0 ? void 0 : log.chainJoin(startMatch, endMatch, closed); + chains[startMatch] = chain1.concat(chain2); } - // check for closed chain - if (closed) { - let finalChain = chain; - let segS = finalChain[0]; - let segE = finalChain[finalChain.length - 1]; - if (finalChain.length > 0 && - geo.isEqualVec2(segS.start(), segE.end())) { - // see if chain is clockwise - let winding = 0; - let last = finalChain[0].start(); - for (const seg of finalChain) { - const here = seg.end(); - winding += here[1] * last[0] - here[0] * last[1]; - last = here; - } - // this assumes Cartesian coordinates (Y is positive going up) - const isClockwise = winding < 0; - if (isClockwise === fill) { - finalChain = reverseChain(index); - segS = finalChain[0]; - segE = finalChain[finalChain.length - 1]; - } - const newStart = joinSegments(segE, segS, geo); - if (newStart) { - finalChain.pop(); - finalChain[0] = newStart; - log === null || log === void 0 ? void 0 : log.chainSimplifyClose(index, { seg: newStart, fill }, closed); - } - // we have a closed chain! - log === null || log === void 0 ? void 0 : log.chainClose(index, closed); - chains.splice(index, 1); - regions.push(finalChain); - } + chains.splice(endMatch, 1); + } + else if (startMatch != null) { + // we matched a single chain at the start + log === null || log === void 0 ? void 0 : log.chainMatch(startMatch, closed); + const chain = chains[startMatch]; + const next = chain[chain.length - 1]; + const newSeg = joinSegments(next, seg, geo); + log === null || log === void 0 ? void 0 : log.chainAddTail(startMatch, seg, closed); + if (newSeg != null) { + chain[chain.length - 1] = newSeg; + } + else { + chain.push(seg); } } - else { - // otherwise, we matched two chains, so we need to combine those chains together - const appendChain = (index1, index2) => { - // index1 gets index2 appended to it, and index2 is removed - const { segs: chain1, fill } = chains[index1]; - const { segs: chain2 } = chains[index2]; - // add seg to chain1's tail - log === null || log === void 0 ? void 0 : log.chainAddTail(index1, { seg, fill }, closed); - chain1.push(seg); - // simplify chain1's tail - const next = chain1[chain1.length - 2]; - const newEnd = joinSegments(next, seg, geo); - if (newEnd) { - chain1.pop(); - chain1[chain1.length - 1] = newEnd; - log === null || log === void 0 ? void 0 : log.chainSimplifyTail(index1, { seg: newEnd, fill }, closed); - } - // simplify chain2's head - const tail = chain1[chain1.length - 1]; - const head = chain2[0]; - const newJoin = joinSegments(tail, head, geo); - if (newJoin) { - chain2.shift(); - chain1[chain1.length - 1] = newJoin; - log === null || log === void 0 ? void 0 : log.chainSimplifyJoin(index1, index2, { seg: newJoin, fill }, closed); - } - log === null || log === void 0 ? void 0 : log.chainJoin(index1, index2, closed); - chains[index1].segs = chain1.concat(chain2); - chains.splice(index2, 1); - }; - const F = firstMatch.index; - const S = secondMatch.index; - log === null || log === void 0 ? void 0 : log.chainConnect(F, S, closed); - // reverse the shorter chain, if needed - const reverseF = chains[F].segs.length < chains[S].segs.length; - if (firstMatch.matchesHead) { - if (secondMatch.matchesHead) { - if (reverseF) { - if (!firstMatch.matchesPt1) { - // <<<< F <<<< <-- >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< --> >>>> S >>>> - reverseChain(F); - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } - else { - if (firstMatch.matchesPt1) { - // <<<< F <<<< --> >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< <-- >>>> S >>>> - reverseChain(S); - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } - } - else { - if (firstMatch.matchesPt1) { - // <<<< F <<<< --> >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } + else if (endMatch != null) { + // we matched a single chain at the end + log === null || log === void 0 ? void 0 : log.chainMatch(endMatch, closed); + const chain = chains[endMatch]; + const next = chain[0]; + const newSeg = joinSegments(seg, next, geo); + log === null || log === void 0 ? void 0 : log.chainAddHead(endMatch, seg, closed); + if (newSeg != null) { + chain[0] = newSeg; } else { - if (secondMatch.matchesHead) { - if (!firstMatch.matchesPt1) { - // >>>> F >>>> <-- >>>> S >>>> - seg = seg.reverse(); - } - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } - else { - if (reverseF) { - if (firstMatch.matchesPt1) { - // >>>> F >>>> --> <<<< S <<<< - seg = seg.reverse(); - } - // >>>> F >>>> <-- <<<< S <<<< - reverseChain(F); - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } - else { - if (!firstMatch.matchesPt1) { - // >>>> F >>>> <-- <<<< S <<<< - seg = seg.reverse(); - } - // >>>> F >>>> --> <<<< S <<<< - reverseChain(S); - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } - } + chain.unshift(seg); } } + else { + // we didn't match anything, so create a new chain + chains.push([seg]); + log === null || log === void 0 ? void 0 : log.chainNew(seg, closed); + } } - for (const { segs } of openChains) { - regions.push(segs); - } + regions.push(...openChains); return regions; } function segmentsToReceiver(segments, geo, receiver, matrix) { @@ -2231,11 +2058,11 @@ class BuildLog { selected(segs) { this.push("selected", { segs }); } - chainStart(sf, closed) { - this.push("chain_start", { sf, closed }); + chainStart(seg, closed) { + this.push("chain_start", { sf: { seg, fill: true }, closed }); } - chainNew(sf, closed) { - this.push("chain_new", { sf, closed }); + chainNew(seg, closed) { + this.push("chain_new", { sf: { seg, fill: true }, closed }); } chainMatch(index, closed) { this.push("chain_match", { index, closed }); @@ -2243,23 +2070,23 @@ class BuildLog { chainClose(index, closed) { this.push("chain_close", { index, closed }); } - chainAddHead(index, sf, closed) { - this.push("chain_add_head", { index, sf, closed }); + chainAddHead(index, seg, closed) { + this.push("chain_add_head", { index, sf: { seg, fill: true }, closed }); } - chainAddTail(index, sf, closed) { - this.push("chain_add_tail", { index, sf, closed }); + chainAddTail(index, seg, closed) { + this.push("chain_add_tail", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyHead(index, sf, closed) { - this.push("chain_simp_head", { index, sf, closed }); + chainSimplifyHead(index, seg, closed) { + this.push("chain_simp_head", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyTail(index, sf, closed) { - this.push("chain_simp_tail", { index, sf, closed }); + chainSimplifyTail(index, seg, closed) { + this.push("chain_simp_tail", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyClose(index, sf, closed) { - this.push("chain_simp_close", { index, sf, closed }); + chainSimplifyClose(index, seg, closed) { + this.push("chain_simp_close", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyJoin(index1, index2, sf, closed) { - this.push("chain_simp_join", { index1, index2, sf, closed }); + chainSimplifyJoin(index1, index2, seg, closed) { + this.push("chain_simp_join", { index1, index2, sf: { seg, fill: true }, closed }); } chainConnect(index1, index2, closed) { this.push("chain_con", { index1, index2, closed }); diff --git a/dist/polybool.d.ts b/dist/polybool.d.ts index 678d189..a4d1d47 100644 --- a/dist/polybool.d.ts +++ b/dist/polybool.d.ts @@ -30,9 +30,9 @@ interface IPolyBoolReceiver { bezierCurveTo: (cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number) => void; closePath: () => void; } -declare function joinLines(seg1: SegmentLine, seg2: SegmentLine, geo: Geometry): SegmentLine | false; -declare function joinCurves(seg1: SegmentCurve, seg2: SegmentCurve, geo: Geometry): SegmentCurve | false; -declare function joinSegments(seg1: Segment | undefined, seg2: Segment | undefined, geo: Geometry): Segment | false; +declare function joinLines(seg1: SegmentLine, seg2: SegmentLine, geo: Geometry): SegmentLine | undefined; +declare function joinCurves(seg1: SegmentCurve, seg2: SegmentCurve, geo: Geometry): SegmentCurve | undefined; +declare function joinSegments(seg1: Segment | undefined, seg2: Segment | undefined, geo: Geometry): Segment | undefined; declare function SegmentChainer(segments: SegmentBool[], geo: Geometry, log: BuildLog | null): Segment[][]; declare function segmentsToReceiver(segments: Segment[][], geo: Geometry, receiver: T, matrix: Vec6): T; @@ -197,10 +197,6 @@ declare class Intersecter { calculate(): SegmentBool[]; } -interface ISegFill { - seg: Segment; - fill: boolean; -} declare class BuildLog { list: Array<{ type: string; @@ -222,16 +218,16 @@ declare class BuildLog { status(seg: SegmentBool, above: SegmentBool | false, below: SegmentBool | false): void; vert(x: number): void; selected(segs: SegmentBool[]): void; - chainStart(sf: ISegFill, closed: boolean): void; - chainNew(sf: ISegFill, closed: boolean): void; + chainStart(seg: Segment, closed: boolean): void; + chainNew(seg: Segment, closed: boolean): void; chainMatch(index: number, closed: boolean): void; chainClose(index: number, closed: boolean): void; - chainAddHead(index: number, sf: ISegFill, closed: boolean): void; - chainAddTail(index: number, sf: ISegFill, closed: boolean): void; - chainSimplifyHead(index: number, sf: ISegFill, closed: boolean): void; - chainSimplifyTail(index: number, sf: ISegFill, closed: boolean): void; - chainSimplifyClose(index: number, sf: ISegFill, closed: boolean): void; - chainSimplifyJoin(index1: number, index2: number, sf: ISegFill, closed: boolean): void; + chainAddHead(index: number, seg: Segment, closed: boolean): void; + chainAddTail(index: number, seg: Segment, closed: boolean): void; + chainSimplifyHead(index: number, seg: Segment, closed: boolean): void; + chainSimplifyTail(index: number, seg: Segment, closed: boolean): void; + chainSimplifyClose(index: number, seg: Segment, closed: boolean): void; + chainSimplifyJoin(index1: number, index2: number, seg: Segment, closed: boolean): void; chainConnect(index1: number, index2: number, closed: boolean): void; chainReverse(index: number, closed: boolean): void; chainJoin(index1: number, index2: number, closed: boolean): void; diff --git a/dist/polybool.js b/dist/polybool.js index 0ccad06..bc383b9 100644 --- a/dist/polybool.js +++ b/dist/polybool.js @@ -1502,7 +1502,6 @@ function joinLines(seg1, seg2, geo) { if (geo.isCollinear(seg1.p0, seg1.p1, seg2.p1)) { return new SegmentLine(seg1.p0, seg2.p1, geo); } - return false; } function joinCurves(seg1, seg2, geo) { if (geo.isCollinear(seg1.p2, seg1.p3, seg2.p1)) { @@ -1527,294 +1526,122 @@ function joinCurves(seg1, seg2, geo) { } } } - return false; } function joinSegments(seg1, seg2, geo) { - if (seg1 === seg2) { - return false; - } - if (seg1 instanceof SegmentLine && seg2 instanceof SegmentLine) { - return joinLines(seg1, seg2, geo); - } - if (seg1 instanceof SegmentCurve && seg2 instanceof SegmentCurve) { - return joinCurves(seg1, seg2, geo); + if (seg1 !== seg2) { + if (seg1 instanceof SegmentLine && seg2 instanceof SegmentLine) { + return joinLines(seg1, seg2, geo); + } + if (seg1 instanceof SegmentCurve && seg2 instanceof SegmentCurve) { + return joinCurves(seg1, seg2, geo); + } } - return false; } function SegmentChainer(segments, geo, log) { const closedChains = []; const openChains = []; const regions = []; for (const segb of segments) { - let seg = segb.data; + const seg = segb.myFill.above ? segb.data : segb.data.reverse(); const closed = segb.closed; const chains = closed ? closedChains : openChains; const pt1 = seg.start(); const pt2 = seg.end(); - const reverseChain = (index) => { - log === null || log === void 0 ? void 0 : log.chainReverse(index, closed); - const newChain = []; - for (const seg of chains[index].segs) { - newChain.unshift(seg.reverse()); - } - chains[index] = { - segs: newChain, - fill: !chains[index].fill, - }; - return newChain; - }; if (seg instanceof SegmentLine && geo.isEqualVec2(pt1, pt2)) { console.warn("PolyBool: Warning: Zero-length segment detected; your epsilon is " + "probably too small or too large"); continue; } - log === null || log === void 0 ? void 0 : log.chainStart({ seg, fill: !!segb.myFill.above }, closed); - // search for two chains that this segment matches - const firstMatch = { - index: 0, - matchesHead: false, - matchesPt1: false, - }; - const secondMatch = { - index: 0, - matchesHead: false, - matchesPt1: false, - }; - let nextMatch = firstMatch; - function setMatch(index, matchesHead, matchesPt1) { - // return true if we've matched twice - if (nextMatch) { - nextMatch.index = index; - nextMatch.matchesHead = matchesHead; - nextMatch.matchesPt1 = matchesPt1; - } - if (nextMatch === firstMatch) { - nextMatch = secondMatch; - return false; - } - nextMatch = null; - return true; // we've matched twice, we're done here - } + log === null || log === void 0 ? void 0 : log.chainStart(seg, closed); + let startMatch; + let endMatch; for (let i = 0; i < chains.length; i++) { - const chain = chains[i].segs; + const chain = chains[i]; const head = chain[0].start(); const tail = chain[chain.length - 1].end(); - if (geo.isEqualVec2(head, pt1)) { - if (setMatch(i, true, true)) { - break; - } + if (startMatch == null && geo.isEqualVec2(tail, pt1)) { + startMatch = i; } - else if (geo.isEqualVec2(head, pt2)) { - if (setMatch(i, true, false)) { - break; - } + if (endMatch == null && geo.isEqualVec2(head, pt2)) { + endMatch = i; } - else if (geo.isEqualVec2(tail, pt1)) { - if (setMatch(i, false, true)) { - break; - } - } - else if (geo.isEqualVec2(tail, pt2)) { - if (setMatch(i, false, false)) { - break; - } + if (startMatch && endMatch) { + break; } } - if (nextMatch === firstMatch) { - // we didn't match anything, so create a new chain - const fill = !!segb.myFill.above; - chains.push({ segs: [seg], fill }); - log === null || log === void 0 ? void 0 : log.chainNew({ seg, fill }, closed); - } - else if (nextMatch === secondMatch) { - // we matched a single chain - const index = firstMatch.index; - log === null || log === void 0 ? void 0 : log.chainMatch(index, closed); - // add the other point to the apporpriate end - const { segs: chain, fill } = chains[index]; - if (firstMatch.matchesHead) { - if (firstMatch.matchesPt1) { - seg = seg.reverse(); - log === null || log === void 0 ? void 0 : log.chainAddHead(index, { seg, fill }, closed); - chain.unshift(seg); - } - else { - log === null || log === void 0 ? void 0 : log.chainAddHead(index, { seg, fill }, closed); - chain.unshift(seg); - } + if (startMatch != null && endMatch != null) { + // otherwise, we matched two chains, so we need to combine those chains together + log === null || log === void 0 ? void 0 : log.chainConnect(startMatch, endMatch, closed); + // index1 gets index2 appended to it, and index2 is removed + const chain1 = chains[startMatch]; + const chain2 = chains[endMatch]; + // add seg to chain1's tail and simplify + const next = chain1[chain1.length - 1]; + const newEnd = joinSegments(next, seg, geo); + if (newEnd != null) { + chain1[chain1.length - 1] = newEnd; + log === null || log === void 0 ? void 0 : log.chainSimplifyTail(startMatch, newEnd, closed); } else { - if (firstMatch.matchesPt1) { - log === null || log === void 0 ? void 0 : log.chainAddTail(index, { seg, fill }, closed); - chain.push(seg); - } - else { - seg = seg.reverse(); - log === null || log === void 0 ? void 0 : log.chainAddTail(index, { seg, fill }, closed); - chain.push(seg); - } + chain1.push(seg); } - // simplify chain - if (firstMatch.matchesHead) { - const next = chain[1]; - const newSeg = joinSegments(seg, next, geo); - if (newSeg) { - chain.shift(); - chain[0] = newSeg; - log === null || log === void 0 ? void 0 : log.chainSimplifyHead(index, { seg: newSeg, fill }, closed); + // simplify chain2's head + const tail = chain1[chain1.length - 1]; + const head = chain2[0]; + const newJoin = joinSegments(tail, head, geo); + if (newJoin != null) { + chain2.shift(); + chain1[chain1.length - 1] = newJoin; + log === null || log === void 0 ? void 0 : log.chainSimplifyJoin(startMatch, endMatch, newJoin, closed); + } + if (startMatch === endMatch) { + if (chain1.length > 0) { + // we have a closed chain! + log === null || log === void 0 ? void 0 : log.chainClose(startMatch, closed); + regions.push(chain1); } } else { - const next = chain[chain.length - 2]; - const newSeg = joinSegments(next, seg, geo); - if (newSeg) { - chain.pop(); - chain[chain.length - 1] = newSeg; - log === null || log === void 0 ? void 0 : log.chainSimplifyTail(index, { seg: newSeg, fill }, closed); - } + log === null || log === void 0 ? void 0 : log.chainJoin(startMatch, endMatch, closed); + chains[startMatch] = chain1.concat(chain2); } - // check for closed chain - if (closed) { - let finalChain = chain; - let segS = finalChain[0]; - let segE = finalChain[finalChain.length - 1]; - if (finalChain.length > 0 && - geo.isEqualVec2(segS.start(), segE.end())) { - // see if chain is clockwise - let winding = 0; - let last = finalChain[0].start(); - for (const seg of finalChain) { - const here = seg.end(); - winding += here[1] * last[0] - here[0] * last[1]; - last = here; - } - // this assumes Cartesian coordinates (Y is positive going up) - const isClockwise = winding < 0; - if (isClockwise === fill) { - finalChain = reverseChain(index); - segS = finalChain[0]; - segE = finalChain[finalChain.length - 1]; - } - const newStart = joinSegments(segE, segS, geo); - if (newStart) { - finalChain.pop(); - finalChain[0] = newStart; - log === null || log === void 0 ? void 0 : log.chainSimplifyClose(index, { seg: newStart, fill }, closed); - } - // we have a closed chain! - log === null || log === void 0 ? void 0 : log.chainClose(index, closed); - chains.splice(index, 1); - regions.push(finalChain); - } + chains.splice(endMatch, 1); + } + else if (startMatch != null) { + // we matched a single chain at the start + log === null || log === void 0 ? void 0 : log.chainMatch(startMatch, closed); + const chain = chains[startMatch]; + const next = chain[chain.length - 1]; + const newSeg = joinSegments(next, seg, geo); + log === null || log === void 0 ? void 0 : log.chainAddTail(startMatch, seg, closed); + if (newSeg != null) { + chain[chain.length - 1] = newSeg; + } + else { + chain.push(seg); } } - else { - // otherwise, we matched two chains, so we need to combine those chains together - const appendChain = (index1, index2) => { - // index1 gets index2 appended to it, and index2 is removed - const { segs: chain1, fill } = chains[index1]; - const { segs: chain2 } = chains[index2]; - // add seg to chain1's tail - log === null || log === void 0 ? void 0 : log.chainAddTail(index1, { seg, fill }, closed); - chain1.push(seg); - // simplify chain1's tail - const next = chain1[chain1.length - 2]; - const newEnd = joinSegments(next, seg, geo); - if (newEnd) { - chain1.pop(); - chain1[chain1.length - 1] = newEnd; - log === null || log === void 0 ? void 0 : log.chainSimplifyTail(index1, { seg: newEnd, fill }, closed); - } - // simplify chain2's head - const tail = chain1[chain1.length - 1]; - const head = chain2[0]; - const newJoin = joinSegments(tail, head, geo); - if (newJoin) { - chain2.shift(); - chain1[chain1.length - 1] = newJoin; - log === null || log === void 0 ? void 0 : log.chainSimplifyJoin(index1, index2, { seg: newJoin, fill }, closed); - } - log === null || log === void 0 ? void 0 : log.chainJoin(index1, index2, closed); - chains[index1].segs = chain1.concat(chain2); - chains.splice(index2, 1); - }; - const F = firstMatch.index; - const S = secondMatch.index; - log === null || log === void 0 ? void 0 : log.chainConnect(F, S, closed); - // reverse the shorter chain, if needed - const reverseF = chains[F].segs.length < chains[S].segs.length; - if (firstMatch.matchesHead) { - if (secondMatch.matchesHead) { - if (reverseF) { - if (!firstMatch.matchesPt1) { - // <<<< F <<<< <-- >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< --> >>>> S >>>> - reverseChain(F); - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } - else { - if (firstMatch.matchesPt1) { - // <<<< F <<<< --> >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< <-- >>>> S >>>> - reverseChain(S); - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } - } - else { - if (firstMatch.matchesPt1) { - // <<<< F <<<< --> >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } + else if (endMatch != null) { + // we matched a single chain at the end + log === null || log === void 0 ? void 0 : log.chainMatch(endMatch, closed); + const chain = chains[endMatch]; + const next = chain[0]; + const newSeg = joinSegments(seg, next, geo); + log === null || log === void 0 ? void 0 : log.chainAddHead(endMatch, seg, closed); + if (newSeg != null) { + chain[0] = newSeg; } else { - if (secondMatch.matchesHead) { - if (!firstMatch.matchesPt1) { - // >>>> F >>>> <-- >>>> S >>>> - seg = seg.reverse(); - } - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } - else { - if (reverseF) { - if (firstMatch.matchesPt1) { - // >>>> F >>>> --> <<<< S <<<< - seg = seg.reverse(); - } - // >>>> F >>>> <-- <<<< S <<<< - reverseChain(F); - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } - else { - if (!firstMatch.matchesPt1) { - // >>>> F >>>> <-- <<<< S <<<< - seg = seg.reverse(); - } - // >>>> F >>>> --> <<<< S <<<< - reverseChain(S); - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } - } + chain.unshift(seg); } } + else { + // we didn't match anything, so create a new chain + chains.push([seg]); + log === null || log === void 0 ? void 0 : log.chainNew(seg, closed); + } } - for (const { segs } of openChains) { - regions.push(segs); - } + regions.push(...openChains); return regions; } function segmentsToReceiver(segments, geo, receiver, matrix) { @@ -2227,11 +2054,11 @@ class BuildLog { selected(segs) { this.push("selected", { segs }); } - chainStart(sf, closed) { - this.push("chain_start", { sf, closed }); + chainStart(seg, closed) { + this.push("chain_start", { sf: { seg, fill: true }, closed }); } - chainNew(sf, closed) { - this.push("chain_new", { sf, closed }); + chainNew(seg, closed) { + this.push("chain_new", { sf: { seg, fill: true }, closed }); } chainMatch(index, closed) { this.push("chain_match", { index, closed }); @@ -2239,23 +2066,23 @@ class BuildLog { chainClose(index, closed) { this.push("chain_close", { index, closed }); } - chainAddHead(index, sf, closed) { - this.push("chain_add_head", { index, sf, closed }); + chainAddHead(index, seg, closed) { + this.push("chain_add_head", { index, sf: { seg, fill: true }, closed }); } - chainAddTail(index, sf, closed) { - this.push("chain_add_tail", { index, sf, closed }); + chainAddTail(index, seg, closed) { + this.push("chain_add_tail", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyHead(index, sf, closed) { - this.push("chain_simp_head", { index, sf, closed }); + chainSimplifyHead(index, seg, closed) { + this.push("chain_simp_head", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyTail(index, sf, closed) { - this.push("chain_simp_tail", { index, sf, closed }); + chainSimplifyTail(index, seg, closed) { + this.push("chain_simp_tail", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyClose(index, sf, closed) { - this.push("chain_simp_close", { index, sf, closed }); + chainSimplifyClose(index, seg, closed) { + this.push("chain_simp_close", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyJoin(index1, index2, sf, closed) { - this.push("chain_simp_join", { index1, index2, sf, closed }); + chainSimplifyJoin(index1, index2, seg, closed) { + this.push("chain_simp_join", { index1, index2, sf: { seg, fill: true }, closed }); } chainConnect(index1, index2, closed) { this.push("chain_con", { index1, index2, closed }); diff --git a/dist/polybool.min.cjs b/dist/polybool.min.cjs index fdfa331..8949f9b 100644 --- a/dist/polybool.min.cjs +++ b/dist/polybool.min.cjs @@ -1 +1 @@ -"use strict";function t(t,e,s){return t+(e-t)*s}function e(e,s,n){return[t(e[0],s[0],n),t(e[1],s[1],n)]}function s(t,e){const[s,n]=t,[i,o]=e;return!(s[0]>o[0]||n[0]o[1]||n[1]0?[-2*e-t/3,e-t/3]:[-e-t/3,2*e-t/3]}const h=o*o*o,l=r*r;if(lt-e))}{const t=(r<0?1:-1)*Math.pow(Math.abs(r)+Math.sqrt(l-h),1/3);return[t+(Math.abs(t)>=this.epsilon?o/t:0)-n]}}solveCubic(t,e,s,n){if(Math.abs(t)0?(i=Math.sqrt(i),[(-s+i)/t,(-s-i)/t].sort(((t,e)=>t-e))):[]}return this.solveCubicNormalized(e/t,s/t,n/t)}isEqualVec2(t,e){return Math.abs(t[0]-e[0])1)return this;for(const e of this.tValues)if(0===this.geo.snap0(t-e))return this;return this.tValues.push(t),this}list(){return this.tValues.sort(((t,e)=>t-e)),this.tValues}}class r{constructor(t,e){this.tValuePairs=[],this.allowOutOfRange=t,this.geo=e}add(t,e){if(t=this.geo.snap01(t),e=this.geo.snap01(e),!this.allowOutOfRange&&(t<0||t>1||e<0||e>1))return this;for(const s of this.tValuePairs)if(0===this.geo.snap0(t-s[0])||0===this.geo.snap0(e-s[1]))return this;return this.tValuePairs.push([t,e]),this}list(){return this.tValuePairs.sort(((t,e)=>t[0]-e[0])),this.tValuePairs}done(){return this.tValuePairs.length<=0?null:{kind:"tValuePairs",tValuePairs:this.list()}}}class a{}class h extends a{constructor(t,e,s){super(),this.p0=t,this.p1=e,this.geo=s}copy(){return new h(this.p0,this.p1,this.geo)}isEqual(t){return this.geo.isEqualVec2(this.p0,t.p0)&&this.geo.isEqualVec2(this.p1,t.p1)}start(){return this.p0}start2(){return this.p1}end2(){return this.p0}end(){return this.p1}setStart(t){this.p0=t}setEnd(t){this.p1=t}point(t){const e=this.p0,s=this.p1;return 0===t?e:1===t?s:[e[0]+(s[0]-e[0])*t,e[1]+(s[1]-e[1])*t]}split(t){if(t.length<=0)return[this];const e=t.map((t=>this.point(t)));e.push(this.p1);const s=[];let n=this.p0;for(const t of e)s.push(new h(n,t,this.geo)),n=t;return s}reverse(){return new h(this.p1,this.p0,this.geo)}boundingBox(){const t=this.p0,e=this.p1;return[[Math.min(t[0],e[0]),Math.min(t[1],e[1])],[Math.max(t[0],e[0]),Math.max(t[1],e[1])]]}pointOn(t){return this.geo.isCollinear(t,this.p0,this.p1)}draw(t){const e=this.p0,s=this.p1;return t.moveTo(e[0],e[1]),t.lineTo(s[0],s[1]),t}}class l extends a{constructor(t,e,s,n,i){super(),this.p0=t,this.p1=e,this.p2=s,this.p3=n,this.geo=i}copy(){return new l(this.p0,this.p1,this.p2,this.p3,this.geo)}isEqual(t){return this.geo.isEqualVec2(this.p0,t.p0)&&this.geo.isEqualVec2(this.p1,t.p1)&&this.geo.isEqualVec2(this.p2,t.p2)&&this.geo.isEqualVec2(this.p3,t.p3)}start(){return this.p0}start2(){return this.p1}end2(){return this.p2}end(){return this.p3}setStart(t){this.p0=t}setEnd(t){this.p3=t}point(t){const e=this.p0,s=this.p1,n=this.p2,i=this.p3;if(0===t)return e;if(1===t)return i;const o=(1-t)*(1-t),r=t*t,a=o*(1-t),h=3*o*t,l=3*r*(1-t),c=r*t;return[e[0]*a+s[0]*h+n[0]*l+i[0]*c,e[1]*a+s[1]*h+n[1]*l+i[1]*c]}split(t){if(t.length<=0)return[this];const s=[],n=(t,n)=>{const[i,o,r,a]=t,h=e(i,o,n),c=e(o,r,n),u=e(r,a,n),p=e(h,c,n),d=e(c,u,n),g=e(p,d,n);return s.push(new l(i,h,p,g,this.geo)),[g,d,u,a]};let i=[this.p0,this.p1,this.p2,this.p3],o=0;for(const e of t)i=n(i,(e-o)/(1-o)),o=e;return s.push(new l(i[0],i[1],i[2],i[3],this.geo)),s}reverse(){return new l(this.p3,this.p2,this.p1,this.p0,this.geo)}getCubicCoefficients(t){const e=this.p0[t],s=this.p1[t],n=this.p2[t];return[this.p3[t]-3*n+3*s-e,3*n-6*s+3*e,3*s-3*e,e]}boundingTValues(){const t=new o(this.geo),e=(e,s,n,i)=>{const o=3*i-9*n+9*s-3*e,r=6*e-12*s+6*n,a=3*s-3*e;if(0===this.geo.snap0(o))t.add(-a/r);else{const e=r*r-4*o*a;if(e>=0){const s=Math.sqrt(e);t.add((-r+s)/(2*o)),t.add((-r-s)/(2*o))}}return t},s=this.p0,n=this.p1,i=this.p2,r=this.p3;return e(s[0],n[0],i[0],r[0]),e(s[1],n[1],i[1],r[1]),t.list()}inflectionTValues(){const t=new o(this.geo);t.addArray(this.boundingTValues());const e=this.p0,s=this.p1,n=this.p2,i=this.p3,r=3*(s[0]-e[0]),a=3*(s[1]-e[1]),h=6*(n[0]-s[0]),l=6*(n[1]-s[1]),c=3*(i[0]-n[0]),u=3*(i[1]-n[1]),p=6*(n[0]-2*s[0]+e[0]),d=6*(n[1]-2*s[1]+e[1]),g=r-h+c,f=a-l+u,m=h-2*r,v=l-2*a,w=6*(i[0]-2*n[0]+s[0])-p,b=6*(i[1]-2*n[1]+s[1])-d,S=g*b-f*w,x=g*d+m*b-f*p-v*w,P=m*d+r*b-v*p-a*w,y=r*d-a*p;for(const e of this.geo.solveCubic(S,x,P,y))t.add(e);return t.list()}boundingBox(){const t=this.p0,e=this.p3,s=[Math.min(t[0],e[0]),Math.min(t[1],e[1])],n=[Math.max(t[0],e[0]),Math.max(t[1],e[1])];for(const t of this.boundingTValues()){const e=this.point(t);s[0]=Math.min(s[0],e[0]),s[1]=Math.min(s[1],e[1]),n[0]=Math.max(n[0],e[0]),n[1]=Math.max(n[1],e[1])}return[s,n]}mapXtoT(t,e=!1){if(0===this.geo.snap0(this.p0[0]-t))return 0;if(0===this.geo.snap0(this.p3[0]-t))return 1;const s=this.p0[0]-t,n=this.p1[0]-t,i=this.p2[0]-t,o=[this.p3[0]-t-3*i+3*n-s,3*i-6*n+3*s,3*n-3*s,s];for(const t of this.geo.solveCubic(o[0],o[1],o[2],o[3])){const e=this.geo.snap01(t);if(e>=0&&e<=1)return t}if(e||t>=Math.min(this.p0[0],this.p3[0])&&t<=Math.max(this.p0[0],this.p3[0]))for(let t=0;t<4;t++){let t=-1;for(let e=0;e<4;e++)0!==o[e]&&(t<0||Math.abs(o[e])=0&&e<=1)return t}}return!1}mapXtoY(t,e=!1){const s=this.mapXtoT(t,e);return!1!==s&&this.point(s)[1]}pointOn(t){if(this.geo.isEqualVec2(this.p0,t)||this.geo.isEqualVec2(this.p3,t))return!0;const e=this.mapXtoY(t[0]);return!1!==e&&0===this.geo.snap0(e-t[1])}toLine(){const t=this.p0,e=this.p1,s=this.p2,n=this.p3;return 0===this.geo.snap0(t[0]-e[0])&&0===this.geo.snap0(t[0]-s[0])&&0===this.geo.snap0(t[0]-n[0])||0===this.geo.snap0(t[1]-e[1])&&0===this.geo.snap0(t[1]-s[1])&&0===this.geo.snap0(t[1]-n[1])?new h(t,n,this.geo):null}draw(t){const e=this.p0,s=this.p1,n=this.p2,i=this.p3;return t.moveTo(e[0],e[1]),t.bezierCurveTo(s[0],s[1],n[0],n[1],i[0],i[1]),t}}function c(t,e){const s=e.p1[0]-e.p0[0],n=e.p1[1]-e.p0[1];return((t[0]-e.p0[0])*s+(t[1]-e.p0[1])*n)/(s*s+n*n)}function u(t,e,s){const n=t.geo,i=t.p0,o=t.p1,a=e.p0,h=e.p1,l=o[0]-i[0],u=o[1]-i[1],p=h[0]-a[0],d=h[1]-a[1],g=l*d-u*p;if(0===n.snap0(g)){if(!n.isCollinear(i,o,a))return null;const s=c(e.p0,t),r=c(e.p1,t),h=n.snap01(Math.min(s,r)),l=n.snap01(Math.max(s,r));if(l<0||h>1)return null;const u=c(t.p0,e),p=c(t.p1,e),d=n.snap01(Math.min(u,p)),g=n.snap01(Math.max(u,p));return g<0||d>1?null:{kind:"tRangePairs",tStart:[Math.max(0,h),Math.max(0,d)],tEnd:[Math.min(1,l),Math.min(1,g)]}}const f=i[0]-a[0],m=i[1]-a[1];return new r(s,n).add((p*m-d*f)/g,(l*m-u*f)/g).done()}function p(t,e,s,n){const i=t.geo,o=t.p0,a=t.p1,h=a[1]-o[1],l=o[0]-a[0];if(0===i.snap0(l)){const t=e.mapXtoT(o[0],!1);if(!1===t)return null;const a=(e.point(t)[1]-o[1])/h,l=new r(s,i);return n?l.add(t,a):l.add(a,t),l.done()}const c=h*o[0]+l*o[1],u=e.getCubicCoefficients(0),p=e.getCubicCoefficients(1),d=h*u[0]+l*p[0],g=h*u[1]+l*p[1],f=h*u[2]+l*p[2],m=h*u[3]+l*p[3]-c,v=i.solveCubic(d,g,f,m),w=new r(s,i);if(0===i.snap0(h))for(const t of v){const e=u[0]*t*t*t+u[1]*t*t+u[2]*t+u[3],s=(o[0]-e)/l;n?w.add(t,s):w.add(s,t)}else for(const t of v){const e=(p[0]*t*t*t+p[1]*t*t+p[2]*t+p[3]-o[1])/h;n?w.add(t,e):w.add(e,t)}return w.done()}function d(t,e,n){const i=t.geo;if(i.isEqualVec2(t.p0,e.p0))return i.isEqualVec2(t.p3,e.p3)?i.isEqualVec2(t.p1,e.p1)&&i.isEqualVec2(t.p2,e.p2)?{kind:"tRangePairs",tStart:[0,0],tEnd:[1,1]}:{kind:"tValuePairs",tValuePairs:[[0,0],[1,1]]}:{kind:"tValuePairs",tValuePairs:[[0,0]]};if(i.isEqualVec2(t.p0,e.p3))return{kind:"tValuePairs",tValuePairs:[[0,1]]};if(i.isEqualVec2(t.p3,e.p0))return{kind:"tValuePairs",tValuePairs:[[1,0]]};if(i.isEqualVec2(t.p3,e.p3))return{kind:"tValuePairs",tValuePairs:[[1,1]]};const o=new r(n,i),a=(t,e,n,r,h,l)=>{if(!s(t.boundingBox(),r.boundingBox()))return;const c=(e+n)/2,u=(h+l)/2;if(0===i.snap0(n-e)&&0===i.snap0(l-h))return void o.add(c,u);const[p,d]=t.split([.5]),[g,f]=r.split([.5]);a(p,e,c,g,h,u),a(d,c,n,g,h,u),a(p,e,c,f,u,l),a(d,c,n,f,u,l)};return a(t,0,1,e,0,1),o.done()}function g(t,e,s){if(t instanceof h){if(e instanceof h)return u(t,e,s);if(e instanceof l)return p(t,e,s,!1)}else if(t instanceof l){if(e instanceof h)return p(e,t,s,!0);if(e instanceof l)return d(t,e,s)}throw new Error("PolyBool: Unknown segment instance in segmentsIntersect")}class f{constructor(t,e=null,s=!1,n=null){var i,o,r;this.otherFill=null,this.id=null!==(i=null==n?void 0:n.segmentId())&&void 0!==i?i:-1,this.data=t,this.myFill={above:null!==(o=null==e?void 0:e.above)&&void 0!==o?o:null,below:null!==(r=null==e?void 0:e.below)&&void 0!==r?r:null},this.closed=s}}class m extends f{}class v extends f{}function w(t,e){if(t instanceof m)return new m(t.data,t.myFill,t.closed,e);if(t instanceof v)return new v(t.data,t.myFill,t.closed,e);throw new Error("PolyBool: Unknown SegmentBool in copySegmentBool")}class b{constructor(t,e,s,n){this.status=null,this.isStart=t,this.p=e,this.seg=s,this.primary=n}}class S{constructor(){this.nodes=[]}remove(t){const e=this.nodes.indexOf(t);e>=0&&this.nodes.splice(e,1)}getIndex(t){return this.nodes.indexOf(t)}isEmpty(){return this.nodes.length<=0}getHead(){return this.nodes[0]}removeHead(){this.nodes.shift()}insertBefore(t,e){this.findTransition(t,e).insert(t)}findTransition(t,e){var s,n;let i=0,o=this.nodes.length;for(;i>1;r=this.nodes[s],e(t)-e(r)>0?o=s:i=s+1}var r;return{before:i<=0?null:null!==(s=this.nodes[i-1])&&void 0!==s?s:null,after:null!==(n=this.nodes[i])&&void 0!==n?n:null,insert:t=>(this.nodes.splice(i,0,t),t)}}}class x{constructor(t,e,s=null){this.events=new S,this.status=new S,this.currentPath=[],this.selfIntersection=t,this.geo=e,this.log=s}compareEvents(t,e,s,n,i,o,r,a){const l=this.geo.compareVec2(e,o);return 0!==l?l:n instanceof h&&a instanceof h&&this.geo.isEqualVec2(s,r)?0:t!==i?t?1:-1:this.compareSegments(a,n)}addEvent(t){this.events.insertBefore(t,(e=>e===t?0:this.compareEvents(t.isStart,t.p,t.other.p,t.seg.data,e.isStart,e.p,e.other.p,e.seg.data)))}divideEvent(t,e,s){var n,i;null===(n=this.log)||void 0===n||n.segmentDivide(t.seg,s);const[o,r]=t.seg.data.split([e]);o.setEnd(s),r.setStart(s);const a=r instanceof h?new m(r,t.seg.myFill,t.seg.closed,this.log):r instanceof l?new v(r,t.seg.myFill,t.seg.closed,this.log):null;if(!a)throw new Error("PolyBool: Unknown segment data in divideEvent");return this.events.remove(t.other),t.seg.data=o,null===(i=this.log)||void 0===i||i.segmentChop(t.seg),t.other.p=s,this.addEvent(t.other),this.addSegment(a,t.primary)}beginPath(){this.currentPath=[]}closePath(){for(const t of this.currentPath)t.closed=!0}addSegment(t,e){const s=new b(!0,t.data.start(),t,e),n=new b(!1,t.data.end(),t,e);return s.other=n,n.other=s,this.addEvent(s),this.addEvent(n),s}addLine(t,e,s=!0){const n=this.geo.compareVec2(t,e);if(0===n)return;const i=new m(new h(n<0?t:e,n<0?e:t,this.geo),null,!1,this.log);this.currentPath.push(i),this.addSegment(i,s)}addCurve(t,e,s,n,i=!0){const o=new l(t,e,s,n,this.geo),r=o.split(o.inflectionTValues());for(const t of r){const e=this.geo.compareVec2(t.start(),t.end());if(0===e)continue;const s=t.toLine();if(s)this.addLine(s.p0,s.p1,i);else{const s=new v(e<0?t:t.reverse(),null,!1,this.log);this.currentPath.push(s),this.addSegment(s,i)}}}compareSegments(t,e){let s=t.start(),n=e.start2();const i=e.start();if(e.pointOn(s)){if(s=t.start2(),e.pointOn(s)){if(t instanceof h){if(e instanceof h)return 0;e instanceof l&&(s=t.point(.5))}t instanceof l&&(s=t.end())}if(e instanceof l&&0===this.geo.snap0(s[0]-i[0])&&0===this.geo.snap0(n[0]-i[0]))return Math.sign(i[1]-s[1])}else{if(e instanceof l){const t=e.mapXtoY(s[0],!0);if(!1!==t)return Math.sign(t-s[1])}if(t instanceof l){const s=g(t,e,!0);if(s&&"tValuePairs"===s.kind)for(const e of s.tValuePairs){const s=this.geo.snap01(e[0]);if(s>0&&s<1){n=t.point(s);break}}}}const[o,r]=s,[a,c]=n,[u,p]=i;return Math.sign((a-o)*(p-r)-(c-r)*(u-o))}statusFindSurrounding(t){return this.status.findTransition(t,(e=>{if(t===e)return 0;const s=this.compareSegments(t.seg.data,e.seg.data);return 0===s?-1:s}))}checkIntersection(t,e){var s;const n=t.seg,i=e.seg;null===(s=this.log)||void 0===s||s.checkIntersection(n,i);const o=g(n.data,i.data,!1);if(null===o)return null;if("tRangePairs"===o.kind){const{tStart:[s,r],tEnd:[a,h]}=o;if(1===s&&1===a&&0===r&&0===h||0===s&&0===a&&1===r&&1===h)return null;if(0===s&&1===a&&0===r&&1===h)return e;const l=n.data.start(),c=n.data.end(),u=i.data.end();return 0===s&&0===r?(1===a?this.divideEvent(e,h,c):this.divideEvent(t,a,u),e):(r>0&&r<1&&(1===a&&1===h||(1===a?this.divideEvent(e,h,c):this.divideEvent(t,a,u)),this.divideEvent(e,r,l)),null)}if("tValuePairs"===o.kind){if(o.tValuePairs.length<=0)return null;let s=o.tValuePairs[0];for(let t=1;t0&&r<1&&this.divideEvent(t,r,h),a>0&&a<1&&this.divideEvent(e,a,h),null}throw new Error("PolyBool: Unknown intersection type")}calculate(){var t,e,s,n,i,o,r,a;const h=[];for(;!this.events.isEmpty();){const a=this.events.getHead();if(null===(t=this.log)||void 0===t||t.vert(a.p[0]),a.isStart){null===(e=this.log)||void 0===e||e.segmentNew(a.seg,a.primary);const t=this.statusFindSurrounding(a),r=t.before,h=t.after;null===(s=this.log)||void 0===s||s.tempStatus(a.seg,!!r&&r.seg,!!h&&h.seg);const l=()=>{if(r){const t=this.checkIntersection(a,r);if(t)return t}return h?this.checkIntersection(a,h):null},c=l();if(c){if(this.selfIntersection){let t;t=null===a.seg.myFill.below?a.seg.closed:a.seg.myFill.above!==a.seg.myFill.below,t&&(c.seg.myFill.above=!c.seg.myFill.above)}else c.seg.otherFill=a.seg.myFill;null===(n=this.log)||void 0===n||n.segmentUpdate(c.seg),this.events.remove(a.other),this.events.remove(a)}if(this.events.getHead()!==a){null===(i=this.log)||void 0===i||i.rewind(a.seg);continue}if(this.selfIntersection){let t;t=null===a.seg.myFill.below?a.seg.closed:a.seg.myFill.above!==a.seg.myFill.below,a.seg.myFill.below=!!h&&h.seg.myFill.above,a.seg.myFill.above=t?!a.seg.myFill.below:a.seg.myFill.below}else if(null===a.seg.otherFill){let t;if(h)if(a.primary===h.primary){if(null===h.seg.otherFill)throw new Error("PolyBool: Unexpected state of otherFill (null)");t=h.seg.otherFill.above}else t=h.seg.myFill.above;else t=!1;a.seg.otherFill={above:t,below:t}}null===(o=this.log)||void 0===o||o.status(a.seg,!!r&&r.seg,!!h&&h.seg),a.other.status=t.insert(a)}else{const t=a.status;if(null===t)throw new Error("PolyBool: Zero-length segment detected; your epsilon is probably too small or too large");const e=this.status.getIndex(t);if(e>0&&eMath.abs(i)?(t.p3[0]-t.p2[0])/n:(t.p3[1]-t.p2[1])/i,r=s.snap01(o);if(0!==r&&1!==r){const n=new l(t.p0,[t.p0[0]+(t.p1[0]-t.p0[0])/o,t.p0[1]+(t.p1[1]-t.p0[1])/o],[e.p2[0]-o*(e.p3[0]-e.p2[0])/(1-o),e.p2[1]-o*(e.p3[1]-e.p2[1])/(1-o)],e.p3,s),[i,r]=n.split([o]);if(i.isEqual(t)&&r.isEqual(e))return n}}return!1}function V(t,e,s){return t!==e&&(t instanceof h&&e instanceof h?M(t,e,s):t instanceof l&&e instanceof l&&E(t,e,s))}function T(t,e,s){const n=[],i=[],o=[];for(const r of t){let a=r.data;const l=r.closed,c=l?n:i,u=a.start(),p=a.end(),d=t=>{null==s||s.chainReverse(t,l);const e=[];for(const s of c[t].segs)e.unshift(s.reverse());return c[t]={segs:e,fill:!c[t].fill},e};if(a instanceof h&&e.isEqualVec2(u,p)){console.warn("PolyBool: Warning: Zero-length segment detected; your epsilon is probably too small or too large");continue}null==s||s.chainStart({seg:a,fill:!!r.myFill.above},l);const g={index:0,matchesHead:!1,matchesPt1:!1},f={index:0,matchesHead:!1,matchesPt1:!1};let m=g;function v(t,e,s){return m&&(m.index=t,m.matchesHead=e,m.matchesPt1=s),m===g?(m=f,!1):(m=null,!0)}for(let w=0;w0&&e.isEqualVec2(F.start(),k.end())){let I=0,_=q[0].start();for(const H of q){const L=H.end();I+=L[1]*_[0]-L[0]*_[1],_=L}I<0===E&&(q=d(y),F=q[0],k=q[q.length-1]);const R=V(k,F,e);R&&(q.pop(),q[0]=R,null==s||s.chainSimplifyClose(y,{seg:R,fill:E},l)),null==s||s.chainClose(y,l),c.splice(y,1),o.push(q)}}}else{const O=(t,n)=>{const{segs:i,fill:o}=c[t],{segs:r}=c[n];null==s||s.chainAddTail(t,{seg:a,fill:o},l),i.push(a);const h=V(i[i.length-2],a,e);h&&(i.pop(),i[i.length-1]=h,null==s||s.chainSimplifyTail(t,{seg:h,fill:o},l));const u=V(i[i.length-1],r[0],e);u&&(r.shift(),i[i.length-1]=u,null==s||s.chainSimplifyJoin(t,n,{seg:u,fill:o},l)),null==s||s.chainJoin(t,n,l),c[t].segs=i.concat(r),c.splice(n,1)},U=g.index,N=f.index;null==s||s.chainConnect(U,N,l);const A=c[U].segs.length{},moveTo:()=>{e.push([])},lineTo:(t,s)=>{e[e.length-1].push([t,s])},bezierCurveTo:(t,s,n,i,o,r)=>{e[e.length-1].push([t,s,n,i,o,r])},closePath:()=>{}};return t.shape.output(s),{regions:e,inverted:t.inverted}}union(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectUnion(i);return this.polygon(o)}intersect(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectIntersect(i);return this.polygon(o)}difference(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectDifference(i);return this.polygon(o)}differenceRev(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectDifferenceRev(i);return this.polygon(o)}xor(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectXor(i);return this.polygon(o)}}const I=new k;exports.EventBool=b,exports.Geometry=n,exports.GeometryEpsilon=i,exports.Intersecter=x,exports.ListBool=S,exports.PolyBool=k,exports.SegmentBase=a,exports.SegmentBoolBase=f,exports.SegmentBoolCurve=v,exports.SegmentBoolLine=m,exports.SegmentChainer=T,exports.SegmentCurve=l,exports.SegmentLine=h,exports.SegmentSelector=y,exports.SegmentTValuePairsBuilder=r,exports.SegmentTValuesBuilder=o,exports.Shape=B,exports.ShapeCombined=q,exports.boundingBoxesIntersect=s,exports.copySegmentBool=w,exports.default=I,exports.joinCurves=E,exports.joinLines=M,exports.joinSegments=V,exports.lerp=t,exports.lerpVec2=e,exports.projectPointOntoSegmentLine=c,exports.segmentCurveIntersectSegmentCurve=d,exports.segmentLineIntersectSegmentCurve=p,exports.segmentLineIntersectSegmentLine=u,exports.segmentsIntersect=g,exports.segmentsToReceiver=C; +"use strict";function t(t,e,s){return t+(e-t)*s}function e(e,s,n){return[t(e[0],s[0],n),t(e[1],s[1],n)]}function s(t,e){const[s,n]=t,[i,o]=e;return!(s[0]>o[0]||n[0]o[1]||n[1]0?[-2*e-t/3,e-t/3]:[-e-t/3,2*e-t/3]}const h=o*o*o,l=r*r;if(lt-e))}{const t=(r<0?1:-1)*Math.pow(Math.abs(r)+Math.sqrt(l-h),1/3);return[t+(Math.abs(t)>=this.epsilon?o/t:0)-n]}}solveCubic(t,e,s,n){if(Math.abs(t)0?(i=Math.sqrt(i),[(-s+i)/t,(-s-i)/t].sort(((t,e)=>t-e))):[]}return this.solveCubicNormalized(e/t,s/t,n/t)}isEqualVec2(t,e){return Math.abs(t[0]-e[0])1)return this;for(const e of this.tValues)if(0===this.geo.snap0(t-e))return this;return this.tValues.push(t),this}list(){return this.tValues.sort(((t,e)=>t-e)),this.tValues}}class r{constructor(t,e){this.tValuePairs=[],this.allowOutOfRange=t,this.geo=e}add(t,e){if(t=this.geo.snap01(t),e=this.geo.snap01(e),!this.allowOutOfRange&&(t<0||t>1||e<0||e>1))return this;for(const s of this.tValuePairs)if(0===this.geo.snap0(t-s[0])||0===this.geo.snap0(e-s[1]))return this;return this.tValuePairs.push([t,e]),this}list(){return this.tValuePairs.sort(((t,e)=>t[0]-e[0])),this.tValuePairs}done(){return this.tValuePairs.length<=0?null:{kind:"tValuePairs",tValuePairs:this.list()}}}class a{}class h extends a{constructor(t,e,s){super(),this.p0=t,this.p1=e,this.geo=s}copy(){return new h(this.p0,this.p1,this.geo)}isEqual(t){return this.geo.isEqualVec2(this.p0,t.p0)&&this.geo.isEqualVec2(this.p1,t.p1)}start(){return this.p0}start2(){return this.p1}end2(){return this.p0}end(){return this.p1}setStart(t){this.p0=t}setEnd(t){this.p1=t}point(t){const e=this.p0,s=this.p1;return 0===t?e:1===t?s:[e[0]+(s[0]-e[0])*t,e[1]+(s[1]-e[1])*t]}split(t){if(t.length<=0)return[this];const e=t.map((t=>this.point(t)));e.push(this.p1);const s=[];let n=this.p0;for(const t of e)s.push(new h(n,t,this.geo)),n=t;return s}reverse(){return new h(this.p1,this.p0,this.geo)}boundingBox(){const t=this.p0,e=this.p1;return[[Math.min(t[0],e[0]),Math.min(t[1],e[1])],[Math.max(t[0],e[0]),Math.max(t[1],e[1])]]}pointOn(t){return this.geo.isCollinear(t,this.p0,this.p1)}draw(t){const e=this.p0,s=this.p1;return t.moveTo(e[0],e[1]),t.lineTo(s[0],s[1]),t}}class l extends a{constructor(t,e,s,n,i){super(),this.p0=t,this.p1=e,this.p2=s,this.p3=n,this.geo=i}copy(){return new l(this.p0,this.p1,this.p2,this.p3,this.geo)}isEqual(t){return this.geo.isEqualVec2(this.p0,t.p0)&&this.geo.isEqualVec2(this.p1,t.p1)&&this.geo.isEqualVec2(this.p2,t.p2)&&this.geo.isEqualVec2(this.p3,t.p3)}start(){return this.p0}start2(){return this.p1}end2(){return this.p2}end(){return this.p3}setStart(t){this.p0=t}setEnd(t){this.p3=t}point(t){const e=this.p0,s=this.p1,n=this.p2,i=this.p3;if(0===t)return e;if(1===t)return i;const o=(1-t)*(1-t),r=t*t,a=o*(1-t),h=3*o*t,l=3*r*(1-t),c=r*t;return[e[0]*a+s[0]*h+n[0]*l+i[0]*c,e[1]*a+s[1]*h+n[1]*l+i[1]*c]}split(t){if(t.length<=0)return[this];const s=[],n=(t,n)=>{const[i,o,r,a]=t,h=e(i,o,n),c=e(o,r,n),u=e(r,a,n),p=e(h,c,n),g=e(c,u,n),d=e(p,g,n);return s.push(new l(i,h,p,d,this.geo)),[d,g,u,a]};let i=[this.p0,this.p1,this.p2,this.p3],o=0;for(const e of t)i=n(i,(e-o)/(1-o)),o=e;return s.push(new l(i[0],i[1],i[2],i[3],this.geo)),s}reverse(){return new l(this.p3,this.p2,this.p1,this.p0,this.geo)}getCubicCoefficients(t){const e=this.p0[t],s=this.p1[t],n=this.p2[t];return[this.p3[t]-3*n+3*s-e,3*n-6*s+3*e,3*s-3*e,e]}boundingTValues(){const t=new o(this.geo),e=(e,s,n,i)=>{const o=3*i-9*n+9*s-3*e,r=6*e-12*s+6*n,a=3*s-3*e;if(0===this.geo.snap0(o))t.add(-a/r);else{const e=r*r-4*o*a;if(e>=0){const s=Math.sqrt(e);t.add((-r+s)/(2*o)),t.add((-r-s)/(2*o))}}return t},s=this.p0,n=this.p1,i=this.p2,r=this.p3;return e(s[0],n[0],i[0],r[0]),e(s[1],n[1],i[1],r[1]),t.list()}inflectionTValues(){const t=new o(this.geo);t.addArray(this.boundingTValues());const e=this.p0,s=this.p1,n=this.p2,i=this.p3,r=3*(s[0]-e[0]),a=3*(s[1]-e[1]),h=6*(n[0]-s[0]),l=6*(n[1]-s[1]),c=3*(i[0]-n[0]),u=3*(i[1]-n[1]),p=6*(n[0]-2*s[0]+e[0]),g=6*(n[1]-2*s[1]+e[1]),d=r-h+c,f=a-l+u,m=h-2*r,v=l-2*a,w=6*(i[0]-2*n[0]+s[0])-p,b=6*(i[1]-2*n[1]+s[1])-g,S=d*b-f*w,x=d*g+m*b-f*p-v*w,P=m*g+r*b-v*p-a*w,y=r*g-a*p;for(const e of this.geo.solveCubic(S,x,P,y))t.add(e);return t.list()}boundingBox(){const t=this.p0,e=this.p3,s=[Math.min(t[0],e[0]),Math.min(t[1],e[1])],n=[Math.max(t[0],e[0]),Math.max(t[1],e[1])];for(const t of this.boundingTValues()){const e=this.point(t);s[0]=Math.min(s[0],e[0]),s[1]=Math.min(s[1],e[1]),n[0]=Math.max(n[0],e[0]),n[1]=Math.max(n[1],e[1])}return[s,n]}mapXtoT(t,e=!1){if(0===this.geo.snap0(this.p0[0]-t))return 0;if(0===this.geo.snap0(this.p3[0]-t))return 1;const s=this.p0[0]-t,n=this.p1[0]-t,i=this.p2[0]-t,o=[this.p3[0]-t-3*i+3*n-s,3*i-6*n+3*s,3*n-3*s,s];for(const t of this.geo.solveCubic(o[0],o[1],o[2],o[3])){const e=this.geo.snap01(t);if(e>=0&&e<=1)return t}if(e||t>=Math.min(this.p0[0],this.p3[0])&&t<=Math.max(this.p0[0],this.p3[0]))for(let t=0;t<4;t++){let t=-1;for(let e=0;e<4;e++)0!==o[e]&&(t<0||Math.abs(o[e])=0&&e<=1)return t}}return!1}mapXtoY(t,e=!1){const s=this.mapXtoT(t,e);return!1!==s&&this.point(s)[1]}pointOn(t){if(this.geo.isEqualVec2(this.p0,t)||this.geo.isEqualVec2(this.p3,t))return!0;const e=this.mapXtoY(t[0]);return!1!==e&&0===this.geo.snap0(e-t[1])}toLine(){const t=this.p0,e=this.p1,s=this.p2,n=this.p3;return 0===this.geo.snap0(t[0]-e[0])&&0===this.geo.snap0(t[0]-s[0])&&0===this.geo.snap0(t[0]-n[0])||0===this.geo.snap0(t[1]-e[1])&&0===this.geo.snap0(t[1]-s[1])&&0===this.geo.snap0(t[1]-n[1])?new h(t,n,this.geo):null}draw(t){const e=this.p0,s=this.p1,n=this.p2,i=this.p3;return t.moveTo(e[0],e[1]),t.bezierCurveTo(s[0],s[1],n[0],n[1],i[0],i[1]),t}}function c(t,e){const s=e.p1[0]-e.p0[0],n=e.p1[1]-e.p0[1];return((t[0]-e.p0[0])*s+(t[1]-e.p0[1])*n)/(s*s+n*n)}function u(t,e,s){const n=t.geo,i=t.p0,o=t.p1,a=e.p0,h=e.p1,l=o[0]-i[0],u=o[1]-i[1],p=h[0]-a[0],g=h[1]-a[1],d=l*g-u*p;if(0===n.snap0(d)){if(!n.isCollinear(i,o,a))return null;const s=c(e.p0,t),r=c(e.p1,t),h=n.snap01(Math.min(s,r)),l=n.snap01(Math.max(s,r));if(l<0||h>1)return null;const u=c(t.p0,e),p=c(t.p1,e),g=n.snap01(Math.min(u,p)),d=n.snap01(Math.max(u,p));return d<0||g>1?null:{kind:"tRangePairs",tStart:[Math.max(0,h),Math.max(0,g)],tEnd:[Math.min(1,l),Math.min(1,d)]}}const f=i[0]-a[0],m=i[1]-a[1];return new r(s,n).add((p*m-g*f)/d,(l*m-u*f)/d).done()}function p(t,e,s,n){const i=t.geo,o=t.p0,a=t.p1,h=a[1]-o[1],l=o[0]-a[0];if(0===i.snap0(l)){const t=e.mapXtoT(o[0],!1);if(!1===t)return null;const a=(e.point(t)[1]-o[1])/h,l=new r(s,i);return n?l.add(t,a):l.add(a,t),l.done()}const c=h*o[0]+l*o[1],u=e.getCubicCoefficients(0),p=e.getCubicCoefficients(1),g=h*u[0]+l*p[0],d=h*u[1]+l*p[1],f=h*u[2]+l*p[2],m=h*u[3]+l*p[3]-c,v=i.solveCubic(g,d,f,m),w=new r(s,i);if(0===i.snap0(h))for(const t of v){const e=u[0]*t*t*t+u[1]*t*t+u[2]*t+u[3],s=(o[0]-e)/l;n?w.add(t,s):w.add(s,t)}else for(const t of v){const e=(p[0]*t*t*t+p[1]*t*t+p[2]*t+p[3]-o[1])/h;n?w.add(t,e):w.add(e,t)}return w.done()}function g(t,e,n){const i=t.geo;if(i.isEqualVec2(t.p0,e.p0))return i.isEqualVec2(t.p3,e.p3)?i.isEqualVec2(t.p1,e.p1)&&i.isEqualVec2(t.p2,e.p2)?{kind:"tRangePairs",tStart:[0,0],tEnd:[1,1]}:{kind:"tValuePairs",tValuePairs:[[0,0],[1,1]]}:{kind:"tValuePairs",tValuePairs:[[0,0]]};if(i.isEqualVec2(t.p0,e.p3))return{kind:"tValuePairs",tValuePairs:[[0,1]]};if(i.isEqualVec2(t.p3,e.p0))return{kind:"tValuePairs",tValuePairs:[[1,0]]};if(i.isEqualVec2(t.p3,e.p3))return{kind:"tValuePairs",tValuePairs:[[1,1]]};const o=new r(n,i),a=(t,e,n,r,h,l)=>{if(!s(t.boundingBox(),r.boundingBox()))return;const c=(e+n)/2,u=(h+l)/2;if(0===i.snap0(n-e)&&0===i.snap0(l-h))return void o.add(c,u);const[p,g]=t.split([.5]),[d,f]=r.split([.5]);a(p,e,c,d,h,u),a(g,c,n,d,h,u),a(p,e,c,f,u,l),a(g,c,n,f,u,l)};return a(t,0,1,e,0,1),o.done()}function d(t,e,s){if(t instanceof h){if(e instanceof h)return u(t,e,s);if(e instanceof l)return p(t,e,s,!1)}else if(t instanceof l){if(e instanceof h)return p(e,t,s,!0);if(e instanceof l)return g(t,e,s)}throw new Error("PolyBool: Unknown segment instance in segmentsIntersect")}class f{constructor(t,e=null,s=!1,n=null){var i,o,r;this.otherFill=null,this.id=null!==(i=null==n?void 0:n.segmentId())&&void 0!==i?i:-1,this.data=t,this.myFill={above:null!==(o=null==e?void 0:e.above)&&void 0!==o?o:null,below:null!==(r=null==e?void 0:e.below)&&void 0!==r?r:null},this.closed=s}}class m extends f{}class v extends f{}function w(t,e){if(t instanceof m)return new m(t.data,t.myFill,t.closed,e);if(t instanceof v)return new v(t.data,t.myFill,t.closed,e);throw new Error("PolyBool: Unknown SegmentBool in copySegmentBool")}class b{constructor(t,e,s,n){this.status=null,this.isStart=t,this.p=e,this.seg=s,this.primary=n}}class S{constructor(){this.nodes=[]}remove(t){const e=this.nodes.indexOf(t);e>=0&&this.nodes.splice(e,1)}getIndex(t){return this.nodes.indexOf(t)}isEmpty(){return this.nodes.length<=0}getHead(){return this.nodes[0]}removeHead(){this.nodes.shift()}insertBefore(t,e){this.findTransition(t,e).insert(t)}findTransition(t,e){var s,n;let i=0,o=this.nodes.length;for(;i>1;r=this.nodes[s],e(t)-e(r)>0?o=s:i=s+1}var r;return{before:i<=0?null:null!==(s=this.nodes[i-1])&&void 0!==s?s:null,after:null!==(n=this.nodes[i])&&void 0!==n?n:null,insert:t=>(this.nodes.splice(i,0,t),t)}}}class x{constructor(t,e,s=null){this.events=new S,this.status=new S,this.currentPath=[],this.selfIntersection=t,this.geo=e,this.log=s}compareEvents(t,e,s,n,i,o,r,a){const l=this.geo.compareVec2(e,o);return 0!==l?l:n instanceof h&&a instanceof h&&this.geo.isEqualVec2(s,r)?0:t!==i?t?1:-1:this.compareSegments(a,n)}addEvent(t){this.events.insertBefore(t,(e=>e===t?0:this.compareEvents(t.isStart,t.p,t.other.p,t.seg.data,e.isStart,e.p,e.other.p,e.seg.data)))}divideEvent(t,e,s){var n,i;null===(n=this.log)||void 0===n||n.segmentDivide(t.seg,s);const[o,r]=t.seg.data.split([e]);o.setEnd(s),r.setStart(s);const a=r instanceof h?new m(r,t.seg.myFill,t.seg.closed,this.log):r instanceof l?new v(r,t.seg.myFill,t.seg.closed,this.log):null;if(!a)throw new Error("PolyBool: Unknown segment data in divideEvent");return this.events.remove(t.other),t.seg.data=o,null===(i=this.log)||void 0===i||i.segmentChop(t.seg),t.other.p=s,this.addEvent(t.other),this.addSegment(a,t.primary)}beginPath(){this.currentPath=[]}closePath(){for(const t of this.currentPath)t.closed=!0}addSegment(t,e){const s=new b(!0,t.data.start(),t,e),n=new b(!1,t.data.end(),t,e);return s.other=n,n.other=s,this.addEvent(s),this.addEvent(n),s}addLine(t,e,s=!0){const n=this.geo.compareVec2(t,e);if(0===n)return;const i=new m(new h(n<0?t:e,n<0?e:t,this.geo),null,!1,this.log);this.currentPath.push(i),this.addSegment(i,s)}addCurve(t,e,s,n,i=!0){const o=new l(t,e,s,n,this.geo),r=o.split(o.inflectionTValues());for(const t of r){const e=this.geo.compareVec2(t.start(),t.end());if(0===e)continue;const s=t.toLine();if(s)this.addLine(s.p0,s.p1,i);else{const s=new v(e<0?t:t.reverse(),null,!1,this.log);this.currentPath.push(s),this.addSegment(s,i)}}}compareSegments(t,e){let s=t.start(),n=e.start2();const i=e.start();if(e.pointOn(s)){if(s=t.start2(),e.pointOn(s)){if(t instanceof h){if(e instanceof h)return 0;e instanceof l&&(s=t.point(.5))}t instanceof l&&(s=t.end())}if(e instanceof l&&0===this.geo.snap0(s[0]-i[0])&&0===this.geo.snap0(n[0]-i[0]))return Math.sign(i[1]-s[1])}else{if(e instanceof l){const t=e.mapXtoY(s[0],!0);if(!1!==t)return Math.sign(t-s[1])}if(t instanceof l){const s=d(t,e,!0);if(s&&"tValuePairs"===s.kind)for(const e of s.tValuePairs){const s=this.geo.snap01(e[0]);if(s>0&&s<1){n=t.point(s);break}}}}const[o,r]=s,[a,c]=n,[u,p]=i;return Math.sign((a-o)*(p-r)-(c-r)*(u-o))}statusFindSurrounding(t){return this.status.findTransition(t,(e=>{if(t===e)return 0;const s=this.compareSegments(t.seg.data,e.seg.data);return 0===s?-1:s}))}checkIntersection(t,e){var s;const n=t.seg,i=e.seg;null===(s=this.log)||void 0===s||s.checkIntersection(n,i);const o=d(n.data,i.data,!1);if(null===o)return null;if("tRangePairs"===o.kind){const{tStart:[s,r],tEnd:[a,h]}=o;if(1===s&&1===a&&0===r&&0===h||0===s&&0===a&&1===r&&1===h)return null;if(0===s&&1===a&&0===r&&1===h)return e;const l=n.data.start(),c=n.data.end(),u=i.data.end();return 0===s&&0===r?(1===a?this.divideEvent(e,h,c):this.divideEvent(t,a,u),e):(r>0&&r<1&&(1===a&&1===h||(1===a?this.divideEvent(e,h,c):this.divideEvent(t,a,u)),this.divideEvent(e,r,l)),null)}if("tValuePairs"===o.kind){if(o.tValuePairs.length<=0)return null;let s=o.tValuePairs[0];for(let t=1;t0&&r<1&&this.divideEvent(t,r,h),a>0&&a<1&&this.divideEvent(e,a,h),null}throw new Error("PolyBool: Unknown intersection type")}calculate(){var t,e,s,n,i,o,r,a;const h=[];for(;!this.events.isEmpty();){const a=this.events.getHead();if(null===(t=this.log)||void 0===t||t.vert(a.p[0]),a.isStart){null===(e=this.log)||void 0===e||e.segmentNew(a.seg,a.primary);const t=this.statusFindSurrounding(a),r=t.before,h=t.after;null===(s=this.log)||void 0===s||s.tempStatus(a.seg,!!r&&r.seg,!!h&&h.seg);const l=()=>{if(r){const t=this.checkIntersection(a,r);if(t)return t}return h?this.checkIntersection(a,h):null},c=l();if(c){if(this.selfIntersection){let t;t=null===a.seg.myFill.below?a.seg.closed:a.seg.myFill.above!==a.seg.myFill.below,t&&(c.seg.myFill.above=!c.seg.myFill.above)}else c.seg.otherFill=a.seg.myFill;null===(n=this.log)||void 0===n||n.segmentUpdate(c.seg),this.events.remove(a.other),this.events.remove(a)}if(this.events.getHead()!==a){null===(i=this.log)||void 0===i||i.rewind(a.seg);continue}if(this.selfIntersection){let t;t=null===a.seg.myFill.below?a.seg.closed:a.seg.myFill.above!==a.seg.myFill.below,a.seg.myFill.below=!!h&&h.seg.myFill.above,a.seg.myFill.above=t?!a.seg.myFill.below:a.seg.myFill.below}else if(null===a.seg.otherFill){let t;if(h)if(a.primary===h.primary){if(null===h.seg.otherFill)throw new Error("PolyBool: Unexpected state of otherFill (null)");t=h.seg.otherFill.above}else t=h.seg.myFill.above;else t=!1;a.seg.otherFill={above:t,below:t}}null===(o=this.log)||void 0===o||o.status(a.seg,!!r&&r.seg,!!h&&h.seg),a.other.status=t.insert(a)}else{const t=a.status;if(null===t)throw new Error("PolyBool: Zero-length segment detected; your epsilon is probably too small or too large");const e=this.status.getIndex(t);if(e>0&&eMath.abs(i)?(t.p3[0]-t.p2[0])/n:(t.p3[1]-t.p2[1])/i,r=s.snap01(o);if(0!==r&&1!==r){const n=new l(t.p0,[t.p0[0]+(t.p1[0]-t.p0[0])/o,t.p0[1]+(t.p1[1]-t.p0[1])/o],[e.p2[0]-o*(e.p3[0]-e.p2[0])/(1-o),e.p2[1]-o*(e.p3[1]-e.p2[1])/(1-o)],e.p3,s),[i,r]=n.split([o]);if(i.isEqual(t)&&r.isEqual(e))return n}}}function V(t,e,s){if(t!==e){if(t instanceof h&&e instanceof h)return M(t,e,s);if(t instanceof l&&e instanceof l)return E(t,e,s)}}function T(t,e,s){const n=[],i=[],o=[];for(const r of t){const t=r.myFill.above?r.data:r.data.reverse(),a=r.closed,l=a?n:i,c=t.start(),u=t.end();if(t instanceof h&&e.isEqualVec2(c,u)){console.warn("PolyBool: Warning: Zero-length segment detected; your epsilon is probably too small or too large");continue}let p,g;null==s||s.chainStart(t,a);for(let t=0;t0&&(null==s||s.chainClose(p,a),o.push(n)):(null==s||s.chainJoin(p,g,a),l[p]=n.concat(i)),l.splice(g,1)}else if(null!=p){null==s||s.chainMatch(p,a);const n=l[p],i=V(n[n.length-1],t,e);null==s||s.chainAddTail(p,t,a),null!=i?n[n.length-1]=i:n.push(t)}else if(null!=g){null==s||s.chainMatch(g,a);const n=l[g],i=V(t,n[0],e);null==s||s.chainAddHead(g,t,a),null!=i?n[0]=i:n.unshift(t)}else l.push([t]),null==s||s.chainNew(t,a)}return o.push(...i),o}function C(t,e,s,n){const[i,o,r,a,c,u]=n;s.beginPath();for(const n of t){if(n.length<=0)continue;for(let t=0;t{},moveTo:()=>{e.push([])},lineTo:(t,s)=>{e[e.length-1].push([t,s])},bezierCurveTo:(t,s,n,i,o,r)=>{e[e.length-1].push([t,s,n,i,o,r])},closePath:()=>{}};return t.shape.output(s),{regions:e,inverted:t.inverted}}union(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectUnion(i);return this.polygon(o)}intersect(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectIntersect(i);return this.polygon(o)}difference(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectDifference(i);return this.polygon(o)}differenceRev(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectDifferenceRev(i);return this.polygon(o)}xor(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectXor(i);return this.polygon(o)}}const k=new I;exports.EventBool=b,exports.Geometry=n,exports.GeometryEpsilon=i,exports.Intersecter=x,exports.ListBool=S,exports.PolyBool=I,exports.SegmentBase=a,exports.SegmentBoolBase=f,exports.SegmentBoolCurve=v,exports.SegmentBoolLine=m,exports.SegmentChainer=T,exports.SegmentCurve=l,exports.SegmentLine=h,exports.SegmentSelector=y,exports.SegmentTValuePairsBuilder=r,exports.SegmentTValuesBuilder=o,exports.Shape=B,exports.ShapeCombined=F,exports.boundingBoxesIntersect=s,exports.copySegmentBool=w,exports.default=k,exports.joinCurves=E,exports.joinLines=M,exports.joinSegments=V,exports.lerp=t,exports.lerpVec2=e,exports.projectPointOntoSegmentLine=c,exports.segmentCurveIntersectSegmentCurve=g,exports.segmentLineIntersectSegmentCurve=p,exports.segmentLineIntersectSegmentLine=u,exports.segmentsIntersect=d,exports.segmentsToReceiver=C; diff --git a/dist/polybool.min.js b/dist/polybool.min.js index 42603b4..58cd916 100644 --- a/dist/polybool.min.js +++ b/dist/polybool.min.js @@ -1 +1 @@ -function t(t,e,s){return t+(e-t)*s}function e(e,s,n){return[t(e[0],s[0],n),t(e[1],s[1],n)]}function s(t,e){const[s,n]=t,[i,o]=e;return!(s[0]>o[0]||n[0]o[1]||n[1]0?[-2*e-t/3,e-t/3]:[-e-t/3,2*e-t/3]}const h=o*o*o,l=r*r;if(lt-e))}{const t=(r<0?1:-1)*Math.pow(Math.abs(r)+Math.sqrt(l-h),1/3);return[t+(Math.abs(t)>=this.epsilon?o/t:0)-n]}}solveCubic(t,e,s,n){if(Math.abs(t)0?(i=Math.sqrt(i),[(-s+i)/t,(-s-i)/t].sort(((t,e)=>t-e))):[]}return this.solveCubicNormalized(e/t,s/t,n/t)}isEqualVec2(t,e){return Math.abs(t[0]-e[0])1)return this;for(const e of this.tValues)if(0===this.geo.snap0(t-e))return this;return this.tValues.push(t),this}list(){return this.tValues.sort(((t,e)=>t-e)),this.tValues}}class r{constructor(t,e){this.tValuePairs=[],this.allowOutOfRange=t,this.geo=e}add(t,e){if(t=this.geo.snap01(t),e=this.geo.snap01(e),!this.allowOutOfRange&&(t<0||t>1||e<0||e>1))return this;for(const s of this.tValuePairs)if(0===this.geo.snap0(t-s[0])||0===this.geo.snap0(e-s[1]))return this;return this.tValuePairs.push([t,e]),this}list(){return this.tValuePairs.sort(((t,e)=>t[0]-e[0])),this.tValuePairs}done(){return this.tValuePairs.length<=0?null:{kind:"tValuePairs",tValuePairs:this.list()}}}class a{}class h extends a{constructor(t,e,s){super(),this.p0=t,this.p1=e,this.geo=s}copy(){return new h(this.p0,this.p1,this.geo)}isEqual(t){return this.geo.isEqualVec2(this.p0,t.p0)&&this.geo.isEqualVec2(this.p1,t.p1)}start(){return this.p0}start2(){return this.p1}end2(){return this.p0}end(){return this.p1}setStart(t){this.p0=t}setEnd(t){this.p1=t}point(t){const e=this.p0,s=this.p1;return 0===t?e:1===t?s:[e[0]+(s[0]-e[0])*t,e[1]+(s[1]-e[1])*t]}split(t){if(t.length<=0)return[this];const e=t.map((t=>this.point(t)));e.push(this.p1);const s=[];let n=this.p0;for(const t of e)s.push(new h(n,t,this.geo)),n=t;return s}reverse(){return new h(this.p1,this.p0,this.geo)}boundingBox(){const t=this.p0,e=this.p1;return[[Math.min(t[0],e[0]),Math.min(t[1],e[1])],[Math.max(t[0],e[0]),Math.max(t[1],e[1])]]}pointOn(t){return this.geo.isCollinear(t,this.p0,this.p1)}draw(t){const e=this.p0,s=this.p1;return t.moveTo(e[0],e[1]),t.lineTo(s[0],s[1]),t}}class l extends a{constructor(t,e,s,n,i){super(),this.p0=t,this.p1=e,this.p2=s,this.p3=n,this.geo=i}copy(){return new l(this.p0,this.p1,this.p2,this.p3,this.geo)}isEqual(t){return this.geo.isEqualVec2(this.p0,t.p0)&&this.geo.isEqualVec2(this.p1,t.p1)&&this.geo.isEqualVec2(this.p2,t.p2)&&this.geo.isEqualVec2(this.p3,t.p3)}start(){return this.p0}start2(){return this.p1}end2(){return this.p2}end(){return this.p3}setStart(t){this.p0=t}setEnd(t){this.p3=t}point(t){const e=this.p0,s=this.p1,n=this.p2,i=this.p3;if(0===t)return e;if(1===t)return i;const o=(1-t)*(1-t),r=t*t,a=o*(1-t),h=3*o*t,l=3*r*(1-t),c=r*t;return[e[0]*a+s[0]*h+n[0]*l+i[0]*c,e[1]*a+s[1]*h+n[1]*l+i[1]*c]}split(t){if(t.length<=0)return[this];const s=[],n=(t,n)=>{const[i,o,r,a]=t,h=e(i,o,n),c=e(o,r,n),u=e(r,a,n),p=e(h,c,n),d=e(c,u,n),f=e(p,d,n);return s.push(new l(i,h,p,f,this.geo)),[f,d,u,a]};let i=[this.p0,this.p1,this.p2,this.p3],o=0;for(const e of t)i=n(i,(e-o)/(1-o)),o=e;return s.push(new l(i[0],i[1],i[2],i[3],this.geo)),s}reverse(){return new l(this.p3,this.p2,this.p1,this.p0,this.geo)}getCubicCoefficients(t){const e=this.p0[t],s=this.p1[t],n=this.p2[t];return[this.p3[t]-3*n+3*s-e,3*n-6*s+3*e,3*s-3*e,e]}boundingTValues(){const t=new o(this.geo),e=(e,s,n,i)=>{const o=3*i-9*n+9*s-3*e,r=6*e-12*s+6*n,a=3*s-3*e;if(0===this.geo.snap0(o))t.add(-a/r);else{const e=r*r-4*o*a;if(e>=0){const s=Math.sqrt(e);t.add((-r+s)/(2*o)),t.add((-r-s)/(2*o))}}return t},s=this.p0,n=this.p1,i=this.p2,r=this.p3;return e(s[0],n[0],i[0],r[0]),e(s[1],n[1],i[1],r[1]),t.list()}inflectionTValues(){const t=new o(this.geo);t.addArray(this.boundingTValues());const e=this.p0,s=this.p1,n=this.p2,i=this.p3,r=3*(s[0]-e[0]),a=3*(s[1]-e[1]),h=6*(n[0]-s[0]),l=6*(n[1]-s[1]),c=3*(i[0]-n[0]),u=3*(i[1]-n[1]),p=6*(n[0]-2*s[0]+e[0]),d=6*(n[1]-2*s[1]+e[1]),f=r-h+c,g=a-l+u,m=h-2*r,v=l-2*a,w=6*(i[0]-2*n[0]+s[0])-p,b=6*(i[1]-2*n[1]+s[1])-d,P=f*b-g*w,S=f*d+m*b-g*p-v*w,y=m*d+r*b-v*p-a*w,M=r*d-a*p;for(const e of this.geo.solveCubic(P,S,y,M))t.add(e);return t.list()}boundingBox(){const t=this.p0,e=this.p3,s=[Math.min(t[0],e[0]),Math.min(t[1],e[1])],n=[Math.max(t[0],e[0]),Math.max(t[1],e[1])];for(const t of this.boundingTValues()){const e=this.point(t);s[0]=Math.min(s[0],e[0]),s[1]=Math.min(s[1],e[1]),n[0]=Math.max(n[0],e[0]),n[1]=Math.max(n[1],e[1])}return[s,n]}mapXtoT(t,e=!1){if(0===this.geo.snap0(this.p0[0]-t))return 0;if(0===this.geo.snap0(this.p3[0]-t))return 1;const s=this.p0[0]-t,n=this.p1[0]-t,i=this.p2[0]-t,o=[this.p3[0]-t-3*i+3*n-s,3*i-6*n+3*s,3*n-3*s,s];for(const t of this.geo.solveCubic(o[0],o[1],o[2],o[3])){const e=this.geo.snap01(t);if(e>=0&&e<=1)return t}if(e||t>=Math.min(this.p0[0],this.p3[0])&&t<=Math.max(this.p0[0],this.p3[0]))for(let t=0;t<4;t++){let t=-1;for(let e=0;e<4;e++)0!==o[e]&&(t<0||Math.abs(o[e])=0&&e<=1)return t}}return!1}mapXtoY(t,e=!1){const s=this.mapXtoT(t,e);return!1!==s&&this.point(s)[1]}pointOn(t){if(this.geo.isEqualVec2(this.p0,t)||this.geo.isEqualVec2(this.p3,t))return!0;const e=this.mapXtoY(t[0]);return!1!==e&&0===this.geo.snap0(e-t[1])}toLine(){const t=this.p0,e=this.p1,s=this.p2,n=this.p3;return 0===this.geo.snap0(t[0]-e[0])&&0===this.geo.snap0(t[0]-s[0])&&0===this.geo.snap0(t[0]-n[0])||0===this.geo.snap0(t[1]-e[1])&&0===this.geo.snap0(t[1]-s[1])&&0===this.geo.snap0(t[1]-n[1])?new h(t,n,this.geo):null}draw(t){const e=this.p0,s=this.p1,n=this.p2,i=this.p3;return t.moveTo(e[0],e[1]),t.bezierCurveTo(s[0],s[1],n[0],n[1],i[0],i[1]),t}}function c(t,e){const s=e.p1[0]-e.p0[0],n=e.p1[1]-e.p0[1];return((t[0]-e.p0[0])*s+(t[1]-e.p0[1])*n)/(s*s+n*n)}function u(t,e,s){const n=t.geo,i=t.p0,o=t.p1,a=e.p0,h=e.p1,l=o[0]-i[0],u=o[1]-i[1],p=h[0]-a[0],d=h[1]-a[1],f=l*d-u*p;if(0===n.snap0(f)){if(!n.isCollinear(i,o,a))return null;const s=c(e.p0,t),r=c(e.p1,t),h=n.snap01(Math.min(s,r)),l=n.snap01(Math.max(s,r));if(l<0||h>1)return null;const u=c(t.p0,e),p=c(t.p1,e),d=n.snap01(Math.min(u,p)),f=n.snap01(Math.max(u,p));return f<0||d>1?null:{kind:"tRangePairs",tStart:[Math.max(0,h),Math.max(0,d)],tEnd:[Math.min(1,l),Math.min(1,f)]}}const g=i[0]-a[0],m=i[1]-a[1];return new r(s,n).add((p*m-d*g)/f,(l*m-u*g)/f).done()}function p(t,e,s,n){const i=t.geo,o=t.p0,a=t.p1,h=a[1]-o[1],l=o[0]-a[0];if(0===i.snap0(l)){const t=e.mapXtoT(o[0],!1);if(!1===t)return null;const a=(e.point(t)[1]-o[1])/h,l=new r(s,i);return n?l.add(t,a):l.add(a,t),l.done()}const c=h*o[0]+l*o[1],u=e.getCubicCoefficients(0),p=e.getCubicCoefficients(1),d=h*u[0]+l*p[0],f=h*u[1]+l*p[1],g=h*u[2]+l*p[2],m=h*u[3]+l*p[3]-c,v=i.solveCubic(d,f,g,m),w=new r(s,i);if(0===i.snap0(h))for(const t of v){const e=u[0]*t*t*t+u[1]*t*t+u[2]*t+u[3],s=(o[0]-e)/l;n?w.add(t,s):w.add(s,t)}else for(const t of v){const e=(p[0]*t*t*t+p[1]*t*t+p[2]*t+p[3]-o[1])/h;n?w.add(t,e):w.add(e,t)}return w.done()}function d(t,e,n){const i=t.geo;if(i.isEqualVec2(t.p0,e.p0))return i.isEqualVec2(t.p3,e.p3)?i.isEqualVec2(t.p1,e.p1)&&i.isEqualVec2(t.p2,e.p2)?{kind:"tRangePairs",tStart:[0,0],tEnd:[1,1]}:{kind:"tValuePairs",tValuePairs:[[0,0],[1,1]]}:{kind:"tValuePairs",tValuePairs:[[0,0]]};if(i.isEqualVec2(t.p0,e.p3))return{kind:"tValuePairs",tValuePairs:[[0,1]]};if(i.isEqualVec2(t.p3,e.p0))return{kind:"tValuePairs",tValuePairs:[[1,0]]};if(i.isEqualVec2(t.p3,e.p3))return{kind:"tValuePairs",tValuePairs:[[1,1]]};const o=new r(n,i),a=(t,e,n,r,h,l)=>{if(!s(t.boundingBox(),r.boundingBox()))return;const c=(e+n)/2,u=(h+l)/2;if(0===i.snap0(n-e)&&0===i.snap0(l-h))return void o.add(c,u);const[p,d]=t.split([.5]),[f,g]=r.split([.5]);a(p,e,c,f,h,u),a(d,c,n,f,h,u),a(p,e,c,g,u,l),a(d,c,n,g,u,l)};return a(t,0,1,e,0,1),o.done()}function f(t,e,s){if(t instanceof h){if(e instanceof h)return u(t,e,s);if(e instanceof l)return p(t,e,s,!1)}else if(t instanceof l){if(e instanceof h)return p(e,t,s,!0);if(e instanceof l)return d(t,e,s)}throw new Error("PolyBool: Unknown segment instance in segmentsIntersect")}class g{constructor(t,e=null,s=!1,n=null){var i,o,r;this.otherFill=null,this.id=null!==(i=null==n?void 0:n.segmentId())&&void 0!==i?i:-1,this.data=t,this.myFill={above:null!==(o=null==e?void 0:e.above)&&void 0!==o?o:null,below:null!==(r=null==e?void 0:e.below)&&void 0!==r?r:null},this.closed=s}}class m extends g{}class v extends g{}function w(t,e){if(t instanceof m)return new m(t.data,t.myFill,t.closed,e);if(t instanceof v)return new v(t.data,t.myFill,t.closed,e);throw new Error("PolyBool: Unknown SegmentBool in copySegmentBool")}class b{constructor(t,e,s,n){this.status=null,this.isStart=t,this.p=e,this.seg=s,this.primary=n}}class P{constructor(){this.nodes=[]}remove(t){const e=this.nodes.indexOf(t);e>=0&&this.nodes.splice(e,1)}getIndex(t){return this.nodes.indexOf(t)}isEmpty(){return this.nodes.length<=0}getHead(){return this.nodes[0]}removeHead(){this.nodes.shift()}insertBefore(t,e){this.findTransition(t,e).insert(t)}findTransition(t,e){var s,n;let i=0,o=this.nodes.length;for(;i>1;r=this.nodes[s],e(t)-e(r)>0?o=s:i=s+1}var r;return{before:i<=0?null:null!==(s=this.nodes[i-1])&&void 0!==s?s:null,after:null!==(n=this.nodes[i])&&void 0!==n?n:null,insert:t=>(this.nodes.splice(i,0,t),t)}}}class S{constructor(t,e,s=null){this.events=new P,this.status=new P,this.currentPath=[],this.selfIntersection=t,this.geo=e,this.log=s}compareEvents(t,e,s,n,i,o,r,a){const l=this.geo.compareVec2(e,o);return 0!==l?l:n instanceof h&&a instanceof h&&this.geo.isEqualVec2(s,r)?0:t!==i?t?1:-1:this.compareSegments(a,n)}addEvent(t){this.events.insertBefore(t,(e=>e===t?0:this.compareEvents(t.isStart,t.p,t.other.p,t.seg.data,e.isStart,e.p,e.other.p,e.seg.data)))}divideEvent(t,e,s){var n,i;null===(n=this.log)||void 0===n||n.segmentDivide(t.seg,s);const[o,r]=t.seg.data.split([e]);o.setEnd(s),r.setStart(s);const a=r instanceof h?new m(r,t.seg.myFill,t.seg.closed,this.log):r instanceof l?new v(r,t.seg.myFill,t.seg.closed,this.log):null;if(!a)throw new Error("PolyBool: Unknown segment data in divideEvent");return this.events.remove(t.other),t.seg.data=o,null===(i=this.log)||void 0===i||i.segmentChop(t.seg),t.other.p=s,this.addEvent(t.other),this.addSegment(a,t.primary)}beginPath(){this.currentPath=[]}closePath(){for(const t of this.currentPath)t.closed=!0}addSegment(t,e){const s=new b(!0,t.data.start(),t,e),n=new b(!1,t.data.end(),t,e);return s.other=n,n.other=s,this.addEvent(s),this.addEvent(n),s}addLine(t,e,s=!0){const n=this.geo.compareVec2(t,e);if(0===n)return;const i=new m(new h(n<0?t:e,n<0?e:t,this.geo),null,!1,this.log);this.currentPath.push(i),this.addSegment(i,s)}addCurve(t,e,s,n,i=!0){const o=new l(t,e,s,n,this.geo),r=o.split(o.inflectionTValues());for(const t of r){const e=this.geo.compareVec2(t.start(),t.end());if(0===e)continue;const s=t.toLine();if(s)this.addLine(s.p0,s.p1,i);else{const s=new v(e<0?t:t.reverse(),null,!1,this.log);this.currentPath.push(s),this.addSegment(s,i)}}}compareSegments(t,e){let s=t.start(),n=e.start2();const i=e.start();if(e.pointOn(s)){if(s=t.start2(),e.pointOn(s)){if(t instanceof h){if(e instanceof h)return 0;e instanceof l&&(s=t.point(.5))}t instanceof l&&(s=t.end())}if(e instanceof l&&0===this.geo.snap0(s[0]-i[0])&&0===this.geo.snap0(n[0]-i[0]))return Math.sign(i[1]-s[1])}else{if(e instanceof l){const t=e.mapXtoY(s[0],!0);if(!1!==t)return Math.sign(t-s[1])}if(t instanceof l){const s=f(t,e,!0);if(s&&"tValuePairs"===s.kind)for(const e of s.tValuePairs){const s=this.geo.snap01(e[0]);if(s>0&&s<1){n=t.point(s);break}}}}const[o,r]=s,[a,c]=n,[u,p]=i;return Math.sign((a-o)*(p-r)-(c-r)*(u-o))}statusFindSurrounding(t){return this.status.findTransition(t,(e=>{if(t===e)return 0;const s=this.compareSegments(t.seg.data,e.seg.data);return 0===s?-1:s}))}checkIntersection(t,e){var s;const n=t.seg,i=e.seg;null===(s=this.log)||void 0===s||s.checkIntersection(n,i);const o=f(n.data,i.data,!1);if(null===o)return null;if("tRangePairs"===o.kind){const{tStart:[s,r],tEnd:[a,h]}=o;if(1===s&&1===a&&0===r&&0===h||0===s&&0===a&&1===r&&1===h)return null;if(0===s&&1===a&&0===r&&1===h)return e;const l=n.data.start(),c=n.data.end(),u=i.data.end();return 0===s&&0===r?(1===a?this.divideEvent(e,h,c):this.divideEvent(t,a,u),e):(r>0&&r<1&&(1===a&&1===h||(1===a?this.divideEvent(e,h,c):this.divideEvent(t,a,u)),this.divideEvent(e,r,l)),null)}if("tValuePairs"===o.kind){if(o.tValuePairs.length<=0)return null;let s=o.tValuePairs[0];for(let t=1;t0&&r<1&&this.divideEvent(t,r,h),a>0&&a<1&&this.divideEvent(e,a,h),null}throw new Error("PolyBool: Unknown intersection type")}calculate(){var t,e,s,n,i,o,r,a;const h=[];for(;!this.events.isEmpty();){const a=this.events.getHead();if(null===(t=this.log)||void 0===t||t.vert(a.p[0]),a.isStart){null===(e=this.log)||void 0===e||e.segmentNew(a.seg,a.primary);const t=this.statusFindSurrounding(a),r=t.before,h=t.after;null===(s=this.log)||void 0===s||s.tempStatus(a.seg,!!r&&r.seg,!!h&&h.seg);const l=()=>{if(r){const t=this.checkIntersection(a,r);if(t)return t}return h?this.checkIntersection(a,h):null},c=l();if(c){if(this.selfIntersection){let t;t=null===a.seg.myFill.below?a.seg.closed:a.seg.myFill.above!==a.seg.myFill.below,t&&(c.seg.myFill.above=!c.seg.myFill.above)}else c.seg.otherFill=a.seg.myFill;null===(n=this.log)||void 0===n||n.segmentUpdate(c.seg),this.events.remove(a.other),this.events.remove(a)}if(this.events.getHead()!==a){null===(i=this.log)||void 0===i||i.rewind(a.seg);continue}if(this.selfIntersection){let t;t=null===a.seg.myFill.below?a.seg.closed:a.seg.myFill.above!==a.seg.myFill.below,a.seg.myFill.below=!!h&&h.seg.myFill.above,a.seg.myFill.above=t?!a.seg.myFill.below:a.seg.myFill.below}else if(null===a.seg.otherFill){let t;if(h)if(a.primary===h.primary){if(null===h.seg.otherFill)throw new Error("PolyBool: Unexpected state of otherFill (null)");t=h.seg.otherFill.above}else t=h.seg.myFill.above;else t=!1;a.seg.otherFill={above:t,below:t}}null===(o=this.log)||void 0===o||o.status(a.seg,!!r&&r.seg,!!h&&h.seg),a.other.status=t.insert(a)}else{const t=a.status;if(null===t)throw new Error("PolyBool: Zero-length segment detected; your epsilon is probably too small or too large");const e=this.status.getIndex(t);if(e>0&&eMath.abs(i)?(t.p3[0]-t.p2[0])/n:(t.p3[1]-t.p2[1])/i,r=s.snap01(o);if(0!==r&&1!==r){const n=new l(t.p0,[t.p0[0]+(t.p1[0]-t.p0[0])/o,t.p0[1]+(t.p1[1]-t.p0[1])/o],[e.p2[0]-o*(e.p3[0]-e.p2[0])/(1-o),e.p2[1]-o*(e.p3[1]-e.p2[1])/(1-o)],e.p3,s),[i,r]=n.split([o]);if(i.isEqual(t)&&r.isEqual(e))return n}}return!1}function V(t,e,s){return t!==e&&(t instanceof h&&e instanceof h?x(t,e,s):t instanceof l&&e instanceof l&&E(t,e,s))}function T(t,e,s){const n=[],i=[],o=[];for(const r of t){let a=r.data;const l=r.closed,c=l?n:i,u=a.start(),p=a.end(),d=t=>{null==s||s.chainReverse(t,l);const e=[];for(const s of c[t].segs)e.unshift(s.reverse());return c[t]={segs:e,fill:!c[t].fill},e};if(a instanceof h&&e.isEqualVec2(u,p)){console.warn("PolyBool: Warning: Zero-length segment detected; your epsilon is probably too small or too large");continue}null==s||s.chainStart({seg:a,fill:!!r.myFill.above},l);const f={index:0,matchesHead:!1,matchesPt1:!1},g={index:0,matchesHead:!1,matchesPt1:!1};let m=f;function v(t,e,s){return m&&(m.index=t,m.matchesHead=e,m.matchesPt1=s),m===f?(m=g,!1):(m=null,!0)}for(let w=0;w0&&e.isEqualVec2(k.start(),I.end())){let B=0,_=F[0].start();for(const H of F){const O=H.end();B+=O[1]*_[0]-O[0]*_[1],_=O}B<0===E&&(F=d(M),k=F[0],I=F[F.length-1]);const R=V(I,k,e);R&&(F.pop(),F[0]=R,null==s||s.chainSimplifyClose(M,{seg:R,fill:E},l)),null==s||s.chainClose(M,l),c.splice(M,1),o.push(F)}}}else{const U=(t,n)=>{const{segs:i,fill:o}=c[t],{segs:r}=c[n];null==s||s.chainAddTail(t,{seg:a,fill:o},l),i.push(a);const h=V(i[i.length-2],a,e);h&&(i.pop(),i[i.length-1]=h,null==s||s.chainSimplifyTail(t,{seg:h,fill:o},l));const u=V(i[i.length-1],r[0],e);u&&(r.shift(),i[i.length-1]=u,null==s||s.chainSimplifyJoin(t,n,{seg:u,fill:o},l)),null==s||s.chainJoin(t,n,l),c[t].segs=i.concat(r),c.splice(n,1)},N=f.index,A=g.index;null==s||s.chainConnect(N,A,l);const z=c[N].segs.length{},moveTo:()=>{e.push([])},lineTo:(t,s)=>{e[e.length-1].push([t,s])},bezierCurveTo:(t,s,n,i,o,r)=>{e[e.length-1].push([t,s,n,i,o,r])},closePath:()=>{}};return t.shape.output(s),{regions:e,inverted:t.inverted}}union(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectUnion(i);return this.polygon(o)}intersect(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectIntersect(i);return this.polygon(o)}difference(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectDifference(i);return this.polygon(o)}differenceRev(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectDifferenceRev(i);return this.polygon(o)}xor(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectXor(i);return this.polygon(o)}}const B=new I;export{b as EventBool,n as Geometry,i as GeometryEpsilon,S as Intersecter,P as ListBool,I as PolyBool,a as SegmentBase,g as SegmentBoolBase,v as SegmentBoolCurve,m as SegmentBoolLine,T as SegmentChainer,l as SegmentCurve,h as SegmentLine,M as SegmentSelector,r as SegmentTValuePairsBuilder,o as SegmentTValuesBuilder,q as Shape,F as ShapeCombined,s as boundingBoxesIntersect,w as copySegmentBool,B as default,E as joinCurves,x as joinLines,V as joinSegments,t as lerp,e as lerpVec2,c as projectPointOntoSegmentLine,d as segmentCurveIntersectSegmentCurve,p as segmentLineIntersectSegmentCurve,u as segmentLineIntersectSegmentLine,f as segmentsIntersect,C as segmentsToReceiver}; +function t(t,e,s){return t+(e-t)*s}function e(e,s,n){return[t(e[0],s[0],n),t(e[1],s[1],n)]}function s(t,e){const[s,n]=t,[i,o]=e;return!(s[0]>o[0]||n[0]o[1]||n[1]0?[-2*e-t/3,e-t/3]:[-e-t/3,2*e-t/3]}const h=o*o*o,l=r*r;if(lt-e))}{const t=(r<0?1:-1)*Math.pow(Math.abs(r)+Math.sqrt(l-h),1/3);return[t+(Math.abs(t)>=this.epsilon?o/t:0)-n]}}solveCubic(t,e,s,n){if(Math.abs(t)0?(i=Math.sqrt(i),[(-s+i)/t,(-s-i)/t].sort(((t,e)=>t-e))):[]}return this.solveCubicNormalized(e/t,s/t,n/t)}isEqualVec2(t,e){return Math.abs(t[0]-e[0])1)return this;for(const e of this.tValues)if(0===this.geo.snap0(t-e))return this;return this.tValues.push(t),this}list(){return this.tValues.sort(((t,e)=>t-e)),this.tValues}}class r{constructor(t,e){this.tValuePairs=[],this.allowOutOfRange=t,this.geo=e}add(t,e){if(t=this.geo.snap01(t),e=this.geo.snap01(e),!this.allowOutOfRange&&(t<0||t>1||e<0||e>1))return this;for(const s of this.tValuePairs)if(0===this.geo.snap0(t-s[0])||0===this.geo.snap0(e-s[1]))return this;return this.tValuePairs.push([t,e]),this}list(){return this.tValuePairs.sort(((t,e)=>t[0]-e[0])),this.tValuePairs}done(){return this.tValuePairs.length<=0?null:{kind:"tValuePairs",tValuePairs:this.list()}}}class a{}class h extends a{constructor(t,e,s){super(),this.p0=t,this.p1=e,this.geo=s}copy(){return new h(this.p0,this.p1,this.geo)}isEqual(t){return this.geo.isEqualVec2(this.p0,t.p0)&&this.geo.isEqualVec2(this.p1,t.p1)}start(){return this.p0}start2(){return this.p1}end2(){return this.p0}end(){return this.p1}setStart(t){this.p0=t}setEnd(t){this.p1=t}point(t){const e=this.p0,s=this.p1;return 0===t?e:1===t?s:[e[0]+(s[0]-e[0])*t,e[1]+(s[1]-e[1])*t]}split(t){if(t.length<=0)return[this];const e=t.map((t=>this.point(t)));e.push(this.p1);const s=[];let n=this.p0;for(const t of e)s.push(new h(n,t,this.geo)),n=t;return s}reverse(){return new h(this.p1,this.p0,this.geo)}boundingBox(){const t=this.p0,e=this.p1;return[[Math.min(t[0],e[0]),Math.min(t[1],e[1])],[Math.max(t[0],e[0]),Math.max(t[1],e[1])]]}pointOn(t){return this.geo.isCollinear(t,this.p0,this.p1)}draw(t){const e=this.p0,s=this.p1;return t.moveTo(e[0],e[1]),t.lineTo(s[0],s[1]),t}}class l extends a{constructor(t,e,s,n,i){super(),this.p0=t,this.p1=e,this.p2=s,this.p3=n,this.geo=i}copy(){return new l(this.p0,this.p1,this.p2,this.p3,this.geo)}isEqual(t){return this.geo.isEqualVec2(this.p0,t.p0)&&this.geo.isEqualVec2(this.p1,t.p1)&&this.geo.isEqualVec2(this.p2,t.p2)&&this.geo.isEqualVec2(this.p3,t.p3)}start(){return this.p0}start2(){return this.p1}end2(){return this.p2}end(){return this.p3}setStart(t){this.p0=t}setEnd(t){this.p3=t}point(t){const e=this.p0,s=this.p1,n=this.p2,i=this.p3;if(0===t)return e;if(1===t)return i;const o=(1-t)*(1-t),r=t*t,a=o*(1-t),h=3*o*t,l=3*r*(1-t),c=r*t;return[e[0]*a+s[0]*h+n[0]*l+i[0]*c,e[1]*a+s[1]*h+n[1]*l+i[1]*c]}split(t){if(t.length<=0)return[this];const s=[],n=(t,n)=>{const[i,o,r,a]=t,h=e(i,o,n),c=e(o,r,n),u=e(r,a,n),p=e(h,c,n),d=e(c,u,n),g=e(p,d,n);return s.push(new l(i,h,p,g,this.geo)),[g,d,u,a]};let i=[this.p0,this.p1,this.p2,this.p3],o=0;for(const e of t)i=n(i,(e-o)/(1-o)),o=e;return s.push(new l(i[0],i[1],i[2],i[3],this.geo)),s}reverse(){return new l(this.p3,this.p2,this.p1,this.p0,this.geo)}getCubicCoefficients(t){const e=this.p0[t],s=this.p1[t],n=this.p2[t];return[this.p3[t]-3*n+3*s-e,3*n-6*s+3*e,3*s-3*e,e]}boundingTValues(){const t=new o(this.geo),e=(e,s,n,i)=>{const o=3*i-9*n+9*s-3*e,r=6*e-12*s+6*n,a=3*s-3*e;if(0===this.geo.snap0(o))t.add(-a/r);else{const e=r*r-4*o*a;if(e>=0){const s=Math.sqrt(e);t.add((-r+s)/(2*o)),t.add((-r-s)/(2*o))}}return t},s=this.p0,n=this.p1,i=this.p2,r=this.p3;return e(s[0],n[0],i[0],r[0]),e(s[1],n[1],i[1],r[1]),t.list()}inflectionTValues(){const t=new o(this.geo);t.addArray(this.boundingTValues());const e=this.p0,s=this.p1,n=this.p2,i=this.p3,r=3*(s[0]-e[0]),a=3*(s[1]-e[1]),h=6*(n[0]-s[0]),l=6*(n[1]-s[1]),c=3*(i[0]-n[0]),u=3*(i[1]-n[1]),p=6*(n[0]-2*s[0]+e[0]),d=6*(n[1]-2*s[1]+e[1]),g=r-h+c,f=a-l+u,m=h-2*r,v=l-2*a,w=6*(i[0]-2*n[0]+s[0])-p,b=6*(i[1]-2*n[1]+s[1])-d,S=g*b-f*w,P=g*d+m*b-f*p-v*w,M=m*d+r*b-v*p-a*w,y=r*d-a*p;for(const e of this.geo.solveCubic(S,P,M,y))t.add(e);return t.list()}boundingBox(){const t=this.p0,e=this.p3,s=[Math.min(t[0],e[0]),Math.min(t[1],e[1])],n=[Math.max(t[0],e[0]),Math.max(t[1],e[1])];for(const t of this.boundingTValues()){const e=this.point(t);s[0]=Math.min(s[0],e[0]),s[1]=Math.min(s[1],e[1]),n[0]=Math.max(n[0],e[0]),n[1]=Math.max(n[1],e[1])}return[s,n]}mapXtoT(t,e=!1){if(0===this.geo.snap0(this.p0[0]-t))return 0;if(0===this.geo.snap0(this.p3[0]-t))return 1;const s=this.p0[0]-t,n=this.p1[0]-t,i=this.p2[0]-t,o=[this.p3[0]-t-3*i+3*n-s,3*i-6*n+3*s,3*n-3*s,s];for(const t of this.geo.solveCubic(o[0],o[1],o[2],o[3])){const e=this.geo.snap01(t);if(e>=0&&e<=1)return t}if(e||t>=Math.min(this.p0[0],this.p3[0])&&t<=Math.max(this.p0[0],this.p3[0]))for(let t=0;t<4;t++){let t=-1;for(let e=0;e<4;e++)0!==o[e]&&(t<0||Math.abs(o[e])=0&&e<=1)return t}}return!1}mapXtoY(t,e=!1){const s=this.mapXtoT(t,e);return!1!==s&&this.point(s)[1]}pointOn(t){if(this.geo.isEqualVec2(this.p0,t)||this.geo.isEqualVec2(this.p3,t))return!0;const e=this.mapXtoY(t[0]);return!1!==e&&0===this.geo.snap0(e-t[1])}toLine(){const t=this.p0,e=this.p1,s=this.p2,n=this.p3;return 0===this.geo.snap0(t[0]-e[0])&&0===this.geo.snap0(t[0]-s[0])&&0===this.geo.snap0(t[0]-n[0])||0===this.geo.snap0(t[1]-e[1])&&0===this.geo.snap0(t[1]-s[1])&&0===this.geo.snap0(t[1]-n[1])?new h(t,n,this.geo):null}draw(t){const e=this.p0,s=this.p1,n=this.p2,i=this.p3;return t.moveTo(e[0],e[1]),t.bezierCurveTo(s[0],s[1],n[0],n[1],i[0],i[1]),t}}function c(t,e){const s=e.p1[0]-e.p0[0],n=e.p1[1]-e.p0[1];return((t[0]-e.p0[0])*s+(t[1]-e.p0[1])*n)/(s*s+n*n)}function u(t,e,s){const n=t.geo,i=t.p0,o=t.p1,a=e.p0,h=e.p1,l=o[0]-i[0],u=o[1]-i[1],p=h[0]-a[0],d=h[1]-a[1],g=l*d-u*p;if(0===n.snap0(g)){if(!n.isCollinear(i,o,a))return null;const s=c(e.p0,t),r=c(e.p1,t),h=n.snap01(Math.min(s,r)),l=n.snap01(Math.max(s,r));if(l<0||h>1)return null;const u=c(t.p0,e),p=c(t.p1,e),d=n.snap01(Math.min(u,p)),g=n.snap01(Math.max(u,p));return g<0||d>1?null:{kind:"tRangePairs",tStart:[Math.max(0,h),Math.max(0,d)],tEnd:[Math.min(1,l),Math.min(1,g)]}}const f=i[0]-a[0],m=i[1]-a[1];return new r(s,n).add((p*m-d*f)/g,(l*m-u*f)/g).done()}function p(t,e,s,n){const i=t.geo,o=t.p0,a=t.p1,h=a[1]-o[1],l=o[0]-a[0];if(0===i.snap0(l)){const t=e.mapXtoT(o[0],!1);if(!1===t)return null;const a=(e.point(t)[1]-o[1])/h,l=new r(s,i);return n?l.add(t,a):l.add(a,t),l.done()}const c=h*o[0]+l*o[1],u=e.getCubicCoefficients(0),p=e.getCubicCoefficients(1),d=h*u[0]+l*p[0],g=h*u[1]+l*p[1],f=h*u[2]+l*p[2],m=h*u[3]+l*p[3]-c,v=i.solveCubic(d,g,f,m),w=new r(s,i);if(0===i.snap0(h))for(const t of v){const e=u[0]*t*t*t+u[1]*t*t+u[2]*t+u[3],s=(o[0]-e)/l;n?w.add(t,s):w.add(s,t)}else for(const t of v){const e=(p[0]*t*t*t+p[1]*t*t+p[2]*t+p[3]-o[1])/h;n?w.add(t,e):w.add(e,t)}return w.done()}function d(t,e,n){const i=t.geo;if(i.isEqualVec2(t.p0,e.p0))return i.isEqualVec2(t.p3,e.p3)?i.isEqualVec2(t.p1,e.p1)&&i.isEqualVec2(t.p2,e.p2)?{kind:"tRangePairs",tStart:[0,0],tEnd:[1,1]}:{kind:"tValuePairs",tValuePairs:[[0,0],[1,1]]}:{kind:"tValuePairs",tValuePairs:[[0,0]]};if(i.isEqualVec2(t.p0,e.p3))return{kind:"tValuePairs",tValuePairs:[[0,1]]};if(i.isEqualVec2(t.p3,e.p0))return{kind:"tValuePairs",tValuePairs:[[1,0]]};if(i.isEqualVec2(t.p3,e.p3))return{kind:"tValuePairs",tValuePairs:[[1,1]]};const o=new r(n,i),a=(t,e,n,r,h,l)=>{if(!s(t.boundingBox(),r.boundingBox()))return;const c=(e+n)/2,u=(h+l)/2;if(0===i.snap0(n-e)&&0===i.snap0(l-h))return void o.add(c,u);const[p,d]=t.split([.5]),[g,f]=r.split([.5]);a(p,e,c,g,h,u),a(d,c,n,g,h,u),a(p,e,c,f,u,l),a(d,c,n,f,u,l)};return a(t,0,1,e,0,1),o.done()}function g(t,e,s){if(t instanceof h){if(e instanceof h)return u(t,e,s);if(e instanceof l)return p(t,e,s,!1)}else if(t instanceof l){if(e instanceof h)return p(e,t,s,!0);if(e instanceof l)return d(t,e,s)}throw new Error("PolyBool: Unknown segment instance in segmentsIntersect")}class f{constructor(t,e=null,s=!1,n=null){var i,o,r;this.otherFill=null,this.id=null!==(i=null==n?void 0:n.segmentId())&&void 0!==i?i:-1,this.data=t,this.myFill={above:null!==(o=null==e?void 0:e.above)&&void 0!==o?o:null,below:null!==(r=null==e?void 0:e.below)&&void 0!==r?r:null},this.closed=s}}class m extends f{}class v extends f{}function w(t,e){if(t instanceof m)return new m(t.data,t.myFill,t.closed,e);if(t instanceof v)return new v(t.data,t.myFill,t.closed,e);throw new Error("PolyBool: Unknown SegmentBool in copySegmentBool")}class b{constructor(t,e,s,n){this.status=null,this.isStart=t,this.p=e,this.seg=s,this.primary=n}}class S{constructor(){this.nodes=[]}remove(t){const e=this.nodes.indexOf(t);e>=0&&this.nodes.splice(e,1)}getIndex(t){return this.nodes.indexOf(t)}isEmpty(){return this.nodes.length<=0}getHead(){return this.nodes[0]}removeHead(){this.nodes.shift()}insertBefore(t,e){this.findTransition(t,e).insert(t)}findTransition(t,e){var s,n;let i=0,o=this.nodes.length;for(;i>1;r=this.nodes[s],e(t)-e(r)>0?o=s:i=s+1}var r;return{before:i<=0?null:null!==(s=this.nodes[i-1])&&void 0!==s?s:null,after:null!==(n=this.nodes[i])&&void 0!==n?n:null,insert:t=>(this.nodes.splice(i,0,t),t)}}}class P{constructor(t,e,s=null){this.events=new S,this.status=new S,this.currentPath=[],this.selfIntersection=t,this.geo=e,this.log=s}compareEvents(t,e,s,n,i,o,r,a){const l=this.geo.compareVec2(e,o);return 0!==l?l:n instanceof h&&a instanceof h&&this.geo.isEqualVec2(s,r)?0:t!==i?t?1:-1:this.compareSegments(a,n)}addEvent(t){this.events.insertBefore(t,(e=>e===t?0:this.compareEvents(t.isStart,t.p,t.other.p,t.seg.data,e.isStart,e.p,e.other.p,e.seg.data)))}divideEvent(t,e,s){var n,i;null===(n=this.log)||void 0===n||n.segmentDivide(t.seg,s);const[o,r]=t.seg.data.split([e]);o.setEnd(s),r.setStart(s);const a=r instanceof h?new m(r,t.seg.myFill,t.seg.closed,this.log):r instanceof l?new v(r,t.seg.myFill,t.seg.closed,this.log):null;if(!a)throw new Error("PolyBool: Unknown segment data in divideEvent");return this.events.remove(t.other),t.seg.data=o,null===(i=this.log)||void 0===i||i.segmentChop(t.seg),t.other.p=s,this.addEvent(t.other),this.addSegment(a,t.primary)}beginPath(){this.currentPath=[]}closePath(){for(const t of this.currentPath)t.closed=!0}addSegment(t,e){const s=new b(!0,t.data.start(),t,e),n=new b(!1,t.data.end(),t,e);return s.other=n,n.other=s,this.addEvent(s),this.addEvent(n),s}addLine(t,e,s=!0){const n=this.geo.compareVec2(t,e);if(0===n)return;const i=new m(new h(n<0?t:e,n<0?e:t,this.geo),null,!1,this.log);this.currentPath.push(i),this.addSegment(i,s)}addCurve(t,e,s,n,i=!0){const o=new l(t,e,s,n,this.geo),r=o.split(o.inflectionTValues());for(const t of r){const e=this.geo.compareVec2(t.start(),t.end());if(0===e)continue;const s=t.toLine();if(s)this.addLine(s.p0,s.p1,i);else{const s=new v(e<0?t:t.reverse(),null,!1,this.log);this.currentPath.push(s),this.addSegment(s,i)}}}compareSegments(t,e){let s=t.start(),n=e.start2();const i=e.start();if(e.pointOn(s)){if(s=t.start2(),e.pointOn(s)){if(t instanceof h){if(e instanceof h)return 0;e instanceof l&&(s=t.point(.5))}t instanceof l&&(s=t.end())}if(e instanceof l&&0===this.geo.snap0(s[0]-i[0])&&0===this.geo.snap0(n[0]-i[0]))return Math.sign(i[1]-s[1])}else{if(e instanceof l){const t=e.mapXtoY(s[0],!0);if(!1!==t)return Math.sign(t-s[1])}if(t instanceof l){const s=g(t,e,!0);if(s&&"tValuePairs"===s.kind)for(const e of s.tValuePairs){const s=this.geo.snap01(e[0]);if(s>0&&s<1){n=t.point(s);break}}}}const[o,r]=s,[a,c]=n,[u,p]=i;return Math.sign((a-o)*(p-r)-(c-r)*(u-o))}statusFindSurrounding(t){return this.status.findTransition(t,(e=>{if(t===e)return 0;const s=this.compareSegments(t.seg.data,e.seg.data);return 0===s?-1:s}))}checkIntersection(t,e){var s;const n=t.seg,i=e.seg;null===(s=this.log)||void 0===s||s.checkIntersection(n,i);const o=g(n.data,i.data,!1);if(null===o)return null;if("tRangePairs"===o.kind){const{tStart:[s,r],tEnd:[a,h]}=o;if(1===s&&1===a&&0===r&&0===h||0===s&&0===a&&1===r&&1===h)return null;if(0===s&&1===a&&0===r&&1===h)return e;const l=n.data.start(),c=n.data.end(),u=i.data.end();return 0===s&&0===r?(1===a?this.divideEvent(e,h,c):this.divideEvent(t,a,u),e):(r>0&&r<1&&(1===a&&1===h||(1===a?this.divideEvent(e,h,c):this.divideEvent(t,a,u)),this.divideEvent(e,r,l)),null)}if("tValuePairs"===o.kind){if(o.tValuePairs.length<=0)return null;let s=o.tValuePairs[0];for(let t=1;t0&&r<1&&this.divideEvent(t,r,h),a>0&&a<1&&this.divideEvent(e,a,h),null}throw new Error("PolyBool: Unknown intersection type")}calculate(){var t,e,s,n,i,o,r,a;const h=[];for(;!this.events.isEmpty();){const a=this.events.getHead();if(null===(t=this.log)||void 0===t||t.vert(a.p[0]),a.isStart){null===(e=this.log)||void 0===e||e.segmentNew(a.seg,a.primary);const t=this.statusFindSurrounding(a),r=t.before,h=t.after;null===(s=this.log)||void 0===s||s.tempStatus(a.seg,!!r&&r.seg,!!h&&h.seg);const l=()=>{if(r){const t=this.checkIntersection(a,r);if(t)return t}return h?this.checkIntersection(a,h):null},c=l();if(c){if(this.selfIntersection){let t;t=null===a.seg.myFill.below?a.seg.closed:a.seg.myFill.above!==a.seg.myFill.below,t&&(c.seg.myFill.above=!c.seg.myFill.above)}else c.seg.otherFill=a.seg.myFill;null===(n=this.log)||void 0===n||n.segmentUpdate(c.seg),this.events.remove(a.other),this.events.remove(a)}if(this.events.getHead()!==a){null===(i=this.log)||void 0===i||i.rewind(a.seg);continue}if(this.selfIntersection){let t;t=null===a.seg.myFill.below?a.seg.closed:a.seg.myFill.above!==a.seg.myFill.below,a.seg.myFill.below=!!h&&h.seg.myFill.above,a.seg.myFill.above=t?!a.seg.myFill.below:a.seg.myFill.below}else if(null===a.seg.otherFill){let t;if(h)if(a.primary===h.primary){if(null===h.seg.otherFill)throw new Error("PolyBool: Unexpected state of otherFill (null)");t=h.seg.otherFill.above}else t=h.seg.myFill.above;else t=!1;a.seg.otherFill={above:t,below:t}}null===(o=this.log)||void 0===o||o.status(a.seg,!!r&&r.seg,!!h&&h.seg),a.other.status=t.insert(a)}else{const t=a.status;if(null===t)throw new Error("PolyBool: Zero-length segment detected; your epsilon is probably too small or too large");const e=this.status.getIndex(t);if(e>0&&eMath.abs(i)?(t.p3[0]-t.p2[0])/n:(t.p3[1]-t.p2[1])/i,r=s.snap01(o);if(0!==r&&1!==r){const n=new l(t.p0,[t.p0[0]+(t.p1[0]-t.p0[0])/o,t.p0[1]+(t.p1[1]-t.p0[1])/o],[e.p2[0]-o*(e.p3[0]-e.p2[0])/(1-o),e.p2[1]-o*(e.p3[1]-e.p2[1])/(1-o)],e.p3,s),[i,r]=n.split([o]);if(i.isEqual(t)&&r.isEqual(e))return n}}}function V(t,e,s){if(t!==e){if(t instanceof h&&e instanceof h)return E(t,e,s);if(t instanceof l&&e instanceof l)return x(t,e,s)}}function T(t,e,s){const n=[],i=[],o=[];for(const r of t){const t=r.myFill.above?r.data:r.data.reverse(),a=r.closed,l=a?n:i,c=t.start(),u=t.end();if(t instanceof h&&e.isEqualVec2(c,u)){console.warn("PolyBool: Warning: Zero-length segment detected; your epsilon is probably too small or too large");continue}let p,d;null==s||s.chainStart(t,a);for(let t=0;t0&&(null==s||s.chainClose(p,a),o.push(n)):(null==s||s.chainJoin(p,d,a),l[p]=n.concat(i)),l.splice(d,1)}else if(null!=p){null==s||s.chainMatch(p,a);const n=l[p],i=V(n[n.length-1],t,e);null==s||s.chainAddTail(p,t,a),null!=i?n[n.length-1]=i:n.push(t)}else if(null!=d){null==s||s.chainMatch(d,a);const n=l[d],i=V(t,n[0],e);null==s||s.chainAddHead(d,t,a),null!=i?n[0]=i:n.unshift(t)}else l.push([t]),null==s||s.chainNew(t,a)}return o.push(...i),o}function C(t,e,s,n){const[i,o,r,a,c,u]=n;s.beginPath();for(const n of t){if(n.length<=0)continue;for(let t=0;t{},moveTo:()=>{e.push([])},lineTo:(t,s)=>{e[e.length-1].push([t,s])},bezierCurveTo:(t,s,n,i,o,r)=>{e[e.length-1].push([t,s,n,i,o,r])},closePath:()=>{}};return t.shape.output(s),{regions:e,inverted:t.inverted}}union(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectUnion(i);return this.polygon(o)}intersect(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectIntersect(i);return this.polygon(o)}difference(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectDifference(i);return this.polygon(o)}differenceRev(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectDifferenceRev(i);return this.polygon(o)}xor(t,e){const s=this.segments(t),n=this.segments(e),i=this.combine(s,n),o=this.selectXor(i);return this.polygon(o)}}const B=new I;export{b as EventBool,n as Geometry,i as GeometryEpsilon,P as Intersecter,S as ListBool,I as PolyBool,a as SegmentBase,f as SegmentBoolBase,v as SegmentBoolCurve,m as SegmentBoolLine,T as SegmentChainer,l as SegmentCurve,h as SegmentLine,y as SegmentSelector,r as SegmentTValuePairsBuilder,o as SegmentTValuesBuilder,F as Shape,q as ShapeCombined,s as boundingBoxesIntersect,w as copySegmentBool,B as default,x as joinCurves,E as joinLines,V as joinSegments,t as lerp,e as lerpVec2,c as projectPointOntoSegmentLine,d as segmentCurveIntersectSegmentCurve,p as segmentLineIntersectSegmentCurve,u as segmentLineIntersectSegmentLine,g as segmentsIntersect,C as segmentsToReceiver}; diff --git a/src/BuildLog.ts b/src/BuildLog.ts index 14abc12..77154f2 100644 --- a/src/BuildLog.ts +++ b/src/BuildLog.ts @@ -9,11 +9,6 @@ import { type SegmentBool } from "./Intersecter"; import { type Vec2 } from "./Geometry"; import { type Segment } from "./Segment"; -interface ISegFill { - seg: Segment; - fill: boolean; -} - export default class BuildLog { list: Array<{ type: string; data: unknown }> = []; nextSegmentId = 0; @@ -89,12 +84,12 @@ export default class BuildLog { this.push("selected", { segs }); } - chainStart(sf: ISegFill, closed: boolean) { - this.push("chain_start", { sf, closed }); + chainStart(seg: Segment, closed: boolean) { + this.push("chain_start", { sf: { seg, fill: true }, closed }); } - chainNew(sf: ISegFill, closed: boolean) { - this.push("chain_new", { sf, closed }); + chainNew(seg: Segment, closed: boolean) { + this.push("chain_new", { sf: { seg, fill: true }, closed }); } chainMatch(index: number, closed: boolean) { @@ -105,33 +100,33 @@ export default class BuildLog { this.push("chain_close", { index, closed }); } - chainAddHead(index: number, sf: ISegFill, closed: boolean) { - this.push("chain_add_head", { index, sf, closed }); + chainAddHead(index: number, seg: Segment, closed: boolean) { + this.push("chain_add_head", { index, sf: { seg, fill: true }, closed }); } - chainAddTail(index: number, sf: ISegFill, closed: boolean) { - this.push("chain_add_tail", { index, sf, closed }); + chainAddTail(index: number, seg: Segment, closed: boolean) { + this.push("chain_add_tail", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyHead(index: number, sf: ISegFill, closed: boolean) { - this.push("chain_simp_head", { index, sf, closed }); + chainSimplifyHead(index: number, seg: Segment, closed: boolean) { + this.push("chain_simp_head", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyTail(index: number, sf: ISegFill, closed: boolean) { - this.push("chain_simp_tail", { index, sf, closed }); + chainSimplifyTail(index: number, seg: Segment, closed: boolean) { + this.push("chain_simp_tail", { index, sf: { seg, fill: true }, closed }); } - chainSimplifyClose(index: number, sf: ISegFill, closed: boolean) { - this.push("chain_simp_close", { index, sf, closed }); + chainSimplifyClose(index: number, seg: Segment, closed: boolean) { + this.push("chain_simp_close", { index, sf: { seg, fill: true }, closed }); } chainSimplifyJoin( index1: number, index2: number, - sf: ISegFill, + seg: Segment, closed: boolean, ) { - this.push("chain_simp_join", { index1, index2, sf, closed }); + this.push("chain_simp_join", { index1, index2, sf: { seg, fill: true }, closed }); } chainConnect(index1: number, index2: number, closed: boolean) { diff --git a/src/SegmentChainer.ts b/src/SegmentChainer.ts index 5dbc657..c10de99 100644 --- a/src/SegmentChainer.ts +++ b/src/SegmentChainer.ts @@ -34,18 +34,17 @@ export function joinLines( seg1: SegmentLine, seg2: SegmentLine, geo: Geometry, -): SegmentLine | false { +): SegmentLine | undefined { if (geo.isCollinear(seg1.p0, seg1.p1, seg2.p1)) { return new SegmentLine(seg1.p0, seg2.p1, geo); } - return false; } export function joinCurves( seg1: SegmentCurve, seg2: SegmentCurve, geo: Geometry, -): SegmentCurve | false { +): SegmentCurve | undefined { if (geo.isCollinear(seg1.p2, seg1.p3, seg2.p1)) { const dx = seg2.p1[0] - seg1.p2[0]; const dy = seg2.p1[1] - seg1.p2[1]; @@ -75,29 +74,22 @@ export function joinCurves( } } } - return false; } export function joinSegments( seg1: Segment | undefined, seg2: Segment | undefined, geo: Geometry, -): Segment | false { - if (seg1 === seg2) { - return false; - } - if (seg1 instanceof SegmentLine && seg2 instanceof SegmentLine) { - return joinLines(seg1, seg2, geo); - } - if (seg1 instanceof SegmentCurve && seg2 instanceof SegmentCurve) { - return joinCurves(seg1, seg2, geo); - } - return false; -} +): Segment | undefined { + if (seg1 !== seg2) { + if (seg1 instanceof SegmentLine && seg2 instanceof SegmentLine) { + return joinLines(seg1, seg2, geo); + } -interface ISegsFill { - segs: Segment[]; - fill: boolean; + if (seg1 instanceof SegmentCurve && seg2 instanceof SegmentCurve) { + return joinCurves(seg1, seg2, geo); + } + } } export function SegmentChainer( @@ -105,30 +97,17 @@ export function SegmentChainer( geo: Geometry, log: BuildLog | null, ): Segment[][] { - const closedChains: ISegsFill[] = []; - const openChains: ISegsFill[] = []; + const closedChains: Segment[][] = []; + const openChains: Segment[][] = []; const regions: Segment[][] = []; for (const segb of segments) { - let seg = segb.data; + const seg = segb.myFill.above ? segb.data : segb.data.reverse(); const closed = segb.closed; const chains = closed ? closedChains : openChains; const pt1 = seg.start(); const pt2 = seg.end(); - const reverseChain = (index: number) => { - log?.chainReverse(index, closed); - const newChain: Segment[] = []; - for (const seg of chains[index].segs) { - newChain.unshift(seg.reverse()); - } - chains[index] = { - segs: newChain, - fill: !chains[index].fill, - }; - return newChain; - }; - if (seg instanceof SegmentLine && geo.isEqualVec2(pt1, pt2)) { console.warn( "PolyBool: Warning: Zero-length segment detected; your epsilon is " + @@ -137,264 +116,115 @@ export function SegmentChainer( continue; } - log?.chainStart({ seg, fill: !!segb.myFill.above }, closed); - - // search for two chains that this segment matches - const firstMatch = { - index: 0, - matchesHead: false, - matchesPt1: false, - }; - const secondMatch = { - index: 0, - matchesHead: false, - matchesPt1: false, - }; - let nextMatch: typeof firstMatch | null = firstMatch; - function setMatch( - index: number, - matchesHead: boolean, - matchesPt1: boolean, - ) { - // return true if we've matched twice - if (nextMatch) { - nextMatch.index = index; - nextMatch.matchesHead = matchesHead; - nextMatch.matchesPt1 = matchesPt1; + log?.chainStart(seg, closed); + + let startMatch: number | undefined; + let endMatch: number | undefined; + + for (let i = 0; i < chains.length; i++) { + const chain = chains[i]; + + if (startMatch == null && geo.isEqualVec2(chain[chain.length - 1].end(), pt1)) { + startMatch = i; } - if (nextMatch === firstMatch) { - nextMatch = secondMatch; - return false; + + if (endMatch == null && geo.isEqualVec2(chain[0].start(), pt2)) { + endMatch = i; } - nextMatch = null; - return true; // we've matched twice, we're done here - } - for (let i = 0; i < chains.length; i++) { - const chain = chains[i].segs; - const head = chain[0].start(); - const tail = chain[chain.length - 1].end(); - if (geo.isEqualVec2(head, pt1)) { - if (setMatch(i, true, true)) { - break; - } - } else if (geo.isEqualVec2(head, pt2)) { - if (setMatch(i, true, false)) { - break; - } - } else if (geo.isEqualVec2(tail, pt1)) { - if (setMatch(i, false, true)) { - break; - } - } else if (geo.isEqualVec2(tail, pt2)) { - if (setMatch(i, false, false)) { - break; - } + + if (startMatch != null && endMatch != null) { + break; } } - if (nextMatch === firstMatch) { - // we didn't match anything, so create a new chain - const fill = !!segb.myFill.above; - chains.push({ segs: [seg], fill }); - log?.chainNew({ seg, fill }, closed); - } else if (nextMatch === secondMatch) { - // we matched a single chain - const index = firstMatch.index; - log?.chainMatch(index, closed); - - // add the other point to the apporpriate end - const { segs: chain, fill } = chains[index]; - if (firstMatch.matchesHead) { - if (firstMatch.matchesPt1) { - seg = seg.reverse(); - log?.chainAddHead(index, { seg, fill }, closed); - chain.unshift(seg); - } else { - log?.chainAddHead(index, { seg, fill }, closed); - chain.unshift(seg); - } + if (startMatch != null && endMatch != null) { + // otherwise, we matched two chains, so we need to combine those chains together + + log?.chainConnect(startMatch, endMatch, closed); + + // index1 gets index2 appended to it, and index2 is removed + const chain1 = chains[startMatch]; + const chain2 = chains[endMatch]; + + // add seg to chain1's tail and simplify + const next = chain1[chain1.length - 1]; + const newEnd = joinSegments(next, seg, geo); + + if (newEnd != null) { + chain1[chain1.length - 1] = newEnd; + log?.chainSimplifyTail(startMatch, newEnd, closed); } else { - if (firstMatch.matchesPt1) { - log?.chainAddTail(index, { seg, fill }, closed); - chain.push(seg); - } else { - seg = seg.reverse(); - log?.chainAddTail(index, { seg, fill }, closed); - chain.push(seg); - } + chain1.push(seg); + } + + // simplify chain2's head + const tail = chain1[chain1.length - 1]; + const head = chain2[0]; + const newJoin = joinSegments(tail, head, geo); + + if (newJoin != null) { + chain2.shift(); + chain1[chain1.length - 1] = newJoin; + log?.chainSimplifyJoin( + startMatch, + endMatch, + newJoin, + closed, + ); } - // simplify chain - if (firstMatch.matchesHead) { - const next = chain[1]; - const newSeg = joinSegments(seg, next, geo); - if (newSeg) { - chain.shift(); - chain[0] = newSeg; - log?.chainSimplifyHead(index, { seg: newSeg, fill }, closed); + if (startMatch === endMatch) { + if (chain1.length > 0) { + // we have a closed chain! + log?.chainClose(startMatch, closed); + regions.push(chain1); } } else { - const next = chain[chain.length - 2]; - const newSeg = joinSegments(next, seg, geo); - if (newSeg) { - chain.pop(); - chain[chain.length - 1] = newSeg; - log?.chainSimplifyTail(index, { seg: newSeg, fill }, closed); - } + log?.chainJoin(startMatch, endMatch, closed); + chains[startMatch] = chain1.concat(chain2); } + chains.splice(endMatch, 1); + } else if (startMatch != null) { + // we matched a single chain at the start + log?.chainMatch(startMatch, closed); - // check for closed chain - if (closed) { - let finalChain = chain; - let segS = finalChain[0]; - let segE = finalChain[finalChain.length - 1]; - if ( - finalChain.length > 0 && - geo.isEqualVec2(segS.start(), segE.end()) - ) { - // see if chain is clockwise - let winding = 0; - let last = finalChain[0].start(); - for (const seg of finalChain) { - const here = seg.end(); - winding += here[1] * last[0] - here[0] * last[1]; - last = here; - } - // this assumes Cartesian coordinates (Y is positive going up) - const isClockwise = winding < 0; - if (isClockwise === fill) { - finalChain = reverseChain(index); - segS = finalChain[0]; - segE = finalChain[finalChain.length - 1]; - } - - const newStart = joinSegments(segE, segS, geo); - if (newStart) { - finalChain.pop(); - finalChain[0] = newStart; - log?.chainSimplifyClose(index, { seg: newStart, fill }, closed); - } + const chain = chains[startMatch]; - // we have a closed chain! - log?.chainClose(index, closed); - chains.splice(index, 1); - regions.push(finalChain); - } + const next = chain[chain.length - 1]; + const newSeg = joinSegments(next, seg, geo); + + log?.chainAddTail(startMatch, seg, closed); + + if (newSeg != null) { + chain[chain.length - 1] = newSeg; + } else { + chain.push(seg); } - } else { - // otherwise, we matched two chains, so we need to combine those chains together - const appendChain = (index1: number, index2: number) => { - // index1 gets index2 appended to it, and index2 is removed - const { segs: chain1, fill } = chains[index1]; - const { segs: chain2 } = chains[index2]; + } else if (endMatch != null) { + // we matched a single chain at the end + log?.chainMatch(endMatch, closed); - // add seg to chain1's tail - log?.chainAddTail(index1, { seg, fill }, closed); - chain1.push(seg); + const chain = chains[endMatch]; - // simplify chain1's tail - const next = chain1[chain1.length - 2]; - const newEnd = joinSegments(next, seg, geo); - if (newEnd) { - chain1.pop(); - chain1[chain1.length - 1] = newEnd; - log?.chainSimplifyTail(index1, { seg: newEnd, fill }, closed); - } + const next = chain[0]; + const newSeg = joinSegments(seg, next, geo); - // simplify chain2's head - const tail = chain1[chain1.length - 1]; - const head = chain2[0]; - const newJoin = joinSegments(tail, head, geo); - if (newJoin) { - chain2.shift(); - chain1[chain1.length - 1] = newJoin; - log?.chainSimplifyJoin( - index1, - index2, - { seg: newJoin, fill }, - closed, - ); - } + log?.chainAddHead(endMatch, seg, closed); - log?.chainJoin(index1, index2, closed); - chains[index1].segs = chain1.concat(chain2); - chains.splice(index2, 1); - }; - - const F = firstMatch.index; - const S = secondMatch.index; - - log?.chainConnect(F, S, closed); - - // reverse the shorter chain, if needed - const reverseF = chains[F].segs.length < chains[S].segs.length; - if (firstMatch.matchesHead) { - if (secondMatch.matchesHead) { - if (reverseF) { - if (!firstMatch.matchesPt1) { - // <<<< F <<<< <-- >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< --> >>>> S >>>> - reverseChain(F); - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } else { - if (firstMatch.matchesPt1) { - // <<<< F <<<< --> >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< <-- >>>> S >>>> - reverseChain(S); - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } - } else { - if (firstMatch.matchesPt1) { - // <<<< F <<<< --> >>>> S >>>> - seg = seg.reverse(); - } - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } + if (newSeg != null) { + chain[0] = newSeg; } else { - if (secondMatch.matchesHead) { - if (!firstMatch.matchesPt1) { - // >>>> F >>>> <-- >>>> S >>>> - seg = seg.reverse(); - } - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } else { - if (reverseF) { - if (firstMatch.matchesPt1) { - // >>>> F >>>> --> <<<< S <<<< - seg = seg.reverse(); - } - // >>>> F >>>> <-- <<<< S <<<< - reverseChain(F); - // <<<< F <<<< <-- <<<< S <<<< logically same as: - // >>>> S >>>> --> >>>> F >>>> - appendChain(S, F); - } else { - if (!firstMatch.matchesPt1) { - // >>>> F >>>> <-- <<<< S <<<< - seg = seg.reverse(); - } - // >>>> F >>>> --> <<<< S <<<< - reverseChain(S); - // >>>> F >>>> --> >>>> S >>>> - appendChain(F, S); - } - } + chain.unshift(seg); } + } else { + // we didn't match anything, so create a new chain + chains.push([seg]); + log?.chainNew(seg, closed); } } - for (const { segs } of openChains) { - regions.push(segs); - } + + regions.push(...openChains); + return regions; } diff --git a/src/polybool.test.ts b/src/polybool.test.ts index cfd5769..5a169b2 100644 --- a/src/polybool.test.ts +++ b/src/polybool.test.ts @@ -86,9 +86,9 @@ const tests: { name: string, func(): void }[] = [ polybool.intersect(triangle1, triangle2), { regions: [[ - [10, 0], + [7.5, 5], [5, 0], - [7.5, 5] + [10, 0], ]], inverted: false } @@ -120,11 +120,11 @@ const tests: { name: string, func(): void }[] = [ polybool.union(box1, curve1), { regions: [[ - [10, 0], - [10, -2.5, 7.5, -3.75, 5, -3.75], - [5, -5], + [0, 0], [0, -5], - [0, 0] + [5, -5], + [5, -3.75], + [7.5, -3.75, 10, -2.5, 10, 0], ]], inverted: false } @@ -167,11 +167,11 @@ const tests: { name: string, func(): void }[] = [ 'lineTo', 110, 50, 'lineTo', 110, 110, 'closePath', - 'moveTo', 150, 150, - 'lineTo', 178, 80, - 'lineTo', 130, 50, - 'lineTo', 130, 130, + 'moveTo', 178, 80, 'lineTo', 150, 150, + 'lineTo', 130, 130, + 'lineTo', 130, 50, + 'lineTo', 178, 80, 'closePath', 'moveTo', 260, 131.25, 'lineTo', 178, 80,