@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 +4 -0
- package/dist/index.js +350 -100
- package/package.json +1 -1
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
|
|
935
|
+
function getInwardPerpendicular(p, bounds) {
|
|
936
936
|
const { minX, maxX, minY, maxY } = bounds;
|
|
937
|
-
const
|
|
938
|
-
const
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if (
|
|
943
|
-
if (
|
|
944
|
-
return { x:
|
|
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
|
|
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
|
|
1099
|
-
|
|
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
|
|
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
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
const
|
|
1113
|
-
const
|
|
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
|
|
1132
|
-
const
|
|
1133
|
-
const
|
|
1134
|
-
const
|
|
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
|
-
|
|
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 =
|
|
1332
|
-
const
|
|
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
|
|
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
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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
|
|
1355
|
-
let
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
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
|
-
|
|
1370
|
-
|
|
1371
|
-
bestCtrl2x = origCtrl2x;
|
|
1372
|
-
bestCtrl2y = origCtrl2y;
|
|
1602
|
+
bestD1 = trace.d1;
|
|
1603
|
+
bestD2 = origD2;
|
|
1373
1604
|
}
|
|
1374
|
-
trace.
|
|
1375
|
-
|
|
1376
|
-
trace.
|
|
1377
|
-
|
|
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
|
-
|
|
1383
|
-
|
|
1384
|
-
bestCtrl2x = trace.ctrl2.x;
|
|
1385
|
-
bestCtrl2y = trace.ctrl2.y;
|
|
1613
|
+
bestD1 = origD1;
|
|
1614
|
+
bestD2 = trace.d2;
|
|
1386
1615
|
}
|
|
1387
|
-
trace.
|
|
1388
|
-
|
|
1389
|
-
trace.
|
|
1390
|
-
trace.
|
|
1391
|
-
|
|
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
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
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.
|
|
1403
|
-
trace.
|
|
1404
|
-
|
|
1405
|
-
trace.ctrl2.y = origCtrl2y;
|
|
1641
|
+
trace.d1 = origD1;
|
|
1642
|
+
trace.d2 = origD2;
|
|
1643
|
+
this.updateControlPointsFromDistances(i);
|
|
1406
1644
|
}
|
|
1407
|
-
trace.
|
|
1408
|
-
trace.
|
|
1409
|
-
|
|
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 >
|
|
1447
|
-
|
|
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
|
}
|