@@ -1220,7 +1220,7 @@ var Two = (() => {
12201220 * @name Two.PublishDate
12211221 * @property {String} - The automatically generated publish date in the build process to verify version release candidates.
12221222 */
1223- PublishDate: "2025-11-26T06:59:54.712Z ",
1223+ PublishDate: "2025-12-01T22:57:49.092Z ",
12241224 /**
12251225 * @name Two.Identifier
12261226 * @property {String} - String prefix for all Two.js object's ids. This trickles down to SVG ids.
@@ -12132,6 +12132,332 @@ var Two = (() => {
1213212132 return xhr2;
1213312133 }
1213412134
12135+ // src/utils/boolean-operations.js
12136+ var EPSILON3 = 1e-12;
12137+ function anchorToSegment(anchor2) {
12138+ const segment = {
12139+ point: { x: anchor2.x, y: anchor2.y },
12140+ handleIn: { x: 0, y: 0 },
12141+ handleOut: { x: 0, y: 0 }
12142+ };
12143+ if (anchor2.controls) {
12144+ if (anchor2.relative) {
12145+ segment.handleIn.x = anchor2.controls.left.x;
12146+ segment.handleIn.y = anchor2.controls.left.y;
12147+ segment.handleOut.x = anchor2.controls.right.x;
12148+ segment.handleOut.y = anchor2.controls.right.y;
12149+ } else {
12150+ segment.handleIn.x = anchor2.controls.left.x - anchor2.x;
12151+ segment.handleIn.y = anchor2.controls.left.y - anchor2.y;
12152+ segment.handleOut.x = anchor2.controls.right.x - anchor2.x;
12153+ segment.handleOut.y = anchor2.controls.right.y - anchor2.y;
12154+ }
12155+ }
12156+ return segment;
12157+ }
12158+ function subdivideCurve(v0, v1, v2, v3, t) {
12159+ const u = 1 - t;
12160+ const v01 = { x: u * v0.x + t * v1.x, y: u * v0.y + t * v1.y };
12161+ const v12 = { x: u * v1.x + t * v2.x, y: u * v1.y + t * v2.y };
12162+ const v23 = { x: u * v2.x + t * v3.x, y: u * v2.y + t * v3.y };
12163+ const v012 = { x: u * v01.x + t * v12.x, y: u * v01.y + t * v12.y };
12164+ const v123 = { x: u * v12.x + t * v23.x, y: u * v12.y + t * v23.y };
12165+ const v0123 = { x: u * v012.x + t * v123.x, y: u * v012.y + t * v123.y };
12166+ return {
12167+ left: [v0, v01, v012, v0123],
12168+ right: [v0123, v123, v23, v3]
12169+ };
12170+ }
12171+ function signedDistance(point, lineStart, lineEnd) {
12172+ const dx = lineEnd.x - lineStart.x;
12173+ const dy = lineEnd.y - lineStart.y;
12174+ const lineLengthSq = dx * dx + dy * dy;
12175+ if (lineLengthSq < EPSILON3) {
12176+ const px = point.x - lineStart.x;
12177+ const py = point.y - lineStart.y;
12178+ return Math.sqrt(px * px + py * py);
12179+ }
12180+ const nx = dy;
12181+ const ny = -dx;
12182+ const lineLength = Math.sqrt(lineLengthSq);
12183+ return ((point.x - lineStart.x) * nx + (point.y - lineStart.y) * ny) / lineLength;
12184+ }
12185+ function clipCurve(v1, v2, t1Min, t1Max, t2Min, t2Max, depth, intersections) {
12186+ const MAX_DEPTH = 32;
12187+ const FLATNESS_TOLERANCE = 0.5;
12188+ if (depth > MAX_DEPTH) {
12189+ return intersections;
12190+ }
12191+ const bbox1 = {
12192+ minX: Math.min(v1[0].x, v1[1].x, v1[2].x, v1[3].x),
12193+ maxX: Math.max(v1[0].x, v1[1].x, v1[2].x, v1[3].x),
12194+ minY: Math.min(v1[0].y, v1[1].y, v1[2].y, v1[3].y),
12195+ maxY: Math.max(v1[0].y, v1[1].y, v1[2].y, v1[3].y)
12196+ };
12197+ const bbox2 = {
12198+ minX: Math.min(v2[0].x, v2[1].x, v2[2].x, v2[3].x),
12199+ maxX: Math.max(v2[0].x, v2[1].x, v2[2].x, v2[3].x),
12200+ minY: Math.min(v2[0].y, v2[1].y, v2[2].y, v2[3].y),
12201+ maxY: Math.max(v2[0].y, v2[1].y, v2[2].y, v2[3].y)
12202+ };
12203+ if (bbox1.maxX < bbox2.minX || bbox2.maxX < bbox1.minX || bbox1.maxY < bbox2.minY || bbox2.maxY < bbox1.minY) {
12204+ return intersections;
12205+ }
12206+ const flatness1 = Math.max(
12207+ Math.abs(signedDistance(v1[1], v1[0], v1[3])),
12208+ Math.abs(signedDistance(v1[2], v1[0], v1[3]))
12209+ );
12210+ const flatness2 = Math.max(
12211+ Math.abs(signedDistance(v2[1], v2[0], v2[3])),
12212+ Math.abs(signedDistance(v2[2], v2[0], v2[3]))
12213+ );
12214+ if (flatness1 < FLATNESS_TOLERANCE && flatness2 < FLATNESS_TOLERANCE) {
12215+ const intersection = lineIntersection(v1[0], v1[3], v2[0], v2[3]);
12216+ if (intersection) {
12217+ const t1 = t1Min + intersection.t1 * (t1Max - t1Min);
12218+ const t2 = t2Min + intersection.t2 * (t2Max - t2Min);
12219+ let isDuplicate = false;
12220+ const DUPLICATE_TOLERANCE = 0.01;
12221+ for (let i = 0; i < intersections.length; i++) {
12222+ const dx = intersections[i].point.x - intersection.point.x;
12223+ const dy = intersections[i].point.y - intersection.point.y;
12224+ const distSq = dx * dx + dy * dy;
12225+ if (distSq < DUPLICATE_TOLERANCE * DUPLICATE_TOLERANCE) {
12226+ isDuplicate = true;
12227+ break;
12228+ }
12229+ }
12230+ if (!isDuplicate) {
12231+ intersections.push({
12232+ point: intersection.point,
12233+ t1,
12234+ t2
12235+ });
12236+ }
12237+ }
12238+ return intersections;
12239+ }
12240+ if (flatness1 > flatness2) {
12241+ const split = subdivideCurve(v1[0], v1[1], v1[2], v1[3], 0.5);
12242+ const mid = (t1Min + t1Max) / 2;
12243+ clipCurve(split.left, v2, t1Min, mid, t2Min, t2Max, depth + 1, intersections);
12244+ clipCurve(split.right, v2, mid, t1Max, t2Min, t2Max, depth + 1, intersections);
12245+ } else {
12246+ const split = subdivideCurve(v2[0], v2[1], v2[2], v2[3], 0.5);
12247+ const mid = (t2Min + t2Max) / 2;
12248+ clipCurve(v1, split.left, t1Min, t1Max, t2Min, mid, depth + 1, intersections);
12249+ clipCurve(v1, split.right, t1Min, t1Max, mid, t2Max, depth + 1, intersections);
12250+ }
12251+ return intersections;
12252+ }
12253+ function lineIntersection(p1, p2, p3, p4) {
12254+ const x1 = p1.x, y1 = p1.y;
12255+ const x2 = p2.x, y2 = p2.y;
12256+ const x3 = p3.x, y3 = p3.y;
12257+ const x4 = p4.x, y4 = p4.y;
12258+ const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
12259+ if (Math.abs(denom) < EPSILON3) {
12260+ return null;
12261+ }
12262+ const t1 = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
12263+ const t2 = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
12264+ if (t1 >= -EPSILON3 && t1 <= 1 + EPSILON3 && t2 >= -EPSILON3 && t2 <= 1 + EPSILON3) {
12265+ return {
12266+ point: {
12267+ x: x1 + t1 * (x2 - x1),
12268+ y: y1 + t1 * (y2 - y1)
12269+ },
12270+ t1: Math.max(0, Math.min(1, t1)),
12271+ t2: Math.max(0, Math.min(1, t2))
12272+ };
12273+ }
12274+ return null;
12275+ }
12276+ function findCurveIntersections(a1, a2, b1, b2, matrix1, matrix2) {
12277+ const seg1Start = anchorToSegment(a1);
12278+ const seg1End = anchorToSegment(a2);
12279+ const seg2Start = anchorToSegment(b1);
12280+ const seg2End = anchorToSegment(b2);
12281+ let curve1 = [
12282+ seg1Start.point,
12283+ { x: seg1Start.point.x + seg1Start.handleOut.x, y: seg1Start.point.y + seg1Start.handleOut.y },
12284+ { x: seg1End.point.x + seg1End.handleIn.x, y: seg1End.point.y + seg1End.handleIn.y },
12285+ seg1End.point
12286+ ];
12287+ let curve2 = [
12288+ seg2Start.point,
12289+ { x: seg2Start.point.x + seg2Start.handleOut.x, y: seg2Start.point.y + seg2Start.handleOut.y },
12290+ { x: seg2End.point.x + seg2End.handleIn.x, y: seg2End.point.y + seg2End.handleIn.y },
12291+ seg2End.point
12292+ ];
12293+ if (matrix1) {
12294+ curve1 = curve1.map((p) => {
12295+ const [x, y] = matrix1.multiply(p.x, p.y);
12296+ return { x, y };
12297+ });
12298+ }
12299+ if (matrix2) {
12300+ curve2 = curve2.map((p) => {
12301+ const [x, y] = matrix2.multiply(p.x, p.y);
12302+ return { x, y };
12303+ });
12304+ }
12305+ const intersections = [];
12306+ clipCurve(curve1, curve2, 0, 1, 0, 1, 0, intersections);
12307+ return intersections;
12308+ }
12309+ function findPathIntersections(path1, path2) {
12310+ const intersections = [];
12311+ if (path1._update) {
12312+ path1._update();
12313+ }
12314+ if (path2._update) {
12315+ path2._update();
12316+ }
12317+ const vertices1 = path1.vertices;
12318+ const vertices2 = path2.vertices;
12319+ if (!vertices1 || !vertices2 || vertices1.length < 2 || vertices2.length < 2) {
12320+ return intersections;
12321+ }
12322+ const matrix1 = path1.worldMatrix || path1._matrix;
12323+ const matrix2 = path2.worldMatrix || path2._matrix;
12324+ for (let i = 0; i < vertices1.length - 1; i++) {
12325+ const a1 = vertices1[i];
12326+ const a2 = vertices1[i + 1];
12327+ if (i > 0 && a2.command === Commands.move) {
12328+ continue;
12329+ }
12330+ for (let j = 0; j < vertices2.length - 1; j++) {
12331+ const b1 = vertices2[j];
12332+ const b2 = vertices2[j + 1];
12333+ if (j > 0 && b2.command === Commands.move) {
12334+ continue;
12335+ }
12336+ const curveIntersections = findCurveIntersections(a1, a2, b1, b2, matrix1, matrix2);
12337+ for (const intersection of curveIntersections) {
12338+ const DUPLICATE_TOLERANCE = 0.1;
12339+ let isDuplicate = false;
12340+ for (let k = 0; k < intersections.length; k++) {
12341+ const dx = intersections[k].point.x - intersection.point.x;
12342+ const dy = intersections[k].point.y - intersection.point.y;
12343+ const distSq = dx * dx + dy * dy;
12344+ if (distSq < DUPLICATE_TOLERANCE * DUPLICATE_TOLERANCE) {
12345+ isDuplicate = true;
12346+ break;
12347+ }
12348+ }
12349+ if (!isDuplicate) {
12350+ intersections.push({
12351+ point: intersection.point,
12352+ t1: intersection.t1,
12353+ t2: intersection.t2,
12354+ index1: i,
12355+ index2: j
12356+ });
12357+ }
12358+ }
12359+ }
12360+ }
12361+ if (path1.closed && vertices1.length > 2) {
12362+ const a1 = vertices1[vertices1.length - 1];
12363+ const a2 = vertices1[0];
12364+ for (let j = 0; j < vertices2.length - 1; j++) {
12365+ const b1 = vertices2[j];
12366+ const b2 = vertices2[j + 1];
12367+ if (j > 0 && b2.command === Commands.move) {
12368+ continue;
12369+ }
12370+ const curveIntersections = findCurveIntersections(a1, a2, b1, b2, matrix1, matrix2);
12371+ for (const intersection of curveIntersections) {
12372+ const DUPLICATE_TOLERANCE = 0.1;
12373+ let isDuplicate = false;
12374+ for (let k = 0; k < intersections.length; k++) {
12375+ const dx = intersections[k].point.x - intersection.point.x;
12376+ const dy = intersections[k].point.y - intersection.point.y;
12377+ const distSq = dx * dx + dy * dy;
12378+ if (distSq < DUPLICATE_TOLERANCE * DUPLICATE_TOLERANCE) {
12379+ isDuplicate = true;
12380+ break;
12381+ }
12382+ }
12383+ if (!isDuplicate) {
12384+ intersections.push({
12385+ point: intersection.point,
12386+ t1: intersection.t1,
12387+ t2: intersection.t2,
12388+ index1: vertices1.length - 1,
12389+ index2: j
12390+ });
12391+ }
12392+ }
12393+ }
12394+ }
12395+ if (path2.closed && vertices2.length > 2) {
12396+ const b1 = vertices2[vertices2.length - 1];
12397+ const b2 = vertices2[0];
12398+ for (let i = 0; i < vertices1.length - 1; i++) {
12399+ const a1 = vertices1[i];
12400+ const a2 = vertices1[i + 1];
12401+ if (i > 0 && a2.command === Commands.move) {
12402+ continue;
12403+ }
12404+ const curveIntersections = findCurveIntersections(a1, a2, b1, b2, matrix1, matrix2);
12405+ for (const intersection of curveIntersections) {
12406+ const DUPLICATE_TOLERANCE = 0.1;
12407+ let isDuplicate = false;
12408+ for (let k = 0; k < intersections.length; k++) {
12409+ const dx = intersections[k].point.x - intersection.point.x;
12410+ const dy = intersections[k].point.y - intersection.point.y;
12411+ const distSq = dx * dx + dy * dy;
12412+ if (distSq < DUPLICATE_TOLERANCE * DUPLICATE_TOLERANCE) {
12413+ isDuplicate = true;
12414+ break;
12415+ }
12416+ }
12417+ if (!isDuplicate) {
12418+ intersections.push({
12419+ point: intersection.point,
12420+ t1: intersection.t1,
12421+ t2: intersection.t2,
12422+ index1: i,
12423+ index2: vertices2.length - 1
12424+ });
12425+ }
12426+ }
12427+ }
12428+ }
12429+ if (path1.closed && vertices1.length > 2 && path2.closed && vertices2.length > 2) {
12430+ const a1 = vertices1[vertices1.length - 1];
12431+ const a2 = vertices1[0];
12432+ const b1 = vertices2[vertices2.length - 1];
12433+ const b2 = vertices2[0];
12434+ const curveIntersections = findCurveIntersections(a1, a2, b1, b2, matrix1, matrix2);
12435+ for (const intersection of curveIntersections) {
12436+ const DUPLICATE_TOLERANCE = 0.1;
12437+ let isDuplicate = false;
12438+ for (let k = 0; k < intersections.length; k++) {
12439+ const dx = intersections[k].point.x - intersection.point.x;
12440+ const dy = intersections[k].point.y - intersection.point.y;
12441+ const distSq = dx * dx + dy * dy;
12442+ if (distSq < DUPLICATE_TOLERANCE * DUPLICATE_TOLERANCE) {
12443+ isDuplicate = true;
12444+ break;
12445+ }
12446+ }
12447+ if (!isDuplicate) {
12448+ intersections.push({
12449+ point: intersection.point,
12450+ t1: intersection.t1,
12451+ t2: intersection.t2,
12452+ index1: vertices1.length - 1,
12453+ index2: vertices2.length - 1
12454+ });
12455+ }
12456+ }
12457+ }
12458+ return intersections;
12459+ }
12460+
1213512461 // src/boolean-group.js
1213612462 var BooleanGroup = class _BooleanGroup extends Group {
1213712463 /**
@@ -15737,7 +16063,9 @@ var Two = (() => {
1573716063 Error: TwoError,
1573816064 getRatio,
1573916065 read,
15740- xhr
16066+ xhr,
16067+ findPathIntersections,
16068+ findCurveIntersections
1574116069 },
1574216070 _,
1574316071 CanvasPolyfill,
0 commit comments