@tscircuit/rectdiff 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/index.d.ts +97 -12
  2. package/dist/index.js +714 -81
  3. package/lib/RectDiffPipeline.ts +79 -13
  4. package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +284 -0
  5. package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +213 -0
  6. package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +129 -0
  7. package/lib/solvers/GapFillSolver/edge-constants.ts +48 -0
  8. package/lib/solvers/GapFillSolver/getBoundsFromCorners.ts +10 -0
  9. package/lib/solvers/GapFillSolver/projectToUncoveredSegments.ts +92 -0
  10. package/lib/solvers/GapFillSolver/visuallyOffsetLine.ts +32 -0
  11. package/lib/solvers/RectDiffSolver.ts +1 -33
  12. package/package.json +9 -6
  13. package/tests/board-outline.test.ts +1 -1
  14. package/tsconfig.json +4 -0
  15. package/vite.config.ts +6 -0
  16. package/lib/solvers/rectdiff/gapfill/detection/deduplicateGaps.ts +0 -28
  17. package/lib/solvers/rectdiff/gapfill/detection/findAllGaps.ts +0 -83
  18. package/lib/solvers/rectdiff/gapfill/detection/findGapsOnLayer.ts +0 -100
  19. package/lib/solvers/rectdiff/gapfill/detection/mergeUncoveredCells.ts +0 -75
  20. package/lib/solvers/rectdiff/gapfill/detection.ts +0 -3
  21. package/lib/solvers/rectdiff/gapfill/engine/addPlacement.ts +0 -27
  22. package/lib/solvers/rectdiff/gapfill/engine/calculateCoverage.ts +0 -44
  23. package/lib/solvers/rectdiff/gapfill/engine/findUncoveredPoints.ts +0 -43
  24. package/lib/solvers/rectdiff/gapfill/engine/getGapFillProgress.ts +0 -42
  25. package/lib/solvers/rectdiff/gapfill/engine/initGapFillState.ts +0 -57
  26. package/lib/solvers/rectdiff/gapfill/engine/stepGapFill.ts +0 -128
  27. package/lib/solvers/rectdiff/gapfill/engine/tryExpandGap.ts +0 -78
  28. package/lib/solvers/rectdiff/gapfill/engine.ts +0 -7
  29. package/lib/solvers/rectdiff/gapfill/types.ts +0 -60
  30. package/lib/solvers/rectdiff/subsolvers/GapFillSubSolver.ts +0 -253
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  // lib/RectDiffPipeline.ts
2
- import { BasePipelineSolver as BasePipelineSolver2, definePipelineStep } from "@tscircuit/solver-utils";
2
+ import {
3
+ BasePipelineSolver as BasePipelineSolver3,
4
+ definePipelineStep as definePipelineStep2
5
+ } from "@tscircuit/solver-utils";
3
6
 
4
7
  // lib/solvers/RectDiffSolver.ts
5
8
  import { BaseSolver } from "@tscircuit/solver-utils";
@@ -1117,50 +1120,6 @@ function rectsToMeshNodes(rects) {
1117
1120
  return out;
1118
1121
  }
1119
1122
 
1120
- // lib/solvers/rectdiff/gapfill/engine/calculateCoverage.ts
1121
- function calculateCoverage({ sampleResolution = 0.1 }, ctx) {
1122
- const { bounds, layerCount, obstaclesByLayer, placedByLayer } = ctx;
1123
- let totalPoints = 0;
1124
- let coveredPoints = 0;
1125
- for (let z = 0; z < layerCount; z++) {
1126
- const obstacles = obstaclesByLayer[z] ?? [];
1127
- const placed = placedByLayer[z] ?? [];
1128
- const allRects = [...obstacles, ...placed];
1129
- for (let x = bounds.x; x <= bounds.x + bounds.width; x += sampleResolution) {
1130
- for (let y = bounds.y; y <= bounds.y + bounds.height; y += sampleResolution) {
1131
- totalPoints++;
1132
- const isCovered = allRects.some(
1133
- (r) => x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height
1134
- );
1135
- if (isCovered) coveredPoints++;
1136
- }
1137
- }
1138
- }
1139
- return totalPoints > 0 ? coveredPoints / totalPoints : 1;
1140
- }
1141
-
1142
- // lib/solvers/rectdiff/gapfill/engine/findUncoveredPoints.ts
1143
- function findUncoveredPoints({ sampleResolution = 0.05 }, ctx) {
1144
- const { bounds, layerCount, obstaclesByLayer, placedByLayer } = ctx;
1145
- const uncovered = [];
1146
- for (let z = 0; z < layerCount; z++) {
1147
- const obstacles = obstaclesByLayer[z] ?? [];
1148
- const placed = placedByLayer[z] ?? [];
1149
- const allRects = [...obstacles, ...placed];
1150
- for (let x = bounds.x; x <= bounds.x + bounds.width; x += sampleResolution) {
1151
- for (let y = bounds.y; y <= bounds.y + bounds.height; y += sampleResolution) {
1152
- const isCovered = allRects.some(
1153
- (r) => x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height
1154
- );
1155
- if (!isCovered) {
1156
- uncovered.push({ x, y, z });
1157
- }
1158
- }
1159
- }
1160
- }
1161
- return uncovered;
1162
- }
1163
-
1164
1123
  // lib/solvers/RectDiffSolver.ts
