@tscircuit/curvy-trace-solver 0.0.6 → 0.0.8

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.
package/dist/index.d.ts CHANGED
@@ -45,9 +45,13 @@ declare class CurvyTraceSolver extends BaseSolver {
45
45
  private initializeTraces;
46
46
  private updateSampledTraces;
47
47
  private updateSingleTraceSample;
48
+ private updateControlPointsFromDistances;
48
49
  private updateCollisionPairs;
49
50
  private computeTotalCost;
50
51
  private computeCostForTrace;
52
+ private tracesIntersect;
53
+ private findIntersectingPairs;
54
+ private resolveIntersections;
51
55
  private optimizeStep;
52
56
  private buildOutputTraces;
53
57
  _step(): void;
package/dist/index.js CHANGED
@@ -932,16 +932,27 @@ function getPerimeterT(p, bounds) {
932
932
  if (Math.abs(p.x - minX) < eps) return 2 * W + H + (p.y - minY);
933
933
  return 0;
934
934
  }
935
- function getPerimeterPoint(t, bounds) {
935
+ function getInwardPerpendicular(p, bounds) {
936
936
  const { minX, maxX, minY, maxY } = bounds;
937
- const W = maxX - minX;
938
- const H = maxY - minY;
939
- const perimeter = 2 * W + 2 * H;
940
- t = (t % perimeter + perimeter) % perimeter;
941
- if (t <= W) return { x: minX + t, y: maxY };
942
- if (t <= W + H) return { x: maxX, y: maxY - (t - W) };
943
- if (t <= 2 * W + H) return { x: maxX - (t - W - H), y: minY };
944
- return { x: minX, y: minY + (t - 2 * W - H) };
937
+ const eps = 1e-6;
938
+ const onTop = Math.abs(p.y - maxY) < eps;
939
+ const onBottom = Math.abs(p.y - minY) < eps;
940
+ const onRight = Math.abs(p.x - maxX) < eps;
941
+ const onLeft = Math.abs(p.x - minX) < eps;
942
+ if (onTop && onRight) return { x: -Math.SQRT1_2, y: -Math.SQRT1_2 };
943
+ if (onTop && onLeft) return { x: Math.SQRT1_2, y: -Math.SQRT1_2 };
944
+ if (onBottom && onRight) return { x: -Math.SQRT1_2, y: Math.SQRT1_2 };
945
+ if (onBottom && onLeft) return { x: Math.SQRT1_2, y: Math.SQRT1_2 };
946
+ if (onTop) return { x: 0, y: -1 };
947
+ if (onBottom) return { x: 0, y: 1 };
948
+ if (onRight) return { x: -1, y: 0 };
949
+ if (onLeft) return { x: 1, y: 0 };
950
+ const cx = (minX + maxX) / 2;
951
+ const cy = (minY + maxY) / 2;
952
+ const dx = cx - p.x;
953
+ const dy = cy - p.y;
954
+ const len = Math.hypot(dx, dy);
955
+ return len > 0 ? { x: dx / len, y: dy / len } : { x: 0, y: -1 };
945
956
  }
946
957
  function sampleCubicBezierInline(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, points, numSamples) {
947
958
  for (let i = 0; i <= numSamples; i++) {
@@ -1003,6 +1014,13 @@ function segmentDistSq(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y) {
1003
1014
  ptSegDistSq(b2x, b2y, a1x, a1y, a2x, a2y)
1004
1015
  );
1005
1016
  }
1017
+ function segmentsIntersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y) {
1018
+ const d1 = (b2x - b1x) * (a1y - b1y) - (b2y - b1y) * (a1x - b1x);
1019
+ const d2 = (b2x - b1x) * (a2y - b1y) - (b2y - b1y) * (a2x - b1x);
1020
+ const d3 = (a2x - a1x) * (b1y - a1y) - (a2y - a1y) * (b1x - a1x);
1021
+ const d4 = (a2x - a1x) * (b2y - a1y) - (a2y - a1y) * (b2x - a1x);
1022
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
1023
+ }
1006
1024
  function chordContains(aT1, aT2, bT1, bT2, perimeter) {
1007
1025
  const normalize = (t) => (t % perimeter + perimeter) % perimeter;
1008
1026
  const a1 = normalize(aT1), a2 = normalize(aT2);
@@ -1091,33 +1109,32 @@ var CurvyTraceSolver = class extends BaseSolver {
1091
1109
  t2: getPerimeterT(pair.end, bounds),
1092
1110
  idx
1093
1111
  }));
1094
- const nestingDepth = /* @__PURE__ */ new Map();
1112
+ const containedBy = tracesWithT.map(() => []);
1113
+ const contains = tracesWithT.map(() => []);
1095
1114
  for (const trace of tracesWithT) {
1096
- let depth = 0;
1097
1115
  for (const other of tracesWithT) {
1098
- if (trace.idx !== other.idx && chordContains(other.t1, other.t2, trace.t1, trace.t2, perimeter)) {
1099
- depth++;
1116
+ if (trace.idx !== other.idx) {
1117
+ if (trace.pair.networkId && other.pair.networkId && trace.pair.networkId === other.pair.networkId) {
1118
+ continue;
1119
+ }
1120
+ if (chordContains(other.t1, other.t2, trace.t1, trace.t2, perimeter)) {
1121
+ containedBy[trace.idx].push(other.idx);
1122
+ contains[other.idx].push(trace.idx);
1123
+ }
1100
1124
  }
1101
1125
  }
1102
- nestingDepth.set(trace.idx, depth);
1103
1126
  }
1104
- const maxDepth = Math.max(...Array.from(nestingDepth.values()), 1);
1127
+ const nestingDepth = containedBy.map((arr) => arr.length);
1128
+ const maxDepth = Math.max(...nestingDepth, 1);
1105
1129
  this.traces = tracesWithT.map(({ pair, t1, t2, idx }) => {
1106
- let dt = t2 - t1;
1107
- if (dt > perimeter / 2) dt -= perimeter;
1108
- if (dt < -perimeter / 2) dt += perimeter;
1109
- const tCtrl1 = t1 + dt * 0.33;
1110
- const tCtrl2 = t1 + dt * 0.67;
1111
- const pPerim1 = getPerimeterPoint(tCtrl1, bounds);
1112
- const pPerim2 = getPerimeterPoint(tCtrl2, bounds);
1113
- const pLinear1 = {
1114
- x: pair.start.x + (pair.end.x - pair.start.x) * 0.33,
1115
- y: pair.start.y + (pair.end.y - pair.start.y) * 0.33
1116
- };
1117
- const pLinear2 = {
1118
- x: pair.start.x + (pair.end.x - pair.start.x) * 0.67,
1119
- y: pair.start.y + (pair.end.y - pair.start.y) * 0.67
1120
- };
1130
+ const perpDir1 = getInwardPerpendicular(pair.start, bounds);
1131
+ const perpDir2 = getInwardPerpendicular(pair.end, bounds);
1132
+ const chordLength = Math.hypot(
1133
+ pair.end.x - pair.start.x,
1134
+ pair.end.y - pair.start.y
1135
+ );
1136
+ const depth = nestingDepth[idx];
1137
+ const normalizedDepth = depth / maxDepth;
1121
1138
  const midPoint = {
1122
1139
  x: (pair.start.x + pair.end.x) / 2,
1123
1140
  y: (pair.start.y + pair.end.y) / 2
@@ -1128,23 +1145,33 @@ var CurvyTraceSolver = class extends BaseSolver {
1128
1145
  );
1129
1146
  const maxDist = Math.hypot(W / 2, H / 2);
1130
1147
  const spatialDepth = 1 - distToCenter / maxDist;
1131
- const depth = nestingDepth.get(idx) || 0;
1132
- const normalizedDepth = depth / maxDepth;
1133
- const basePull = 0.3 + spatialDepth * 0.4;
1134
- const pullAmount = Math.max(0.05, basePull - normalizedDepth * 0.2);
1148
+ const baseFactor = 0.25 + spatialDepth * 0.15;
1149
+ const depthAdjustment = 1 - normalizedDepth * 0.3;
1150
+ const initialDist = chordLength * baseFactor * depthAdjustment;
1151
+ const minDist = Math.min(W, H) * 0.05;
1152
+ const d1 = Math.max(minDist, initialDist);
1153
+ const d2 = Math.max(minDist, initialDist);
1154
+ const ctrl1 = {
1155
+ x: pair.start.x + d1 * perpDir1.x,
1156
+ y: pair.start.y + d1 * perpDir1.y
1157
+ };
1158
+ const ctrl2 = {
1159
+ x: pair.end.x + d2 * perpDir2.x,
1160
+ y: pair.end.y + d2 * perpDir2.y
1161
+ };
1135
1162
  return {
1136
1163
  waypointPair: pair,
1137
- ctrl1: {
1138
- x: pLinear1.x * (1 - pullAmount) + pPerim1.x * pullAmount,
1139
- y: pLinear1.y * (1 - pullAmount) + pPerim1.y * pullAmount
1140
- },
1141
- ctrl2: {
1142
- x: pLinear2.x * (1 - pullAmount) + pPerim2.x * pullAmount,
1143
- y: pLinear2.y * (1 - pullAmount) + pPerim2.y * pullAmount
1144
- },
1164
+ ctrl1,
1165
+ ctrl2,
1145
1166
  networkId: pair.networkId,
1146
1167
  t1,
1147
- t2
1168
+ t2,
1169
+ perpDir1,
1170
+ perpDir2,
1171
+ d1,
1172
+ d2,
1173
+ containedBy: containedBy[idx],
1174
+ contains: contains[idx]
1148
1175
  };
1149
1176
  });
1150
1177
  this.sampledPoints = this.traces.map(
@@ -1199,6 +1226,14 @@ var CurvyTraceSolver = class extends BaseSolver {
1199
1226
  OPT_SAMPLES + 1
1200
1227
  );
1201
1228
  }
1229
+ // Update control points from perpendicular distances
1230
+ updateControlPointsFromDistances(i) {
1231
+ const trace = this.traces[i];
1232
+ trace.ctrl1.x = trace.waypointPair.start.x + trace.d1 * trace.perpDir1.x;
1233
+ trace.ctrl1.y = trace.waypointPair.start.y + trace.d1 * trace.perpDir1.y;
1234
+ trace.ctrl2.x = trace.waypointPair.end.x + trace.d2 * trace.perpDir2.x;
1235
+ trace.ctrl2.y = trace.waypointPair.end.y + trace.d2 * trace.perpDir2.y;
1236
+ }
1202
1237
  // Determine which trace pairs could possibly collide based on bounding boxes
1203
1238
  updateCollisionPairs() {
1204
1239
  const { preferredTraceToTraceSpacing: preferredSpacing } = this.problem;
@@ -1324,12 +1359,213 @@ var CurvyTraceSolver = class extends BaseSolver {
1324
1359
  }
1325
1360
  return cost;
1326
1361
  }
1362
+ // Check if two traces intersect or are very close (potential intersection)
1363
+ // Uses higher resolution sampling for accuracy
1364
+ tracesIntersect(i, j) {
1365
+ const trace1 = this.traces[i];
1366
+ const trace2 = this.traces[j];
1367
+ const INTERSECT_SAMPLES = 15;
1368
+ const p1 = new Float64Array((INTERSECT_SAMPLES + 1) * 2);
1369
+ const p2 = new Float64Array((INTERSECT_SAMPLES + 1) * 2);
1370
+ sampleCubicBezierInline(
1371
+ trace1.waypointPair.start.x,
1372
+ trace1.waypointPair.start.y,
1373
+ trace1.ctrl1.x,
1374
+ trace1.ctrl1.y,
1375
+ trace1.ctrl2.x,
1376
+ trace1.ctrl2.y,
1377
+ trace1.waypointPair.end.x,
1378
+ trace1.waypointPair.end.y,
1379
+ p1,
1380
+ INTERSECT_SAMPLES
1381
+ );
1382
+ sampleCubicBezierInline(
1383
+ trace2.waypointPair.start.x,
1384
+ trace2.waypointPair.start.y,
1385
+ trace2.ctrl1.x,
1386
+ trace2.ctrl1.y,
1387
+ trace2.ctrl2.x,
1388
+ trace2.ctrl2.y,
1389
+ trace2.waypointPair.end.x,
1390
+ trace2.waypointPair.end.y,
1391
+ p2,
1392
+ INTERSECT_SAMPLES
1393
+ );
1394
+ for (let a = 0; a < INTERSECT_SAMPLES; a++) {
1395
+ const a1x = p1[a * 2];
1396
+ const a1y = p1[a * 2 + 1];
1397
+ const a2x = p1[(a + 1) * 2];
1398
+ const a2y = p1[(a + 1) * 2 + 1];
1399
+ for (let b = 0; b < INTERSECT_SAMPLES; b++) {
1400
+ const b1x = p2[b * 2];
1401
+ const b1y = p2[b * 2 + 1];
1402
+ const b2x = p2[(b + 1) * 2];
1403
+ const b2y = p2[(b + 1) * 2 + 1];
1404
+ if (segmentsIntersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y)) {
1405
+ return true;
1406
+ }
1407
+ }
1408
+ }
1409
+ return false;
1410
+ }
1411
+ // Find all pairs of traces that currently intersect
1412
+ findIntersectingPairs() {
1413
+ const intersecting = [];
1414
+ for (let i = 0; i < this.traces.length; i++) {
1415
+ for (let j = i + 1; j < this.traces.length; j++) {
1416
+ const ti = this.traces[i];
1417
+ const tj = this.traces[j];
1418
+ if (ti.networkId && tj.networkId && ti.networkId === tj.networkId) {
1419
+ continue;
1420
+ }
1421
+ const bi = this.traceBounds[i];
1422
+ const bj = this.traceBounds[j];
1423
+ if (bi.maxX < bj.minX || bj.maxX < bi.minX || bi.maxY < bj.minY || bj.maxY < bi.minY) {
1424
+ continue;
1425
+ }
1426
+ if (this.tracesIntersect(i, j)) {
1427
+ intersecting.push([i, j]);
1428
+ }
1429
+ }
1430
+ }
1431
+ return intersecting;
1432
+ }
1433
+ // Resolve intersections by separating traces in depth
1434
+ // Only keeps changes if they improve overall cost
1435
+ resolveIntersections() {
1436
+ const { bounds, preferredTraceToTraceSpacing } = this.problem;
1437
+ const { minX, maxX, minY, maxY } = bounds;
1438
+ const minDim = Math.min(maxX - minX, maxY - minY);
1439
+ const minDist = minDim * 0.02;
1440
+ const maxDist = minDim * 1.5;
1441
+ const intersecting = this.findIntersectingPairs();
1442
+ if (intersecting.length === 0) return 0;
1443
+ let resolved = 0;
1444
+ for (const [i, j] of intersecting) {
1445
+ const ti = this.traces[i];
1446
+ const tj = this.traces[j];
1447
+ if (!this.tracesIntersect(i, j)) continue;
1448
+ let outerIdx;
1449
+ let innerIdx;
1450
+ if (ti.containedBy.includes(j)) {
1451
+ outerIdx = j;
1452
+ innerIdx = i;
1453
+ } else if (tj.containedBy.includes(i)) {
1454
+ outerIdx = i;
1455
+ innerIdx = j;
1456
+ } else {
1457
+ const avgDi = (ti.d1 + ti.d2) / 2;
1458
+ const avgDj = (tj.d1 + tj.d2) / 2;
1459
+ if (avgDi < avgDj) {
1460
+ outerIdx = i;
1461
+ innerIdx = j;
1462
+ } else {
1463
+ outerIdx = j;
1464
+ innerIdx = i;
1465
+ }
1466
+ }
1467
+ const outerTrace = this.traces[outerIdx];
1468
+ const innerTrace = this.traces[innerIdx];
1469
+ const origOuterD1 = outerTrace.d1;
1470
+ const origOuterD2 = outerTrace.d2;
1471
+ const origInnerD1 = innerTrace.d1;
1472
+ const origInnerD2 = innerTrace.d2;
1473
+ const costBeforeThis = this.computeTotalCost();
1474
+ const separation = preferredTraceToTraceSpacing * 2;
1475
+ const strategies = [
1476
+ // Strategy 1: Small increase for inner
1477
+ { innerMult: 1, outerMult: 0 },
1478
+ // Strategy 2: Small decrease for outer
1479
+ { innerMult: 0, outerMult: 1 },
1480
+ // Strategy 3: Both directions
1481
+ { innerMult: 0.5, outerMult: 0.5 },
1482
+ // Strategy 4: Larger increase for inner
1483
+ { innerMult: 2, outerMult: 0 },
1484
+ // Strategy 5: Both larger
1485
+ { innerMult: 1, outerMult: 1 },
1486
+ // More aggressive
1487
+ { innerMult: 3, outerMult: 0 },
1488
+ { innerMult: 2, outerMult: 1 },
1489
+ { innerMult: 4, outerMult: 0 }
1490
+ ];
1491
+ let bestCost = costBeforeThis;
1492
+ let bestStrategy = null;
1493
+ for (const strategy of strategies) {
1494
+ outerTrace.d1 = origOuterD1;
1495
+ outerTrace.d2 = origOuterD2;
1496
+ innerTrace.d1 = origInnerD1;
1497
+ innerTrace.d2 = origInnerD2;
1498
+ innerTrace.d1 = Math.min(
1499
+ maxDist,
1500
+ origInnerD1 + separation * strategy.innerMult
1501
+ );
1502
+ innerTrace.d2 = Math.min(
1503
+ maxDist,
1504
+ origInnerD2 + separation * strategy.innerMult
1505
+ );
1506
+ outerTrace.d1 = Math.max(
1507
+ minDist,
1508
+ origOuterD1 - separation * strategy.outerMult
1509
+ );
1510
+ outerTrace.d2 = Math.max(
1511
+ minDist,
1512
+ origOuterD2 - separation * strategy.outerMult
1513
+ );
1514
+ this.updateControlPointsFromDistances(innerIdx);
1515
+ this.updateControlPointsFromDistances(outerIdx);
1516
+ this.updateSingleTraceSample(innerIdx);
1517
+ this.updateSingleTraceSample(outerIdx);
1518
+ if (!this.tracesIntersect(outerIdx, innerIdx)) {
1519
+ const newCost = this.computeTotalCost();
1520
+ if (bestStrategy === null || newCost < bestCost) {
1521
+ bestCost = newCost;
1522
+ bestStrategy = strategy;
1523
+ }
1524
+ }
1525
+ }
1526
+ if (bestStrategy) {
1527
+ innerTrace.d1 = Math.min(
1528
+ maxDist,
1529
+ origInnerD1 + separation * bestStrategy.innerMult
1530
+ );
1531
+ innerTrace.d2 = Math.min(
1532
+ maxDist,
1533
+ origInnerD2 + separation * bestStrategy.innerMult
1534
+ );
1535
+ outerTrace.d1 = Math.max(
1536
+ minDist,
1537
+ origOuterD1 - separation * bestStrategy.outerMult
1538
+ );
1539
+ outerTrace.d2 = Math.max(
1540
+ minDist,
1541
+ origOuterD2 - separation * bestStrategy.outerMult
1542
+ );
1543
+ this.updateControlPointsFromDistances(innerIdx);
1544
+ this.updateControlPointsFromDistances(outerIdx);
1545
+ this.updateSingleTraceSample(innerIdx);
1546
+ this.updateSingleTraceSample(outerIdx);
1547
+ resolved++;
1548
+ } else {
1549
+ outerTrace.d1 = origOuterD1;
1550
+ outerTrace.d2 = origOuterD2;
1551
+ innerTrace.d1 = origInnerD1;
1552
+ innerTrace.d2 = origInnerD2;
1553
+ this.updateControlPointsFromDistances(outerIdx);
1554
+ this.updateControlPointsFromDistances(innerIdx);
1555
+ this.updateSingleTraceSample(outerIdx);
1556
+ this.updateSingleTraceSample(innerIdx);
1557
+ }
1558
+ }
1559
+ return resolved;
1560
+ }
1327
1561
  optimizeStep() {
1328
- const { bounds } = this.problem;
1562
+ const { bounds, preferredTraceToTraceSpacing } = this.problem;
1329
1563
  const { minX, maxX, minY, maxY } = bounds;
1564
+ const minDim = Math.min(maxX - minX, maxY - minY);
1330
1565
  const progress = this.optimizationStep / this.maxOptimizationSteps;
1331
- const baseStep = 3.5 * (1 - progress) + 0.5;
1332
- const SQRT1_2 = Math.SQRT1_2;
1566
+ const baseStep = 4 * (1 - progress) + 0.5;
1567
+ const minDist = minDim * 0.02;
1568
+ const maxDist = minDim * 1.5;
1333
1569
  const traceCosts = [];
1334
1570
  for (let i = 0; i < this.traces.length; i++) {
1335
1571
  traceCosts.push({ idx: i, cost: this.computeCostForTrace(i) });
@@ -1338,76 +1574,77 @@ var CurvyTraceSolver = class extends BaseSolver {
1338
1574
  for (const { idx: i, cost: currentCost } of traceCosts) {
1339
1575
  if (currentCost === 0) continue;
1340
1576
  const trace = this.traces[i];
1341
- const steps = [baseStep, baseStep * 1.5, baseStep * 0.5];
1577
+ const costMultiplier = currentCost > 100 ? 2 : 1;
1578
+ const steps = [baseStep * costMultiplier, baseStep * 1.5 * costMultiplier, baseStep * 0.5];
1342
1579
  for (const step of steps) {
1343
- const directions = [
1344
- { dx: step, dy: 0 },
1345
- { dx: -step, dy: 0 },
1346
- { dx: 0, dy: step },
1347
- { dx: 0, dy: -step },
1348
- { dx: step * SQRT1_2, dy: step * SQRT1_2 },
1349
- { dx: -step * SQRT1_2, dy: -step * SQRT1_2 },
1350
- { dx: step * SQRT1_2, dy: -step * SQRT1_2 },
1351
- { dx: -step * SQRT1_2, dy: step * SQRT1_2 }
1352
- ];
1580
+ const deltas = currentCost > 100 ? [
1581
+ step,
1582
+ -step,
1583
+ step * 2,
1584
+ -step * 2,
1585
+ step * 3,
1586
+ -step * 3,
1587
+ preferredTraceToTraceSpacing * 2,
1588
+ -preferredTraceToTraceSpacing * 2
1589
+ ] : [step, -step, step * 2, -step * 2];
1353
1590
  let bestCost = this.computeCostForTrace(i);
1354
- let bestCtrl1x = trace.ctrl1.x;
1355
- let bestCtrl1y = trace.ctrl1.y;
1356
- let bestCtrl2x = trace.ctrl2.x;
1357
- let bestCtrl2y = trace.ctrl2.y;
1358
- const origCtrl1x = trace.ctrl1.x;
1359
- const origCtrl1y = trace.ctrl1.y;
1360
- const origCtrl2x = trace.ctrl2.x;
1361
- const origCtrl2y = trace.ctrl2.y;
1362
- for (const dir of directions) {
1363
- trace.ctrl1.x = Math.max(minX, Math.min(maxX, origCtrl1x + dir.dx));
1364
- trace.ctrl1.y = Math.max(minY, Math.min(maxY, origCtrl1y + dir.dy));
1591
+ let bestD1 = trace.d1;
1592
+ let bestD2 = trace.d2;
1593
+ const origD1 = trace.d1;
1594
+ const origD2 = trace.d2;
1595
+ for (const delta of deltas) {
1596
+ trace.d1 = Math.max(minDist, Math.min(maxDist, origD1 + delta));
1597
+ this.updateControlPointsFromDistances(i);
1365
1598
  this.updateSingleTraceSample(i);
1366
1599
  const cost1 = this.computeCostForTrace(i);
1367
1600
  if (cost1 < bestCost) {
1368
1601
  bestCost = cost1;
1369
- bestCtrl1x = trace.ctrl1.x;
1370
- bestCtrl1y = trace.ctrl1.y;
1371
- bestCtrl2x = origCtrl2x;
1372
- bestCtrl2y = origCtrl2y;
1602
+ bestD1 = trace.d1;
1603
+ bestD2 = origD2;
1373
1604
  }
1374
- trace.ctrl1.x = origCtrl1x;
1375
- trace.ctrl1.y = origCtrl1y;
1376
- trace.ctrl2.x = Math.max(minX, Math.min(maxX, origCtrl2x + dir.dx));
1377
- trace.ctrl2.y = Math.max(minY, Math.min(maxY, origCtrl2y + dir.dy));
1605
+ trace.d1 = origD1;
1606
+ this.updateControlPointsFromDistances(i);
1607
+ trace.d2 = Math.max(minDist, Math.min(maxDist, origD2 + delta));
1608
+ this.updateControlPointsFromDistances(i);
1378
1609
  this.updateSingleTraceSample(i);
1379
1610
  const cost2 = this.computeCostForTrace(i);
1380
1611
  if (cost2 < bestCost) {
1381
1612
  bestCost = cost2;
1382
- bestCtrl1x = origCtrl1x;
1383
- bestCtrl1y = origCtrl1y;
1384
- bestCtrl2x = trace.ctrl2.x;
1385
- bestCtrl2y = trace.ctrl2.y;
1613
+ bestD1 = origD1;
1614
+ bestD2 = trace.d2;
1386
1615
  }
1387
- trace.ctrl2.x = origCtrl2x;
1388
- trace.ctrl2.y = origCtrl2y;
1389
- trace.ctrl1.x = Math.max(minX, Math.min(maxX, origCtrl1x + dir.dx));
1390
- trace.ctrl1.y = Math.max(minY, Math.min(maxY, origCtrl1y + dir.dy));
1391
- trace.ctrl2.x = Math.max(minX, Math.min(maxX, origCtrl2x + dir.dx));
1392
- trace.ctrl2.y = Math.max(minY, Math.min(maxY, origCtrl2y + dir.dy));
1616
+ trace.d2 = origD2;
1617
+ this.updateControlPointsFromDistances(i);
1618
+ trace.d1 = Math.max(minDist, Math.min(maxDist, origD1 + delta));
1619
+ trace.d2 = Math.max(minDist, Math.min(maxDist, origD2 + delta));
1620
+ this.updateControlPointsFromDistances(i);
1393
1621
  this.updateSingleTraceSample(i);
1394
1622
  const cost3 = this.computeCostForTrace(i);
1395
1623
  if (cost3 < bestCost) {
1396
1624
  bestCost = cost3;
1397
- bestCtrl1x = trace.ctrl1.x;
1398
- bestCtrl1y = trace.ctrl1.y;
1399
- bestCtrl2x = trace.ctrl2.x;
1400
- bestCtrl2y = trace.ctrl2.y;
1625
+ bestD1 = trace.d1;
1626
+ bestD2 = trace.d2;
1627
+ }
1628
+ trace.d1 = origD1;
1629
+ trace.d2 = origD2;
1630
+ this.updateControlPointsFromDistances(i);
1631
+ trace.d1 = Math.max(minDist, Math.min(maxDist, origD1 + delta));
1632
+ trace.d2 = Math.max(minDist, Math.min(maxDist, origD2 - delta));
1633
+ this.updateControlPointsFromDistances(i);
1634
+ this.updateSingleTraceSample(i);
1635
+ const cost4 = this.computeCostForTrace(i);
1636
+ if (cost4 < bestCost) {
1637
+ bestCost = cost4;
1638
+ bestD1 = trace.d1;
1639
+ bestD2 = trace.d2;
1401
1640
  }
1402
- trace.ctrl1.x = origCtrl1x;
1403
- trace.ctrl1.y = origCtrl1y;
1404
- trace.ctrl2.x = origCtrl2x;
1405
- trace.ctrl2.y = origCtrl2y;
1641
+ trace.d1 = origD1;
1642
+ trace.d2 = origD2;
1643
+ this.updateControlPointsFromDistances(i);
1406
1644
  }
1407
- trace.ctrl1.x = bestCtrl1x;
1408
- trace.ctrl1.y = bestCtrl1y;
1409
- trace.ctrl2.x = bestCtrl2x;
1410
- trace.ctrl2.y = bestCtrl2y;
1645
+ trace.d1 = bestD1;
1646
+ trace.d2 = bestD2;
1647
+ this.updateControlPointsFromDistances(i);
1411
1648
  this.updateSingleTraceSample(i);
1412
1649
  if (bestCost < currentCost * 0.9) break;
1413
1650
  }
@@ -1438,13 +1675,25 @@ var CurvyTraceSolver = class extends BaseSolver {
1438
1675
  if (this.optimizationStep < this.maxOptimizationSteps) {
1439
1676
  this.optimizeStep();
1440
1677
  this.optimizationStep++;
1678
+ if (this.optimizationStep % 10 === 0) {
1679
+ const resolved = this.resolveIntersections();
1680
+ if (resolved > 0) {
1681
+ this.updateCollisionPairs();
1682
+ }
1683
+ }
1441
1684
  const currentCost = this.computeTotalCost();
1442
1685
  if (currentCost === 0) {
1443
1686
  this.optimizationStep = this.maxOptimizationSteps;
1444
1687
  } else if (currentCost >= this.lastCost * 0.99) {
1445
1688
  this.stagnantSteps++;
1446
- if (this.stagnantSteps > 15) {
1447
- this.optimizationStep = this.maxOptimizationSteps;
1689
+ if (this.stagnantSteps > 10) {
1690
+ const resolved = this.resolveIntersections();
1691
+ if (resolved > 0) {
1692
+ this.updateCollisionPairs();
1693
+ this.stagnantSteps = 0;
1694
+ } else if (this.stagnantSteps > 15) {
1695
+ this.optimizationStep = this.maxOptimizationSteps;
1696
+ }
1448
1697
  }
1449
1698
  } else {
1450
1699
  this.stagnantSteps = 0;
@@ -1452,6 +1701,7 @@ var CurvyTraceSolver = class extends BaseSolver {
1452
1701
  this.lastCost = currentCost;
1453
1702
  }
1454
1703
  if (this.optimizationStep >= this.maxOptimizationSteps) {
1704
+ this.resolveIntersections();
1455
1705
  this.buildOutputTraces();
1456
1706
  this.solved = true;
1457
1707
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/curvy-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",