1165
1124
  var RectDiffSolver = class extends BaseSolver {
1166
1125
  srj;
@@ -1209,30 +1168,6 @@ var RectDiffSolver = class extends BaseSolver {
1209
1168
  getOutput() {
1210
1169
  return { meshNodes: this._meshNodes };
1211
1170
  }
1212
- /** Get coverage percentage (0-1). */
1213
- getCoverage(sampleResolution = 0.05) {
1214
- return calculateCoverage(
1215
- { sampleResolution },
1216
- {
1217
- bounds: this.state.bounds,
1218
- layerCount: this.state.layerCount,
1219
- obstaclesByLayer: this.state.obstaclesByLayer,
1220
- placedByLayer: this.state.placedByLayer
1221
- }
1222
- );
1223
- }
1224
- /** Find uncovered points for debugging gaps. */
1225
- getUncoveredPoints(sampleResolution = 0.05) {
1226
- return findUncoveredPoints(
1227
- { sampleResolution },
1228
- {
1229
- bounds: this.state.bounds,
1230
- layerCount: this.state.layerCount,
1231
- obstaclesByLayer: this.state.obstaclesByLayer,
1232
- placedByLayer: this.state.placedByLayer
1233
- }
1234
- );
1235
- }
1236
1171
  /** Get color based on z layer for visualization. */
1237
1172
  getColorForZLayer(zLayers) {
1238
1173
  const minZ = Math.min(...zLayers);
@@ -1342,6 +1277,7 @@ var RectDiffSolver = class extends BaseSolver {
1342
1277
  height: p.rect.height,
1343
1278
  fill: colors.fill,
1344
1279
  stroke: colors.stroke,
1280
+ layer: `z${p.zLayers.join(",")}`,
1345
1281
  label: `free
1346
1282
  z:${p.zLayers.join(",")}`
1347
1283
  });
@@ -1410,40 +1346,737 @@ function createBaseVisualization(srj, title = "RectDiff") {
1410
1346
  };
1411
1347
  }
1412
1348
 
1349
+ // lib/solvers/GapFillSolver/GapFillSolverPipeline.ts
1350
+ import {
1351
+ BasePipelineSolver as BasePipelineSolver2,
1352
+ definePipelineStep
1353
+ } from "@tscircuit/solver-utils";
1354
+
1355
+ // lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
1356
+ import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
1357
+ import Flatbush from "flatbush";
1358
+
1359
+ // lib/solvers/GapFillSolver/projectToUncoveredSegments.ts
1360
+ var EPS2 = 1e-4;
1361
+ function projectToUncoveredSegments(primaryEdge, overlappingEdges) {
1362
+ const isHorizontal = Math.abs(primaryEdge.start.y - primaryEdge.end.y) < EPS2;
1363
+ const isVertical = Math.abs(primaryEdge.start.x - primaryEdge.end.x) < EPS2;
1364
+ if (!isHorizontal && !isVertical) return [];
1365
+ const axis = isHorizontal ? "x" : "y";
1366
+ const perp = isHorizontal ? "y" : "x";
1367
+ const lineCoord = primaryEdge.start[perp];
1368
+ const p0 = primaryEdge.start[axis];
1369
+ const p1 = primaryEdge.end[axis];
1370
+ const pMin = Math.min(p0, p1);
1371
+ const pMax = Math.max(p0, p1);
1372
+ const clamp2 = (v) => Math.max(pMin, Math.min(pMax, v));
1373
+ const intervals = [];
1374
+ for (const e of overlappingEdges) {
1375
+ if (e === primaryEdge) continue;
1376
+ const eIsHorizontal = Math.abs(e.start.y - e.end.y) < EPS2;
1377
+ const eIsVertical = Math.abs(e.start.x - e.end.x) < EPS2;
1378
+ if (axis === "x" && !eIsHorizontal) continue;
1379
+ if (axis === "y" && !eIsVertical) continue;
1380
+ if (Math.abs(e.start[perp] - lineCoord) > EPS2) continue;
1381
+ const eMin = Math.min(e.start[axis], e.end[axis]);
1382
+ const eMax = Math.max(e.start[axis], e.end[axis]);
1383
+ const s = clamp2(eMin);
1384
+ const t = clamp2(eMax);
1385
+ if (t - s > EPS2) intervals.push({ s, e: t });
1386
+ }
1387
+ if (intervals.length === 0) {
1388
+ return [
1389
+ {
1390
+ ...primaryEdge,
1391
+ start: { ...primaryEdge.start },
1392
+ end: { ...primaryEdge.end }
1393
+ }
1394
+ ];
1395
+ }
1396
+ intervals.sort((a, b) => a.s - b.s);
1397
+ const merged = [];
1398
+ for (const it of intervals) {
1399
+ const last = merged[merged.length - 1];
1400
+ if (!last || it.s > last.e + EPS2) merged.push({ ...it });
1401
+ else last.e = Math.max(last.e, it.e);
1402
+ }
1403
+ const uncovered = [];
1404
+ let cursor = pMin;
1405
+ for (const m of merged) {
1406
+ if (m.s > cursor + EPS2) uncovered.push({ s: cursor, e: m.s });
1407
+ cursor = Math.max(cursor, m.e);
1408
+ if (cursor >= pMax - EPS2) break;
1409
+ }
1410
+ if (pMax > cursor + EPS2) uncovered.push({ s: cursor, e: pMax });
1411
+ if (uncovered.length === 0) return [];
1412
+ return uncovered.filter((u) => u.e - u.s > EPS2).map((u) => {
1413
+ const start = axis === "x" ? { x: u.s, y: lineCoord } : { x: lineCoord, y: u.s };
1414
+ const end = axis === "x" ? { x: u.e, y: lineCoord } : { x: lineCoord, y: u.e };
1415
+ return {
1416
+ parent: primaryEdge.parent,
1417
+ facingDirection: primaryEdge.facingDirection,
1418
+ start,
1419
+ end,
1420
+ z: primaryEdge.z
1421
+ };
1422
+ });
1423
+ }
1424
+
1425
+ // lib/solvers/GapFillSolver/edge-constants.ts
1426
+ var EDGES = [
1427
+ {
1428
+ facingDirection: "x-",
1429
+ dx: -1,
1430
+ dy: 0,
1431
+ startX: -0.5,
1432
+ startY: 0.5,
1433
+ endX: -0.5,
1434
+ endY: -0.5
1435
+ },
1436
+ {
1437
+ facingDirection: "x+",
1438
+ dx: 1,
1439
+ dy: 0,
1440
+ startX: 0.5,
1441
+ startY: 0.5,
1442
+ endX: 0.5,
1443
+ endY: -0.5
1444
+ },
1445
+ {
1446
+ facingDirection: "y-",
1447
+ dx: 0,
1448
+ dy: -1,
1449
+ startX: -0.5,
1450
+ startY: -0.5,
1451
+ endX: 0.5,
1452
+ endY: -0.5
1453
+ },
1454
+ {
1455
+ facingDirection: "y+",
1456
+ dx: 0,
1457
+ dy: 1,
1458
+ startX: 0.5,
1459
+ startY: 0.5,
1460
+ endX: -0.5,
1461
+ endY: 0.5
1462
+ }
1463
+ ];
1464
+ var EDGE_MAP = {
1465
+ "x-": EDGES.find((e) => e.facingDirection === "x-"),
1466
+ "x+": EDGES.find((e) => e.facingDirection === "x+"),
1467
+ "y-": EDGES.find((e) => e.facingDirection === "y-"),
1468
+ "y+": EDGES.find((e) => e.facingDirection === "y+")
1469
+ };
1470
+
1471
+ // lib/solvers/GapFillSolver/visuallyOffsetLine.ts
1472
+ var OFFSET_DIR_MAP = {
1473
+ "x-": {
1474
+ x: -1,
1475
+ y: 0
1476
+ },
1477
+ "x+": {
1478
+ x: 1,
1479
+ y: 0
1480
+ },
1481
+ "y-": {
1482
+ x: 0,
1483
+ y: -1
1484
+ },
1485
+ "y+": {
1486
+ x: 0,
1487
+ y: 1
1488
+ }
1489
+ };
1490
+ var visuallyOffsetLine = (line, dir, amt) => {
1491
+ const offset = OFFSET_DIR_MAP[dir];
1492
+ return line.map((p) => ({
1493
+ x: p.x + offset.x * amt,
1494
+ y: p.y + offset.y * amt
1495
+ }));
1496
+ };
1497
+
1498
+ // lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts
1499
+ import "@tscircuit/math-utils";
1500
+ var EPS3 = 1e-4;
1501
+ var FindSegmentsWithAdjacentEmptySpaceSolver = class extends BaseSolver2 {
1502
+ constructor(input) {
1503
+ super();
1504
+ this.input = input;
1505
+ for (const node of this.input.meshNodes) {
1506
+ for (const edge of EDGES) {
1507
+ let start = {
1508
+ x: node.center.x + node.width * edge.startX,
1509
+ y: node.center.y + node.height * edge.startY
1510
+ };
1511
+ let end = {
1512
+ x: node.center.x + node.width * edge.endX,
1513
+ y: node.center.y + node.height * edge.endY
1514
+ };
1515
+ if (start.x > end.x) {
1516
+ ;
1517
+ [start, end] = [end, start];
1518
+ }
1519
+ if (Math.abs(start.x - end.x) < EPS3 && start.y > end.y) {
1520
+ ;
1521
+ [start, end] = [end, start];
1522
+ }
1523
+ for (const z of node.availableZ) {
1524
+ this.unprocessedEdges.push({
1525
+ parent: node,
1526
+ start,
1527
+ end,
1528
+ facingDirection: edge.facingDirection,
1529
+ z
1530
+ });
1531
+ }
1532
+ }
1533
+ }
1534
+ this.allEdges = [...this.unprocessedEdges];
1535
+ this.edgeSpatialIndex = new Flatbush(this.allEdges.length);
1536
+ for (const edge of this.allEdges) {
1537
+ this.edgeSpatialIndex.add(
1538
+ edge.start.x,
1539
+ edge.start.y,
1540
+ edge.end.x,
1541
+ edge.end.y
1542
+ );
1543
+ }
1544
+ this.edgeSpatialIndex.finish();
1545
+ }
1546
+ allEdges;
1547
+ unprocessedEdges = [];
1548
+ segmentsWithAdjacentEmptySpace = [];
1549
+ edgeSpatialIndex;
1550
+ lastCandidateEdge = null;
1551
+ lastOverlappingEdges = null;
1552
+ lastUncoveredSegments = null;
1553
+ _step() {
1554
+ if (this.unprocessedEdges.length === 0) {
1555
+ this.solved = true;
1556
+ this.lastCandidateEdge = null;
1557
+ this.lastOverlappingEdges = null;
1558
+ this.lastUncoveredSegments = null;
1559
+ return;
1560
+ }
1561
+ const candidateEdge = this.unprocessedEdges.shift();
1562
+ this.lastCandidateEdge = candidateEdge;
1563
+ const nearbyEdges = this.edgeSpatialIndex.search(
1564
+ candidateEdge.start.x - EPS3,
1565
+ candidateEdge.start.y - EPS3,
1566
+ candidateEdge.end.x + EPS3,
1567
+ candidateEdge.end.y + EPS3
1568
+ );
1569
+ const overlappingEdges = nearbyEdges.map((i) => this.allEdges[i]).filter((e) => e.z === candidateEdge.z);
1570
+ this.lastOverlappingEdges = overlappingEdges;
1571
+ const uncoveredSegments = projectToUncoveredSegments(
1572
+ candidateEdge,
1573
+ overlappingEdges
1574
+ );
1575
+ this.lastUncoveredSegments = uncoveredSegments;
1576
+ this.segmentsWithAdjacentEmptySpace.push(...uncoveredSegments);
1577
+ }
1578
+ getOutput() {
1579
+ return {
1580
+ segmentsWithAdjacentEmptySpace: this.segmentsWithAdjacentEmptySpace
1581
+ };
1582
+ }
1583
+ visualize() {
1584
+ const graphics = {
1585
+ title: "FindSegmentsWithAdjacentEmptySpace",
1586
+ coordinateSystem: "cartesian",
1587
+ rects: [],
1588
+ points: [],
1589
+ lines: [],
1590
+ circles: [],
1591
+ arrows: [],
1592
+ texts: []
1593
+ };
1594
+ for (const node of this.input.meshNodes) {
1595
+ graphics.rects.push({
1596
+ center: node.center,
1597
+ width: node.width,
1598
+ height: node.height,
1599
+ stroke: "rgba(0, 0, 0, 0.1)"
1600
+ });
1601
+ }
1602
+ for (const unprocessedEdge of this.unprocessedEdges) {
1603
+ graphics.lines.push({
1604
+ points: visuallyOffsetLine(
1605
+ [unprocessedEdge.start, unprocessedEdge.end],
1606
+ unprocessedEdge.facingDirection,
1607
+ -0.1
1608
+ ),
1609
+ strokeColor: "rgba(0, 0, 255, 0.5)",
1610
+ strokeDash: "5 5"
1611
+ });
1612
+ }
1613
+ for (const edge of this.segmentsWithAdjacentEmptySpace) {
1614
+ graphics.lines.push({
1615
+ points: [edge.start, edge.end],
1616
+ strokeColor: "rgba(0,255,0, 0.5)"
1617
+ });
1618
+ }
1619
+ if (this.lastCandidateEdge) {
1620
+ graphics.lines.push({
1621
+ points: [this.lastCandidateEdge.start, this.lastCandidateEdge.end],
1622
+ strokeColor: "blue"
1623
+ });
1624
+ }
1625
+ if (this.lastOverlappingEdges) {
1626
+ for (const edge of this.lastOverlappingEdges) {
1627
+ graphics.lines.push({
1628
+ points: visuallyOffsetLine(
1629
+ [edge.start, edge.end],
1630
+ edge.facingDirection,
1631
+ 0.05
1632
+ ),
1633
+ strokeColor: "red",
1634
+ strokeDash: "2 2"
1635
+ });
1636
+ }
1637
+ }
1638
+ if (this.lastUncoveredSegments) {
1639
+ for (const edge of this.lastUncoveredSegments) {
1640
+ graphics.lines.push({
1641
+ points: visuallyOffsetLine(
1642
+ [edge.start, edge.end],
1643
+ edge.facingDirection,
1644
+ -0.05
1645
+ ),
1646
+ strokeColor: "green",
1647
+ strokeDash: "2 2"
1648
+ });
1649
+ }
1650
+ }
1651
+ return graphics;
1652
+ }
1653
+ };
1654
+
1655
+ // lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
1656
+ import { BaseSolver as BaseSolver3 } from "@tscircuit/solver-utils";
1657
+ import RBush from "rbush";
1658
+
1659
+ // lib/solvers/GapFillSolver/getBoundsFromCorners.ts
1660
+ var getBoundsFromCorners = (corners) => {
1661
+ return {
1662
+ minX: Math.min(...corners.map((c) => c.x)),
1663
+ minY: Math.min(...corners.map((c) => c.y)),
1664
+ maxX: Math.max(...corners.map((c) => c.x)),
1665
+ maxY: Math.max(...corners.map((c) => c.y))
1666
+ };
1667
+ };
1668
+
1669
+ // lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts
1670
+ import { segmentToBoxMinDistance } from "@tscircuit/math-utils";
1671
+ var EPS4 = 1e-4;
1672
+ var ExpandEdgesToEmptySpaceSolver = class extends BaseSolver3 {
1673
+ constructor(input) {
1674
+ super();
1675
+ this.input = input;
1676
+ this.unprocessedSegments = [...this.input.segmentsWithAdjacentEmptySpace];
1677
+ this.rectSpatialIndex = new RBush();
1678
+ this.rectSpatialIndex.load(
1679
+ this.input.inputMeshNodes.map((n) => ({
1680
+ ...n,
1681
+ minX: n.center.x - n.width / 2,
1682
+ minY: n.center.y - n.height / 2,
1683
+ maxX: n.center.x + n.width / 2,
1684
+ maxY: n.center.y + n.height / 2
1685
+ }))
1686
+ );
1687
+ }
1688
+ unprocessedSegments = [];
1689
+ expandedSegments = [];
1690
+ lastSegment = null;
1691
+ lastSearchBounds = null;
1692
+ lastCollidingNodes = null;
1693
+ lastSearchCorner1 = null;
1694
+ lastSearchCorner2 = null;
1695
+ lastExpandedSegment = null;
1696
+ rectSpatialIndex;
1697
+ _step() {
1698
+ if (this.unprocessedSegments.length === 0) {
1699
+ this.solved = true;
1700
+ return;
1701
+ }
1702
+ const segment = this.unprocessedSegments.shift();
1703
+ this.lastSegment = segment;
1704
+ const { dx, dy } = EDGE_MAP[segment.facingDirection];
1705
+ const deltaStartEnd = {
1706
+ x: segment.end.x - segment.start.x,
1707
+ y: segment.end.y - segment.start.y
1708
+ };
1709
+ const segLength = Math.sqrt(deltaStartEnd.x ** 2 + deltaStartEnd.y ** 2);
1710
+ const normDeltaStartEnd = {
1711
+ x: deltaStartEnd.x / segLength,
1712
+ y: deltaStartEnd.y / segLength
1713
+ };
1714
+ let collidingNodes = null;
1715
+ let searchDistance = 1;
1716
+ const searchCorner1 = {
1717
+ x: segment.start.x + dx * EPS4 + normDeltaStartEnd.x * EPS4 * 10,
1718
+ y: segment.start.y + dy * EPS4 + normDeltaStartEnd.y * EPS4 * 10
1719
+ };
1720
+ const searchCorner2 = {
1721
+ x: segment.end.x + dx * EPS4 - normDeltaStartEnd.x * EPS4 * 10,
1722
+ y: segment.end.y + dy * EPS4 - normDeltaStartEnd.y * EPS4 * 10
1723
+ };
1724
+ this.lastSearchCorner1 = searchCorner1;
1725
+ this.lastSearchCorner2 = searchCorner2;
1726
+ while ((!collidingNodes || collidingNodes.length === 0) && searchDistance < 1e3) {
1727
+ const searchBounds = getBoundsFromCorners([
1728
+ searchCorner1,
1729
+ searchCorner2,
1730
+ {
1731
+ x: searchCorner1.x + dx * searchDistance,
1732
+ y: searchCorner1.y + dy * searchDistance
1733
+ },
1734
+ {
1735
+ x: searchCorner2.x + dx * searchDistance,
1736
+ y: searchCorner2.y + dy * searchDistance
1737
+ }
1738
+ ]);
1739
+ this.lastSearchBounds = searchBounds;
1740
+ collidingNodes = this.rectSpatialIndex.search(searchBounds).filter((n) => n.availableZ.includes(segment.z)).filter(
1741
+ (n) => n.capacityMeshNodeId !== segment.parent.capacityMeshNodeId
1742
+ );
1743
+ searchDistance *= 4;
1744
+ }
1745
+ if (!collidingNodes || collidingNodes.length === 0) {
1746
+ return;
1747
+ }
1748
+ this.lastCollidingNodes = collidingNodes;
1749
+ let smallestDistance = Infinity;
1750
+ for (const node of collidingNodes) {
1751
+ const distance = segmentToBoxMinDistance(segment.start, segment.end, node);
1752
+ if (distance < smallestDistance) {
1753
+ smallestDistance = distance;
1754
+ }
1755
+ }
1756
+ const expandDistance = smallestDistance;
1757
+ const nodeBounds = getBoundsFromCorners([
1758
+ segment.start,
1759
+ segment.end,
1760
+ {
1761
+ x: segment.start.x + dx * expandDistance,
1762
+ y: segment.start.y + dy * expandDistance
1763
+ },
1764
+ {
1765
+ x: segment.end.x + dx * expandDistance,
1766
+ y: segment.end.y + dy * expandDistance
1767
+ }
1768
+ ]);
1769
+ const nodeCenter = {
1770
+ x: (nodeBounds.minX + nodeBounds.maxX) / 2,
1771
+ y: (nodeBounds.minY + nodeBounds.maxY) / 2
1772
+ };
1773
+ const nodeWidth = nodeBounds.maxX - nodeBounds.minX;
1774
+ const nodeHeight = nodeBounds.maxY - nodeBounds.minY;
1775
+ const expandedSegment = {
1776
+ segment,
1777
+ newNode: {
1778
+ capacityMeshNodeId: `new-${segment.parent.capacityMeshNodeId}-${this.expandedSegments.length}`,
1779
+ center: nodeCenter,
1780
+ width: nodeWidth,
1781
+ height: nodeHeight,
1782
+ availableZ: [segment.z],
1783
+ layer: segment.parent.layer
1784
+ }
1785
+ };
1786
+ this.lastExpandedSegment = expandedSegment;
1787
+ if (nodeWidth < EPS4 || nodeHeight < EPS4) {
1788
+ return;
1789
+ }
1790
+ this.expandedSegments.push(expandedSegment);
1791
+ this.rectSpatialIndex.insert({
1792
+ ...expandedSegment.newNode,
1793
+ ...nodeBounds
1794
+ });
1795
+ }
1796
+ getOutput() {
1797
+ return {
1798
+ expandedSegments: this.expandedSegments
1799
+ };
1800
+ }
1801
+ visualize() {
1802
+ const graphics = {
1803
+ title: "ExpandEdgesToEmptySpace",
1804
+ coordinateSystem: "cartesian",
1805
+ rects: [],
1806
+ points: [],
1807
+ lines: [],
1808
+ circles: [],
1809
+ arrows: [],
1810
+ texts: []
1811
+ };
1812
+ for (const node of this.input.inputMeshNodes) {
1813
+ graphics.rects.push({
1814
+ center: node.center,
1815
+ width: node.width,
1816
+ height: node.height,
1817
+ stroke: "rgba(0, 0, 0, 0.1)",
1818
+ layer: `z${node.availableZ.join(",")}`,
1819
+ label: [
1820
+ `node ${node.capacityMeshNodeId}`,
1821
+ `z:${node.availableZ.join(",")}`
1822
+ ].join("\n")
1823
+ });
1824
+ }
1825
+ for (const { newNode } of this.expandedSegments) {
1826
+ graphics.rects.push({
1827
+ center: newNode.center,
1828
+ width: newNode.width,
1829
+ height: newNode.height,
1830
+ fill: "green",
1831
+ label: `expandedSegment (z=${newNode.availableZ.join(",")})`,
1832
+ layer: `z${newNode.availableZ.join(",")}`
1833
+ });
1834
+ }
1835
+ if (this.lastSegment) {
1836
+ graphics.lines.push({
1837
+ points: [this.lastSegment.start, this.lastSegment.end],
1838
+ strokeColor: "rgba(0, 0, 255, 0.5)"
1839
+ });
1840
+ }
1841
+ if (this.lastSearchBounds) {
1842
+ graphics.rects.push({
1843
+ center: {
1844
+ x: (this.lastSearchBounds.minX + this.lastSearchBounds.maxX) / 2,
1845
+ y: (this.lastSearchBounds.minY + this.lastSearchBounds.maxY) / 2
1846
+ },
1847
+ width: this.lastSearchBounds.maxX - this.lastSearchBounds.minX,
1848
+ height: this.lastSearchBounds.maxY - this.lastSearchBounds.minY,
1849
+ fill: "rgba(0, 0, 255, 0.25)"
1850
+ });
1851
+ }
1852
+ if (this.lastSearchCorner1 && this.lastSearchCorner2) {
1853
+ graphics.points.push({
1854
+ x: this.lastSearchCorner1.x,
1855
+ y: this.lastSearchCorner1.y,
1856
+ color: "rgba(0, 0, 255, 0.5)",
1857
+ label: ["searchCorner1", `z=${this.lastSegment?.z}`].join("\n")
1858
+ });
1859
+ graphics.points.push({
1860
+ x: this.lastSearchCorner2.x,
1861
+ y: this.lastSearchCorner2.y,
1862
+ color: "rgba(0, 0, 255, 0.5)",
1863
+ label: ["searchCorner2", `z=${this.lastSegment?.z}`].join("\n")
1864
+ });
1865
+ }
1866
+ if (this.lastExpandedSegment) {
1867
+ graphics.rects.push({
1868
+ center: this.lastExpandedSegment.newNode.center,
1869
+ width: this.lastExpandedSegment.newNode.width,
1870
+ height: this.lastExpandedSegment.newNode.height,
1871
+ fill: "purple",
1872
+ label: `expandedSegment (z=${this.lastExpandedSegment.segment.z})`
1873
+ });
1874
+ }
1875
+ if (this.lastCollidingNodes) {
1876
+ for (const node of this.lastCollidingNodes) {
1877
+ graphics.rects.push({
1878
+ center: node.center,
1879
+ width: node.width,
1880
+ height: node.height,
1881
+ fill: "rgba(255, 0, 0, 0.5)"
1882
+ });
1883
+ }
1884
+ }
1885
+ return graphics;
1886
+ }
1887
+ };
1888
+
1889
+ // lib/solvers/GapFillSolver/GapFillSolverPipeline.ts
1890
+ var GapFillSolverPipeline = class extends BasePipelineSolver2 {
1891
+ findSegmentsWithAdjacentEmptySpaceSolver;
1892
+ expandEdgesToEmptySpaceSolver;
1893
+ pipelineDef = [
1894
+ definePipelineStep(
1895
+ "findSegmentsWithAdjacentEmptySpaceSolver",
1896
+ FindSegmentsWithAdjacentEmptySpaceSolver,
1897
+ (gapFillPipeline) => [
1898
+ {
1899
+ meshNodes: gapFillPipeline.inputProblem.meshNodes
1900
+ }
1901
+ ],
1902
+ {
1903
+ onSolved: () => {
1904
+ }
1905
+ }
1906
+ ),
1907
+ definePipelineStep(
1908
+ "expandEdgesToEmptySpace",
1909
+ ExpandEdgesToEmptySpaceSolver,
1910
+ (gapFillPipeline) => [
1911
+ {
1912
+ inputMeshNodes: gapFillPipeline.inputProblem.meshNodes,
1913
+ segmentsWithAdjacentEmptySpace: gapFillPipeline.findSegmentsWithAdjacentEmptySpaceSolver.getOutput().segmentsWithAdjacentEmptySpace
1914
+ }
1915
+ ],
1916
+ {
1917
+ onSolved: () => {
1918
+ }
1919
+ }
1920
+ )
1921
+ ];
1922
+ getOutput() {
1923
+ const expandedSegments = this.expandEdgesToEmptySpaceSolver?.getOutput().expandedSegments ?? [];
1924
+ const expandedNodes = expandedSegments.map((es) => es.newNode);
1925
+ return {
1926
+ outputNodes: [...this.inputProblem.meshNodes, ...expandedNodes]
1927
+ };
1928
+ }
1929
+ initialVisualize() {
1930
+ const graphics = {
1931
+ title: "GapFillSolverPipeline - Initial",
1932
+ coordinateSystem: "cartesian",
1933
+ rects: [],
1934
+ points: [],
1935
+ lines: [],
1936
+ circles: [],
1937
+ arrows: [],
1938
+ texts: []
1939
+ };
1940
+ for (const node of this.inputProblem.meshNodes) {
1941
+ graphics.rects.push({
1942
+ center: node.center,
1943
+ width: node.width,
1944
+ height: node.height,
1945
+ stroke: "rgba(0, 0, 0, 0.3)",
1946
+ fill: "rgba(100, 100, 100, 0.1)",
1947
+ layer: `z${node.availableZ.join(",")}`,
1948
+ label: [
1949
+ `node ${node.capacityMeshNodeId}`,
1950
+ `z:${node.availableZ.join(",")}`
1951
+ ].join("\n")
1952
+ });
1953
+ }
1954
+ return graphics;
1955
+ }
1956
+ finalVisualize() {
1957
+ const graphics = {
1958
+ title: "GapFillSolverPipeline - Final",
1959
+ coordinateSystem: "cartesian",
1960
+ rects: [],
1961
+ points: [],
1962
+ lines: [],
1963
+ circles: [],
1964
+ arrows: [],
1965
+ texts: []
1966
+ };
1967
+ const { outputNodes } = this.getOutput();
1968
+ const expandedSegments = this.expandEdgesToEmptySpaceSolver?.getOutput().expandedSegments ?? [];
1969
+ const expandedNodeIds = new Set(
1970
+ expandedSegments.map((es) => es.newNode.capacityMeshNodeId)
1971
+ );
1972
+ for (const node of outputNodes) {
1973
+ const isExpanded = expandedNodeIds.has(node.capacityMeshNodeId);
1974
+ graphics.rects.push({
1975
+ center: node.center,
1976
+ width: node.width,
1977
+ height: node.height,
1978
+ stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
1979
+ fill: isExpanded ? "rgba(0, 200, 0, 0.3)" : "rgba(100, 100, 100, 0.1)",
1980
+ layer: `z${node.availableZ.join(",")}`,
1981
+ label: [
1982
+ `${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
1983
+ `z:${node.availableZ.join(",")}`
1984
+ ].join("\n")
1985
+ });
1986
+ }
1987
+ return graphics;
1988
+ }
1989
+ };
1990
+
1413
1991
  // lib/RectDiffPipeline.ts
1414
- var RectDiffPipeline = class extends BasePipelineSolver2 {
1992
+ var RectDiffPipeline = class extends BasePipelineSolver3 {
1415
1993
  rectDiffSolver;
1994
+ gapFillSolver;
1416
1995
  pipelineDef = [
1417
- definePipelineStep(
1996
+ definePipelineStep2(
1418
1997
  "rectDiffSolver",
1419
1998
  RectDiffSolver,
1420
- (instance) => [
1999
+ (rectDiffPipeline) => [
1421
2000
  {
1422
- simpleRouteJson: instance.inputProblem.simpleRouteJson,
1423
- gridOptions: instance.inputProblem.gridOptions
2001
+ simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
2002
+ gridOptions: rectDiffPipeline.inputProblem.gridOptions
1424
2003
  }
1425
2004
  ],
1426
2005
  {
1427
2006
  onSolved: () => {
1428
2007
  }
1429
2008
  }
2009
+ ),
2010
+ definePipelineStep2(
2011
+ "gapFillSolver",
2012
+ GapFillSolverPipeline,
2013
+ (rectDiffPipeline) => [
2014
+ {
2015
+ meshNodes: rectDiffPipeline.rectDiffSolver?.getOutput().meshNodes ?? []
2016
+ }
2017
+ ]
1430
2018
  )
1431
2019
  ];
1432
2020
  getConstructorParams() {
1433
2021
  return [this.inputProblem];
1434
2022
  }
1435
2023
  getOutput() {
1436
- return this.getSolver("rectDiffSolver").getOutput();
2024
+ const gapFillOutput = this.gapFillSolver?.getOutput();
2025
+ if (gapFillOutput) {
2026
+ return { meshNodes: gapFillOutput.outputNodes };
2027
+ }
2028
+ return this.rectDiffSolver.getOutput();
1437
2029
  }
1438
- visualize() {
1439
- const solver = this.getSolver("rectDiffSolver");
1440
- if (solver) {
1441
- return solver.visualize();
2030
+ initialVisualize() {
2031
+ console.log("RectDiffPipeline - initialVisualize");
2032
+ const graphics = createBaseVisualization(
2033
+ this.inputProblem.simpleRouteJson,
2034
+ "RectDiffPipeline - Initial"
2035
+ );
2036
+ const initialNodes = this.rectDiffSolver?.getOutput().meshNodes ?? [];
2037
+ for (const node of initialNodes) {
2038
+ graphics.rects.push({
2039
+ center: node.center,
2040
+ width: node.width,
2041
+ height: node.height,
2042
+ stroke: "rgba(0, 0, 0, 0.3)",
2043
+ fill: "rgba(100, 100, 100, 0.1)",
2044
+ layer: `z${node.availableZ.join(",")}`,
2045
+ label: [
2046
+ `node ${node.capacityMeshNodeId}`,
2047
+ `z:${node.availableZ.join(",")}`
2048
+ ].join("\n")
2049
+ });
1442
2050
  }
1443
- return createBaseVisualization(
2051
+ return graphics;
2052
+ }
2053
+ finalVisualize() {
2054
+ const graphics = createBaseVisualization(
1444
2055
  this.inputProblem.simpleRouteJson,
1445
- "RectDiff Pipeline (not started)"
2056
+ "RectDiffPipeline - Final"
1446
2057
  );
2058
+ const { meshNodes: outputNodes } = this.getOutput();
2059
+ const initialNodeIds = new Set(
2060
+ (this.rectDiffSolver?.getOutput().meshNodes ?? []).map(
2061
+ (n) => n.capacityMeshNodeId
2062
+ )
2063
+ );
2064
+ for (const node of outputNodes) {
2065
+ const isExpanded = !initialNodeIds.has(node.capacityMeshNodeId);
2066
+ graphics.rects.push({
2067
+ center: node.center,
2068
+ width: node.width,
2069
+ height: node.height,
2070
+ stroke: isExpanded ? "rgba(0, 128, 0, 0.8)" : "rgba(0, 0, 0, 0.3)",
2071
+ fill: isExpanded ? "rgba(0, 200, 0, 0.3)" : "rgba(100, 100, 100, 0.1)",
2072
+ layer: `z${node.availableZ.join(",")}`,
2073
+ label: [
2074
+ `${isExpanded ? "[expanded] " : ""}node ${node.capacityMeshNodeId}`,
2075
+ `z:${node.availableZ.join(",")}`
2076
+ ].join("\n")
2077
+ });
2078
+ }
2079
+ return graphics;
1447
2080
  }
1448
2081
  };
1449
2082
  export {