@tscircuit/hypergraph 0.0.34 → 0.0.36
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 +76 -215
- package/dist/index.js +634 -369
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -337,6 +337,15 @@ var HyperGraphSolver = class extends BaseSolver {
|
|
|
337
337
|
isRipRequiredForPortUsage(_region, _port1, _port2) {
|
|
338
338
|
return false;
|
|
339
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* OPTIONALLY OVERRIDE THIS
|
|
342
|
+
*
|
|
343
|
+
* Return false to prevent transitioning through a region from `_port1` to
|
|
344
|
+
* `_port2`.
|
|
345
|
+
*/
|
|
346
|
+
isTransitionAllowed(_region, _port1, _port2) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
340
349
|
computeG(candidate) {
|
|
341
350
|
return candidate.parent.g + this.computeIncreasedRegionCostIfPortsAreUsed(
|
|
342
351
|
candidate.lastRegion,
|
|
@@ -405,6 +414,13 @@ var HyperGraphSolver = class extends BaseSolver {
|
|
|
405
414
|
const nextCandidatesByRegion = {};
|
|
406
415
|
for (const port of currentRegion.ports) {
|
|
407
416
|
if (port === currentCandidate.port) continue;
|
|
417
|
+
if (!this.isTransitionAllowed(
|
|
418
|
+
currentRegion,
|
|
419
|
+
currentPort,
|
|
420
|
+
port
|
|
421
|
+
)) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
408
424
|
const ripRequired = port.assignment && port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId || this.isRipRequiredForPortUsage(
|
|
409
425
|
currentRegion,
|
|
410
426
|
currentPort,
|
|
@@ -772,42 +788,156 @@ var rotateGraph90Degrees = (graph) => {
|
|
|
772
788
|
};
|
|
773
789
|
|
|
774
790
|
// lib/JumperGraphSolver/perimeterChordUtils.ts
|
|
791
|
+
function clamp2(value, min, max) {
|
|
792
|
+
return Math.max(min, Math.min(max, value));
|
|
793
|
+
}
|
|
794
|
+
function dist2(a, b) {
|
|
795
|
+
const dx = a.x - b.x;
|
|
796
|
+
const dy = a.y - b.y;
|
|
797
|
+
return dx * dx + dy * dy;
|
|
798
|
+
}
|
|
799
|
+
function getRectanglePerimeter(xmin, xmax, ymin, ymax) {
|
|
800
|
+
return 2 * (xmax - xmin) + 2 * (ymax - ymin);
|
|
801
|
+
}
|
|
775
802
|
function perimeterT(p, xmin, xmax, ymin, ymax) {
|
|
776
803
|
const W = xmax - xmin;
|
|
777
804
|
const H = ymax - ymin;
|
|
778
805
|
const eps = 1e-6;
|
|
779
|
-
if (Math.abs(p.y - ymax) < eps)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (Math.abs(p.x -
|
|
783
|
-
return W + (ymax - p.y);
|
|
784
|
-
}
|
|
785
|
-
if (Math.abs(p.y - ymin) < eps) {
|
|
786
|
-
return W + H + (xmax - p.x);
|
|
787
|
-
}
|
|
788
|
-
if (Math.abs(p.x - xmin) < eps) {
|
|
789
|
-
return 2 * W + H + (p.y - ymin);
|
|
790
|
-
}
|
|
806
|
+
if (Math.abs(p.y - ymax) < eps) return p.x - xmin;
|
|
807
|
+
if (Math.abs(p.x - xmax) < eps) return W + (ymax - p.y);
|
|
808
|
+
if (Math.abs(p.y - ymin) < eps) return W + H + (xmax - p.x);
|
|
809
|
+
if (Math.abs(p.x - xmin) < eps) return 2 * W + H + (p.y - ymin);
|
|
791
810
|
const distTop = Math.abs(p.y - ymax);
|
|
792
811
|
const distRight = Math.abs(p.x - xmax);
|
|
793
812
|
const distBottom = Math.abs(p.y - ymin);
|
|
794
813
|
const distLeft = Math.abs(p.x - xmin);
|
|
795
814
|
const minDist = Math.min(distTop, distRight, distBottom, distLeft);
|
|
796
|
-
if (minDist === distTop)
|
|
797
|
-
|
|
798
|
-
}
|
|
799
|
-
if (minDist === distRight) {
|
|
800
|
-
return W + Math.max(0, Math.min(H, ymax - p.y));
|
|
801
|
-
}
|
|
815
|
+
if (minDist === distTop) return Math.max(0, Math.min(W, p.x - xmin));
|
|
816
|
+
if (minDist === distRight) return W + Math.max(0, Math.min(H, ymax - p.y));
|
|
802
817
|
if (minDist === distBottom) {
|
|
803
818
|
return W + H + Math.max(0, Math.min(W, xmax - p.x));
|
|
804
819
|
}
|
|
805
820
|
return 2 * W + H + Math.max(0, Math.min(H, p.y - ymin));
|
|
806
821
|
}
|
|
822
|
+
function projectToSegment(p, a, b) {
|
|
823
|
+
const abx = b.x - a.x;
|
|
824
|
+
const aby = b.y - a.y;
|
|
825
|
+
const apx = p.x - a.x;
|
|
826
|
+
const apy = p.y - a.y;
|
|
827
|
+
const ab2 = abx * abx + aby * aby;
|
|
828
|
+
const u = ab2 > 0 ? clamp2((apx * abx + apy * aby) / ab2, 0, 1) : 0;
|
|
829
|
+
const q = { x: a.x + u * abx, y: a.y + u * aby };
|
|
830
|
+
return { u, d2: dist2(p, q) };
|
|
831
|
+
}
|
|
832
|
+
function createPolygonPerimeterCache(polygon2) {
|
|
833
|
+
const n = polygon2.length;
|
|
834
|
+
const edgeLengths = new Array(n);
|
|
835
|
+
const cumulative = new Array(n + 1);
|
|
836
|
+
cumulative[0] = 0;
|
|
837
|
+
for (let i = 0; i < n; i++) {
|
|
838
|
+
const a = polygon2[i];
|
|
839
|
+
const b = polygon2[(i + 1) % n];
|
|
840
|
+
const edgeLength = Math.hypot(b.x - a.x, b.y - a.y);
|
|
841
|
+
edgeLengths[i] = edgeLength;
|
|
842
|
+
cumulative[i + 1] = cumulative[i] + edgeLength;
|
|
843
|
+
}
|
|
844
|
+
return { edgeLengths, cumulative, perimeter: cumulative[n] };
|
|
845
|
+
}
|
|
846
|
+
function perimeterTPolygonWithCache(p, polygon2, cache, eps = 1e-6) {
|
|
847
|
+
let bestEdgeIndex = 0;
|
|
848
|
+
let bestU = 0;
|
|
849
|
+
let bestD2 = Number.POSITIVE_INFINITY;
|
|
850
|
+
const eps2 = eps * eps;
|
|
851
|
+
for (let i = 0; i < polygon2.length; i++) {
|
|
852
|
+
const a = polygon2[i];
|
|
853
|
+
const b = polygon2[(i + 1) % polygon2.length];
|
|
854
|
+
const projected = projectToSegment(p, a, b);
|
|
855
|
+
if (projected.d2 <= eps2) {
|
|
856
|
+
bestEdgeIndex = i;
|
|
857
|
+
bestU = projected.u;
|
|
858
|
+
bestD2 = projected.d2;
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
if (projected.d2 < bestD2) {
|
|
862
|
+
bestEdgeIndex = i;
|
|
863
|
+
bestU = projected.u;
|
|
864
|
+
bestD2 = projected.d2;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return cache.cumulative[bestEdgeIndex] + bestU * cache.edgeLengths[bestEdgeIndex];
|
|
868
|
+
}
|
|
869
|
+
function getRegionPolygonCache(region) {
|
|
870
|
+
const polygon2 = region.d.polygon;
|
|
871
|
+
if (!polygon2 || polygon2.length < 3) return null;
|
|
872
|
+
const existing = region.d.polygonPerimeterCache;
|
|
873
|
+
if (existing) return existing;
|
|
874
|
+
const cache = createPolygonPerimeterCache(polygon2);
|
|
875
|
+
region.d.polygonPerimeterCache = cache;
|
|
876
|
+
return cache;
|
|
877
|
+
}
|
|
878
|
+
function getRegionPerimeter(region) {
|
|
879
|
+
const polygonCache = getRegionPolygonCache(region);
|
|
880
|
+
if (polygonCache) return polygonCache.perimeter;
|
|
881
|
+
const { minX, maxX, minY, maxY } = region.d.bounds;
|
|
882
|
+
return getRectanglePerimeter(minX, maxX, minY, maxY);
|
|
883
|
+
}
|
|
884
|
+
function getPortPerimeterTInRegion(port, region) {
|
|
885
|
+
if (port.region1 === region) {
|
|
886
|
+
if (typeof port.region1T === "number") return port.region1T;
|
|
887
|
+
const t = getPointPerimeterTInRegion(port.d, region);
|
|
888
|
+
port.region1T = t;
|
|
889
|
+
return t;
|
|
890
|
+
}
|
|
891
|
+
if (port.region2 === region) {
|
|
892
|
+
if (typeof port.region2T === "number") return port.region2T;
|
|
893
|
+
const t = getPointPerimeterTInRegion(port.d, region);
|
|
894
|
+
port.region2T = t;
|
|
895
|
+
return t;
|
|
896
|
+
}
|
|
897
|
+
return getPointPerimeterTInRegion(port.d, region);
|
|
898
|
+
}
|
|
899
|
+
function getPointPerimeterTInRegion(p, region) {
|
|
900
|
+
const polygon2 = region.d.polygon;
|
|
901
|
+
if (polygon2 && polygon2.length >= 3) {
|
|
902
|
+
const cache = getRegionPolygonCache(region);
|
|
903
|
+
if (cache) return perimeterTPolygonWithCache(p, polygon2, cache);
|
|
904
|
+
}
|
|
905
|
+
const { minX, maxX, minY, maxY } = region.d.bounds;
|
|
906
|
+
return perimeterT(p, minX, maxX, minY, maxY);
|
|
907
|
+
}
|
|
807
908
|
function areCoincident(t1, t2, eps = 1e-6) {
|
|
808
909
|
return Math.abs(t1 - t2) < eps;
|
|
809
910
|
}
|
|
810
|
-
function
|
|
911
|
+
function normalizeMod(value, modulus) {
|
|
912
|
+
return (value % modulus + modulus) % modulus;
|
|
913
|
+
}
|
|
914
|
+
function areCoincidentOnCircle(t1, t2, perimeter, eps) {
|
|
915
|
+
const delta = Math.abs(normalizeMod(t1 - t2, perimeter));
|
|
916
|
+
return delta < eps || perimeter - delta < eps;
|
|
917
|
+
}
|
|
918
|
+
function betweenMod(x, start, end, perimeter, eps) {
|
|
919
|
+
const nx = normalizeMod(x, perimeter);
|
|
920
|
+
const ns = normalizeMod(start, perimeter);
|
|
921
|
+
const ne = normalizeMod(end, perimeter);
|
|
922
|
+
if (Math.abs(ns - ne) < eps) return false;
|
|
923
|
+
if (ns < ne) return ns < nx && nx < ne;
|
|
924
|
+
return nx > ns || nx < ne;
|
|
925
|
+
}
|
|
926
|
+
function chordsCross(chord1, chord2, perimeter) {
|
|
927
|
+
if (typeof perimeter === "number" && perimeter > 0) {
|
|
928
|
+
let [a2, b2] = chord1;
|
|
929
|
+
let [c2, d2] = chord2;
|
|
930
|
+
a2 = normalizeMod(a2, perimeter);
|
|
931
|
+
b2 = normalizeMod(b2, perimeter);
|
|
932
|
+
c2 = normalizeMod(c2, perimeter);
|
|
933
|
+
d2 = normalizeMod(d2, perimeter);
|
|
934
|
+
if (areCoincidentOnCircle(a2, c2, perimeter, 1e-6) || areCoincidentOnCircle(a2, d2, perimeter, 1e-6) || areCoincidentOnCircle(b2, c2, perimeter, 1e-6) || areCoincidentOnCircle(b2, d2, perimeter, 1e-6)) {
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
const cInside = betweenMod(c2, a2, b2, perimeter, 1e-12);
|
|
938
|
+
const dInside = betweenMod(d2, a2, b2, perimeter, 1e-12);
|
|
939
|
+
return cInside !== dInside;
|
|
940
|
+
}
|
|
811
941
|
const [a, b] = chord1[0] < chord1[1] ? chord1 : [chord1[1], chord1[0]];
|
|
812
942
|
const [c, d] = chord2[0] < chord2[1] ? chord2 : [chord2[1], chord2[0]];
|
|
813
943
|
if (areCoincident(a, c) || areCoincident(a, d) || areCoincident(b, c) || areCoincident(b, d)) {
|
|
@@ -818,29 +948,23 @@ function chordsCross(chord1, chord2) {
|
|
|
818
948
|
|
|
819
949
|
// lib/JumperGraphSolver/computeCrossingAssignments.ts
|
|
820
950
|
function computeCrossingAssignments(region, port1, port2) {
|
|
821
|
-
const
|
|
822
|
-
const t1 =
|
|
823
|
-
const t2 =
|
|
951
|
+
const perimeter = getRegionPerimeter(region);
|
|
952
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
953
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
824
954
|
const newChord = [t1, t2];
|
|
825
955
|
const crossingAssignments = [];
|
|
826
956
|
const assignments = region.assignments ?? [];
|
|
827
957
|
for (const assignment of assignments) {
|
|
828
|
-
const existingT1 =
|
|
829
|
-
assignment.regionPort1
|
|
830
|
-
|
|
831
|
-
xmax,
|
|
832
|
-
ymin,
|
|
833
|
-
ymax
|
|
958
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
959
|
+
assignment.regionPort1,
|
|
960
|
+
region
|
|
834
961
|
);
|
|
835
|
-
const existingT2 =
|
|
836
|
-
assignment.regionPort2
|
|
837
|
-
|
|
838
|
-
xmax,
|
|
839
|
-
ymin,
|
|
840
|
-
ymax
|
|
962
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
963
|
+
assignment.regionPort2,
|
|
964
|
+
region
|
|
841
965
|
);
|
|
842
966
|
const existingChord = [existingT1, existingT2];
|
|
843
|
-
if (chordsCross(newChord, existingChord)) {
|
|
967
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
844
968
|
crossingAssignments.push(assignment);
|
|
845
969
|
}
|
|
846
970
|
}
|
|
@@ -849,29 +973,23 @@ function computeCrossingAssignments(region, port1, port2) {
|
|
|
849
973
|
|
|
850
974
|
// lib/JumperGraphSolver/computeDifferentNetCrossings.ts
|
|
851
975
|
function computeDifferentNetCrossings(region, port1, port2) {
|
|
852
|
-
const
|
|
853
|
-
const t1 =
|
|
854
|
-
const t2 =
|
|
976
|
+
const perimeter = getRegionPerimeter(region);
|
|
977
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
978
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
855
979
|
const newChord = [t1, t2];
|
|
856
980
|
let crossings = 0;
|
|
857
981
|
const assignments = region.assignments ?? [];
|
|
858
982
|
for (const assignment of assignments) {
|
|
859
|
-
const existingT1 =
|
|
860
|
-
assignment.regionPort1
|
|
861
|
-
|
|
862
|
-
xmax,
|
|
863
|
-
ymin,
|
|
864
|
-
ymax
|
|
983
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
984
|
+
assignment.regionPort1,
|
|
985
|
+
region
|
|
865
986
|
);
|
|
866
|
-
const existingT2 =
|
|
867
|
-
assignment.regionPort2
|
|
868
|
-
|
|
869
|
-
xmax,
|
|
870
|
-
ymin,
|
|
871
|
-
ymax
|
|
987
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
988
|
+
assignment.regionPort2,
|
|
989
|
+
region
|
|
872
990
|
);
|
|
873
991
|
const existingChord = [existingT1, existingT2];
|
|
874
|
-
if (chordsCross(newChord, existingChord)) {
|
|
992
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
875
993
|
crossings++;
|
|
876
994
|
}
|
|
877
995
|
}
|
|
@@ -909,6 +1027,7 @@ function countInputConnectionCrossings(graph, connections) {
|
|
|
909
1027
|
}
|
|
910
1028
|
}
|
|
911
1029
|
const chords = [];
|
|
1030
|
+
const perimeter = 2 * (maxX - minX) + 2 * (maxY - minY);
|
|
912
1031
|
for (const conn of connections) {
|
|
913
1032
|
let startCenter;
|
|
914
1033
|
let endCenter;
|
|
@@ -931,7 +1050,7 @@ function countInputConnectionCrossings(graph, connections) {
|
|
|
931
1050
|
let crossings = 0;
|
|
932
1051
|
for (let i = 0; i < chords.length; i++) {
|
|
933
1052
|
for (let j = i + 1; j < chords.length; j++) {
|
|
934
|
-
if (chordsCross(chords[i], chords[j])) {
|
|
1053
|
+
if (chordsCross(chords[i], chords[j], perimeter)) {
|
|
935
1054
|
crossings++;
|
|
936
1055
|
}
|
|
937
1056
|
}
|
|
@@ -972,9 +1091,7 @@ var visualizeJumperGraph = (graph, options) => {
|
|
|
972
1091
|
const points = polygon2;
|
|
973
1092
|
graphics.polygons.push({
|
|
974
1093
|
points,
|
|
975
|
-
fill
|
|
976
|
-
stroke: "rgba(128, 128, 128, 0.5)",
|
|
977
|
-
strokeWidth: 0.03
|
|
1094
|
+
fill
|
|
978
1095
|
});
|
|
979
1096
|
} else {
|
|
980
1097
|
graphics.rects.push({
|
|
@@ -1065,6 +1182,12 @@ var visualizeJumperGraphSolver = (solver) => {
|
|
|
1065
1182
|
hidePortPoints: true
|
|
1066
1183
|
} : {}
|
|
1067
1184
|
});
|
|
1185
|
+
if (solver.iterations === 0) {
|
|
1186
|
+
for (const polygon2 of graphics.polygons) {
|
|
1187
|
+
polygon2.stroke = "rgba(128, 128, 128, 0.5)";
|
|
1188
|
+
polygon2.strokeWidth = 0.03;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1068
1191
|
if (solver.currentConnection && !solver.solved) {
|
|
1069
1192
|
const connectionColor = getConnectionColor(
|
|
1070
1193
|
solver.currentConnection.connectionId
|
|
@@ -1230,11 +1353,35 @@ var JumperGraphSolver = class extends HyperGraphSolver {
|
|
|
1230
1353
|
const ripCount = port.ripCount ?? 0;
|
|
1231
1354
|
return ripCount * this.portUsagePenalty + ripCount * this.portUsagePenaltySq;
|
|
1232
1355
|
}
|
|
1356
|
+
isTransitionAllowed(region, port1, port2) {
|
|
1357
|
+
if (!region.d.isPad) return true;
|
|
1358
|
+
const usesThroughJumper = (port) => {
|
|
1359
|
+
const otherRegion = port.region1 === region ? port.region2 : port.region1;
|
|
1360
|
+
return Boolean(otherRegion.d.isThroughJumper);
|
|
1361
|
+
};
|
|
1362
|
+
return usesThroughJumper(port1) || usesThroughJumper(port2);
|
|
1363
|
+
}
|
|
1233
1364
|
computeIncreasedRegionCostIfPortsAreUsed(region, port1, port2) {
|
|
1365
|
+
if (region.d.isPad) {
|
|
1366
|
+
const assignments = region.assignments ?? [];
|
|
1367
|
+
const differentNetCount = assignments.filter(
|
|
1368
|
+
(a) => a.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId
|
|
1369
|
+
).length;
|
|
1370
|
+
if (differentNetCount > 0) {
|
|
1371
|
+
return differentNetCount * this.crossingPenalty + differentNetCount * this.crossingPenaltySq;
|
|
1372
|
+
}
|
|
1373
|
+
return 0;
|
|
1374
|
+
}
|
|
1234
1375
|
const crossings = computeDifferentNetCrossings(region, port1, port2);
|
|
1235
1376
|
return crossings * this.crossingPenalty + crossings * this.crossingPenaltySq;
|
|
1236
1377
|
}
|
|
1237
1378
|
getRipsRequiredForPortUsage(region, port1, port2) {
|
|
1379
|
+
if (region.d.isPad) {
|
|
1380
|
+
const assignments = region.assignments ?? [];
|
|
1381
|
+
return assignments.filter(
|
|
1382
|
+
(a) => a.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1238
1385
|
const crossingAssignments = computeCrossingAssignments(region, port1, port2);
|
|
1239
1386
|
const conflictingAssignments = crossingAssignments.filter(
|
|
1240
1387
|
(a) => a.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId
|
|
@@ -1249,7 +1396,7 @@ var JumperGraphSolver = class extends HyperGraphSolver {
|
|
|
1249
1396
|
return conflictingAssignments;
|
|
1250
1397
|
}
|
|
1251
1398
|
isRipRequiredForPortUsage(region, _port1, _port2) {
|
|
1252
|
-
if (!region.d.isThroughJumper) return false;
|
|
1399
|
+
if (!region.d.isThroughJumper && !region.d.isPad) return false;
|
|
1253
1400
|
for (const assignment of region.assignments ?? []) {
|
|
1254
1401
|
if (assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId) {
|
|
1255
1402
|
return true;
|
|
@@ -1301,23 +1448,123 @@ var createConnectionRegion = (regionId, x, y) => {
|
|
|
1301
1448
|
};
|
|
1302
1449
|
|
|
1303
1450
|
// lib/JumperGraphSolver/jumper-graph-generator/findBoundaryRegion.ts
|
|
1304
|
-
var
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1451
|
+
var EPS = 0.01;
|
|
1452
|
+
var clamp3 = (value, min, max) => {
|
|
1453
|
+
return Math.max(min, Math.min(max, value));
|
|
1454
|
+
};
|
|
1455
|
+
var getBoundarySidesForPoint = (x, y, graphBounds) => {
|
|
1456
|
+
const sides = [];
|
|
1457
|
+
if (Math.abs(x - graphBounds.minX) < EPS) sides.push("left");
|
|
1458
|
+
if (Math.abs(x - graphBounds.maxX) < EPS) sides.push("right");
|
|
1459
|
+
if (Math.abs(y - graphBounds.maxY) < EPS) sides.push("top");
|
|
1460
|
+
if (Math.abs(y - graphBounds.minY) < EPS) sides.push("bottom");
|
|
1461
|
+
return sides;
|
|
1462
|
+
};
|
|
1463
|
+
var isPointOnSide = (p, side, b) => {
|
|
1464
|
+
if (side === "left") return Math.abs(p.x - b.minX) < EPS;
|
|
1465
|
+
if (side === "right") return Math.abs(p.x - b.maxX) < EPS;
|
|
1466
|
+
if (side === "top") return Math.abs(p.y - b.maxY) < EPS;
|
|
1467
|
+
return Math.abs(p.y - b.minY) < EPS;
|
|
1468
|
+
};
|
|
1469
|
+
var projectToSegment2 = (x, y, a, b) => {
|
|
1470
|
+
const abx = b.x - a.x;
|
|
1471
|
+
const aby = b.y - a.y;
|
|
1472
|
+
const apx = x - a.x;
|
|
1473
|
+
const apy = y - a.y;
|
|
1474
|
+
const ab2 = abx * abx + aby * aby;
|
|
1475
|
+
const t = ab2 > 0 ? clamp3((apx * abx + apy * aby) / ab2, 0, 1) : 0;
|
|
1476
|
+
const px = a.x + t * abx;
|
|
1477
|
+
const py = a.y + t * aby;
|
|
1478
|
+
const dx = x - px;
|
|
1479
|
+
const dy = y - py;
|
|
1480
|
+
return {
|
|
1481
|
+
x: px,
|
|
1482
|
+
y: py,
|
|
1483
|
+
d2: dx * dx + dy * dy
|
|
1484
|
+
};
|
|
1485
|
+
};
|
|
1486
|
+
var getRegionBoundaryProjection = (x, y, region, graphBounds, preferredSides) => {
|
|
1487
|
+
const polygon2 = region.d.polygon;
|
|
1488
|
+
if (polygon2 && polygon2.length >= 3) {
|
|
1489
|
+
const sideSet = new Set(preferredSides);
|
|
1490
|
+
let best2 = null;
|
|
1491
|
+
for (let i = 0; i < polygon2.length; i++) {
|
|
1492
|
+
const a = polygon2[i];
|
|
1493
|
+
const b = polygon2[(i + 1) % polygon2.length];
|
|
1494
|
+
if (preferredSides.length > 0) {
|
|
1495
|
+
const edgeOnPreferredSide = preferredSides.some(
|
|
1496
|
+
(side) => isPointOnSide(a, side, graphBounds) && isPointOnSide(b, side, graphBounds) && sideSet.has(side)
|
|
1497
|
+
);
|
|
1498
|
+
if (!edgeOnPreferredSide) continue;
|
|
1499
|
+
}
|
|
1500
|
+
const p = projectToSegment2(x, y, a, b);
|
|
1501
|
+
if (!best2 || p.d2 < best2.d2) {
|
|
1502
|
+
best2 = p;
|
|
1503
|
+
}
|
|
1313
1504
|
}
|
|
1314
|
-
if (
|
|
1315
|
-
|
|
1505
|
+
if (best2) return best2;
|
|
1506
|
+
}
|
|
1507
|
+
const bounds = region.d.bounds;
|
|
1508
|
+
const sideCandidates = [];
|
|
1509
|
+
if (preferredSides.length > 0) {
|
|
1510
|
+
for (const side of preferredSides) {
|
|
1511
|
+
if (side === "left") {
|
|
1512
|
+
sideCandidates.push({
|
|
1513
|
+
side,
|
|
1514
|
+
x: bounds.minX,
|
|
1515
|
+
y: clamp3(y, bounds.minY, bounds.maxY)
|
|
1516
|
+
});
|
|
1517
|
+
} else if (side === "right") {
|
|
1518
|
+
sideCandidates.push({
|
|
1519
|
+
side,
|
|
1520
|
+
x: bounds.maxX,
|
|
1521
|
+
y: clamp3(y, bounds.minY, bounds.maxY)
|
|
1522
|
+
});
|
|
1523
|
+
} else if (side === "top") {
|
|
1524
|
+
sideCandidates.push({
|
|
1525
|
+
side,
|
|
1526
|
+
x: clamp3(x, bounds.minX, bounds.maxX),
|
|
1527
|
+
y: bounds.maxY
|
|
1528
|
+
});
|
|
1529
|
+
} else {
|
|
1530
|
+
sideCandidates.push({
|
|
1531
|
+
side,
|
|
1532
|
+
x: clamp3(x, bounds.minX, bounds.maxX),
|
|
1533
|
+
y: bounds.minY
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1316
1536
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1537
|
+
}
|
|
1538
|
+
if (sideCandidates.length === 0) {
|
|
1539
|
+
sideCandidates.push(
|
|
1540
|
+
{ side: "left", x: bounds.minX, y: clamp3(y, bounds.minY, bounds.maxY) },
|
|
1541
|
+
{
|
|
1542
|
+
side: "right",
|
|
1543
|
+
x: bounds.maxX,
|
|
1544
|
+
y: clamp3(y, bounds.minY, bounds.maxY)
|
|
1545
|
+
},
|
|
1546
|
+
{ side: "top", x: clamp3(x, bounds.minX, bounds.maxX), y: bounds.maxY },
|
|
1547
|
+
{
|
|
1548
|
+
side: "bottom",
|
|
1549
|
+
x: clamp3(x, bounds.minX, bounds.maxX),
|
|
1550
|
+
y: bounds.minY
|
|
1551
|
+
}
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
let best = null;
|
|
1555
|
+
for (const c of sideCandidates) {
|
|
1556
|
+
if (preferredSides.length > 0 && !preferredSides.includes(c.side)) continue;
|
|
1557
|
+
const dx = x - c.x;
|
|
1558
|
+
const dy = y - c.y;
|
|
1559
|
+
const d2 = dx * dx + dy * dy;
|
|
1560
|
+
if (!best || d2 < best.d2) {
|
|
1561
|
+
best = { x: c.x, y: c.y, d2 };
|
|
1319
1562
|
}
|
|
1320
1563
|
}
|
|
1564
|
+
return best;
|
|
1565
|
+
};
|
|
1566
|
+
var findBoundaryRegion = (x, y, regions, graphBounds) => {
|
|
1567
|
+
const preferredSides = getBoundarySidesForPoint(x, y, graphBounds);
|
|
1321
1568
|
let closestRegion = null;
|
|
1322
1569
|
let closestDistance = Number.POSITIVE_INFINITY;
|
|
1323
1570
|
let closestPortPosition = { x, y };
|
|
@@ -1326,23 +1573,19 @@ var findBoundaryRegion = (x, y, regions, graphBounds) => {
|
|
|
1326
1573
|
const bounds = region.d.bounds;
|
|
1327
1574
|
const isOuterRegion = Math.abs(bounds.minX - graphBounds.minX) < 0.01 || Math.abs(bounds.maxX - graphBounds.maxX) < 0.01 || Math.abs(bounds.minY - graphBounds.minY) < 0.01 || Math.abs(bounds.maxY - graphBounds.maxY) < 0.01;
|
|
1328
1575
|
if (!isOuterRegion) continue;
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1576
|
+
const projection = getRegionBoundaryProjection(
|
|
1577
|
+
x,
|
|
1578
|
+
y,
|
|
1579
|
+
region,
|
|
1580
|
+
graphBounds,
|
|
1581
|
+
preferredSides
|
|
1582
|
+
);
|
|
1583
|
+
if (!projection) continue;
|
|
1584
|
+
const dist = Math.sqrt(projection.d2);
|
|
1332
1585
|
if (dist < closestDistance) {
|
|
1333
1586
|
closestDistance = dist;
|
|
1334
1587
|
closestRegion = region;
|
|
1335
|
-
|
|
1336
|
-
closestPortPosition = { x: bounds.minX, y: clampedY };
|
|
1337
|
-
} else if (x > bounds.maxX) {
|
|
1338
|
-
closestPortPosition = { x: bounds.maxX, y: clampedY };
|
|
1339
|
-
} else if (y < bounds.minY) {
|
|
1340
|
-
closestPortPosition = { x: clampedX, y: bounds.minY };
|
|
1341
|
-
} else if (y > bounds.maxY) {
|
|
1342
|
-
closestPortPosition = { x: clampedX, y: bounds.maxY };
|
|
1343
|
-
} else {
|
|
1344
|
-
closestPortPosition = { x: clampedX, y: clampedY };
|
|
1345
|
-
}
|
|
1588
|
+
closestPortPosition = { x: projection.x, y: projection.y };
|
|
1346
1589
|
}
|
|
1347
1590
|
}
|
|
1348
1591
|
if (closestRegion) {
|
|
@@ -3640,48 +3883,24 @@ var via_tile_default = {
|
|
|
3640
3883
|
y: 0.582296
|
|
3641
3884
|
},
|
|
3642
3885
|
{
|
|
3643
|
-
x: 1.
|
|
3644
|
-
y: 0.
|
|
3645
|
-
},
|
|
3646
|
-
{
|
|
3647
|
-
x: 1.425309,
|
|
3648
|
-
y: 0.74908
|
|
3649
|
-
},
|
|
3650
|
-
{
|
|
3651
|
-
x: 1.762721,
|
|
3652
|
-
y: 1.086492
|
|
3653
|
-
},
|
|
3654
|
-
{
|
|
3655
|
-
x: 1.762721,
|
|
3656
|
-
y: 1.563668
|
|
3657
|
-
},
|
|
3658
|
-
{
|
|
3659
|
-
x: 1.425309,
|
|
3660
|
-
y: 1.90108
|
|
3886
|
+
x: 1.712721,
|
|
3887
|
+
y: 0.811183
|
|
3661
3888
|
},
|
|
3662
3889
|
{
|
|
3663
|
-
x: 1.
|
|
3664
|
-
y: 1.
|
|
3890
|
+
x: 1.712721,
|
|
3891
|
+
y: 1.542957
|
|
3665
3892
|
},
|
|
3666
3893
|
{
|
|
3667
|
-
x: 0.
|
|
3668
|
-
y: 2.
|
|
3894
|
+
x: 0.920111,
|
|
3895
|
+
y: 2.335567
|
|
3669
3896
|
},
|
|
3670
3897
|
{
|
|
3671
|
-
x: 0.
|
|
3672
|
-
y: 2.
|
|
3673
|
-
},
|
|
3674
|
-
{
|
|
3675
|
-
x: -0.753066,
|
|
3676
|
-
y: 1.618543
|
|
3677
|
-
},
|
|
3678
|
-
{
|
|
3679
|
-
x: -0.753066,
|
|
3680
|
-
y: 1.360375
|
|
3898
|
+
x: 0.034666,
|
|
3899
|
+
y: 2.335566
|
|
3681
3900
|
},
|
|
3682
3901
|
{
|
|
3683
3902
|
x: -0.703066,
|
|
3684
|
-
y: 1.
|
|
3903
|
+
y: 1.597834
|
|
3685
3904
|
},
|
|
3686
3905
|
{
|
|
3687
3906
|
x: -0.703066,
|
|
@@ -3704,32 +3923,20 @@ var via_tile_default = {
|
|
|
3704
3923
|
y: -1.893395
|
|
3705
3924
|
},
|
|
3706
3925
|
{
|
|
3707
|
-
x: -2.
|
|
3708
|
-
y: -1.
|
|
3709
|
-
},
|
|
3710
|
-
{
|
|
3711
|
-
x: -2.2,
|
|
3712
|
-
y: 0.903041
|
|
3713
|
-
},
|
|
3714
|
-
{
|
|
3715
|
-
x: -1.603041,
|
|
3716
|
-
y: 1.5
|
|
3926
|
+
x: -2.10103,
|
|
3927
|
+
y: -1.49897
|
|
3717
3928
|
},
|
|
3718
3929
|
{
|
|
3719
|
-
x: -
|
|
3720
|
-
y: 1.
|
|
3930
|
+
x: -2.10103,
|
|
3931
|
+
y: 1.002011
|
|
3721
3932
|
},
|
|
3722
3933
|
{
|
|
3723
|
-
x: -1.
|
|
3724
|
-
y: 1.
|
|
3934
|
+
x: -1.708294,
|
|
3935
|
+
y: 1.394747
|
|
3725
3936
|
},
|
|
3726
3937
|
{
|
|
3727
|
-
x: -0.
|
|
3728
|
-
y: 1.
|
|
3729
|
-
},
|
|
3730
|
-
{
|
|
3731
|
-
x: -0.703066,
|
|
3732
|
-
y: 1.310375
|
|
3938
|
+
x: -0.897813,
|
|
3939
|
+
y: 1.394747
|
|
3733
3940
|
},
|
|
3734
3941
|
{
|
|
3735
3942
|
x: -0.703066,
|
|
@@ -3739,81 +3946,37 @@ var via_tile_default = {
|
|
|
3739
3946
|
},
|
|
3740
3947
|
{
|
|
3741
3948
|
routeId: "Net5:route_0",
|
|
3742
|
-
fromPort: "
|
|
3743
|
-
toPort: "
|
|
3949
|
+
fromPort: "4e57cee8-0910-4c83-9ec1-1ed2c9d16dcc",
|
|
3950
|
+
toPort: "a3797f13-73f9-48c3-a448-bea3980cdd65",
|
|
3744
3951
|
layer: "bottom",
|
|
3745
3952
|
segments: [
|
|
3746
3953
|
{
|
|
3747
|
-
x:
|
|
3748
|
-
y: -
|
|
3749
|
-
},
|
|
3750
|
-
{
|
|
3751
|
-
x: -0.764375,
|
|
3752
|
-
y: -0.447998
|
|
3753
|
-
},
|
|
3754
|
-
{
|
|
3755
|
-
x: -0.636191,
|
|
3756
|
-
y: -0.447998
|
|
3757
|
-
},
|
|
3758
|
-
{
|
|
3759
|
-
x: -0.368136,
|
|
3760
|
-
y: -0.179943
|
|
3761
|
-
},
|
|
3762
|
-
{
|
|
3763
|
-
x: -0.368136,
|
|
3764
|
-
y: -0.178529
|
|
3765
|
-
},
|
|
3766
|
-
{
|
|
3767
|
-
x: 0.556834,
|
|
3768
|
-
y: 0.746441
|
|
3954
|
+
x: 1.268717,
|
|
3955
|
+
y: -1.698536
|
|
3769
3956
|
},
|
|
3770
3957
|
{
|
|
3771
|
-
x: 0.
|
|
3772
|
-
y: 0.
|
|
3958
|
+
x: 0.369558,
|
|
3959
|
+
y: -0.799377
|
|
3773
3960
|
},
|
|
3774
3961
|
{
|
|
3775
|
-
x:
|
|
3776
|
-
y:
|
|
3962
|
+
x: -1.115754,
|
|
3963
|
+
y: -0.799377
|
|
3777
3964
|
}
|
|
3778
3965
|
]
|
|
3779
3966
|
},
|
|
3780
3967
|
{
|
|
3781
3968
|
routeId: "Net5:route_1",
|
|
3782
|
-
fromPort: "
|
|
3969
|
+
fromPort: "a3797f13-73f9-48c3-a448-bea3980cdd65",
|
|
3783
3970
|
toPort: "b0c56bb1-ea80-4154-9bb7-cb0fd760de8f",
|
|
3784
3971
|
layer: "bottom",
|
|
3785
3972
|
segments: [
|
|
3786
3973
|
{
|
|
3787
|
-
x: 1.
|
|
3788
|
-
y: -
|
|
3789
|
-
},
|
|
3790
|
-
{
|
|
3791
|
-
x: 1.405649,
|
|
3792
|
-
y: -1.561604
|
|
3793
|
-
},
|
|
3794
|
-
{
|
|
3795
|
-
x: 1.393076,
|
|
3796
|
-
y: -1.561604
|
|
3797
|
-
},
|
|
3798
|
-
{
|
|
3799
|
-
x: 1.055664,
|
|
3800
|
-
y: -1.224192
|
|
3801
|
-
},
|
|
3802
|
-
{
|
|
3803
|
-
x: 1.055664,
|
|
3804
|
-
y: -0.905992
|
|
3805
|
-
},
|
|
3806
|
-
{
|
|
3807
|
-
x: 0.556834,
|
|
3808
|
-
y: -0.407162
|
|
3809
|
-
},
|
|
3810
|
-
{
|
|
3811
|
-
x: 0.556834,
|
|
3812
|
-
y: 0.746441
|
|
3974
|
+
x: -1.115754,
|
|
3975
|
+
y: -0.799377
|
|
3813
3976
|
},
|
|
3814
3977
|
{
|
|
3815
3978
|
x: 0.397934,
|
|
3816
|
-
y: 0.
|
|
3979
|
+
y: 0.714311
|
|
3817
3980
|
},
|
|
3818
3981
|
{
|
|
3819
3982
|
x: 0.397934,
|
|
@@ -3871,28 +4034,16 @@ var via_tile_default = {
|
|
|
3871
4034
|
x: 0.159346,
|
|
3872
4035
|
y: 2.034567
|
|
3873
4036
|
},
|
|
3874
|
-
{
|
|
3875
|
-
x: 0.06239,
|
|
3876
|
-
y: 1.93761
|
|
3877
|
-
},
|
|
3878
4037
|
{
|
|
3879
4038
|
x: -0.178066,
|
|
3880
4039
|
y: 1.697155
|
|
3881
4040
|
},
|
|
3882
4041
|
{
|
|
3883
4042
|
x: -0.178066,
|
|
3884
|
-
y: 0.
|
|
4043
|
+
y: 0.981123
|
|
3885
4044
|
},
|
|
3886
4045
|
{
|
|
3887
|
-
x: -1.
|
|
3888
|
-
y: -0.040093
|
|
3889
|
-
},
|
|
3890
|
-
{
|
|
3891
|
-
x: -1.169705,
|
|
3892
|
-
y: -0.040093
|
|
3893
|
-
},
|
|
3894
|
-
{
|
|
3895
|
-
x: -1.209798,
|
|
4046
|
+
x: -1.159189,
|
|
3896
4047
|
y: 0
|
|
3897
4048
|
},
|
|
3898
4049
|
{
|
|
@@ -3912,69 +4063,25 @@ var via_tile_default = {
|
|
|
3912
4063
|
y: -0.164505
|
|
3913
4064
|
},
|
|
3914
4065
|
{
|
|
3915
|
-
x: 1.
|
|
3916
|
-
y: -0.
|
|
3917
|
-
},
|
|
3918
|
-
{
|
|
3919
|
-
x: 1.634312,
|
|
3920
|
-
y: -0.173664
|
|
3921
|
-
},
|
|
3922
|
-
{
|
|
3923
|
-
x: 2.207664,
|
|
3924
|
-
y: -0.747016
|
|
3925
|
-
},
|
|
3926
|
-
{
|
|
3927
|
-
x: 2.207664,
|
|
3928
|
-
y: -1.224192
|
|
3929
|
-
},
|
|
3930
|
-
{
|
|
3931
|
-
x: 1.870252,
|
|
3932
|
-
y: -1.561604
|
|
4066
|
+
x: 1.565592,
|
|
4067
|
+
y: -0.164505
|
|
3933
4068
|
},
|
|
3934
4069
|
{
|
|
3935
|
-
x:
|
|
3936
|
-
y: -
|
|
4070
|
+
x: 2.181408,
|
|
4071
|
+
y: -0.780321
|
|
3937
4072
|
},
|
|
3938
4073
|
{
|
|
3939
|
-
x:
|
|
3940
|
-
y: -1.
|
|
4074
|
+
x: 2.181408,
|
|
4075
|
+
y: -1.600433
|
|
3941
4076
|
},
|
|
3942
4077
|
{
|
|
3943
4078
|
x: 1.507305,
|
|
3944
4079
|
y: -2.274536
|
|
3945
4080
|
},
|
|
3946
4081
|
{
|
|
3947
|
-
x:
|
|
4082
|
+
x: 0.630282,
|
|
3948
4083
|
y: -2.274536
|
|
3949
4084
|
},
|
|
3950
|
-
{
|
|
3951
|
-
x: 0.692717,
|
|
3952
|
-
y: -1.937124
|
|
3953
|
-
},
|
|
3954
|
-
{
|
|
3955
|
-
x: 0.692717,
|
|
3956
|
-
y: -1.739534
|
|
3957
|
-
},
|
|
3958
|
-
{
|
|
3959
|
-
x: 0.475553,
|
|
3960
|
-
y: -1.739534
|
|
3961
|
-
},
|
|
3962
|
-
{
|
|
3963
|
-
x: 0.470734,
|
|
3964
|
-
y: -1.744353
|
|
3965
|
-
},
|
|
3966
|
-
{
|
|
3967
|
-
x: 0.455647,
|
|
3968
|
-
y: -1.744353
|
|
3969
|
-
},
|
|
3970
|
-
{
|
|
3971
|
-
x: 0.30246,
|
|
3972
|
-
y: -1.89754
|
|
3973
|
-
},
|
|
3974
|
-
{
|
|
3975
|
-
x: 0.253286,
|
|
3976
|
-
y: -1.89754
|
|
3977
|
-
},
|
|
3978
4085
|
{
|
|
3979
4086
|
x: 0.222457,
|
|
3980
4087
|
y: -1.866711
|
|
@@ -3992,23 +4099,15 @@ var via_tile_default = {
|
|
|
3992
4099
|
y: -1.866711
|
|
3993
4100
|
},
|
|
3994
4101
|
{
|
|
3995
|
-
x: 0.
|
|
4102
|
+
x: 0.137528,
|
|
3996
4103
|
y: -1.866711
|
|
3997
4104
|
},
|
|
3998
4105
|
{
|
|
3999
|
-
x: 0.
|
|
4000
|
-
y: -1.
|
|
4001
|
-
},
|
|
4002
|
-
{
|
|
4003
|
-
x: 0.2,
|
|
4004
|
-
y: -1.2
|
|
4005
|
-
},
|
|
4006
|
-
{
|
|
4007
|
-
x: -0.701789,
|
|
4008
|
-
y: -1.2
|
|
4106
|
+
x: -0.329183,
|
|
4107
|
+
y: -1.4
|
|
4009
4108
|
},
|
|
4010
4109
|
{
|
|
4011
|
-
x: -0.
|
|
4110
|
+
x: -0.361788,
|
|
4012
4111
|
y: -1.367395
|
|
4013
4112
|
},
|
|
4014
4113
|
{
|
|
@@ -4016,28 +4115,16 @@ var via_tile_default = {
|
|
|
4016
4115
|
y: -1.367395
|
|
4017
4116
|
},
|
|
4018
4117
|
{
|
|
4019
|
-
x: -1.
|
|
4020
|
-
y: -1.
|
|
4118
|
+
x: -1.237643,
|
|
4119
|
+
y: -1.587161
|
|
4021
4120
|
},
|
|
4022
4121
|
{
|
|
4023
|
-
x: -1.
|
|
4024
|
-
y: -1.
|
|
4025
|
-
},
|
|
4026
|
-
{
|
|
4027
|
-
x: -1.529719,
|
|
4028
|
-
y: -1.2
|
|
4029
|
-
},
|
|
4030
|
-
{
|
|
4031
|
-
x: -1.636994,
|
|
4032
|
-
y: -1.2
|
|
4033
|
-
},
|
|
4034
|
-
{
|
|
4035
|
-
x: -1.718497,
|
|
4036
|
-
y: -1.118497
|
|
4122
|
+
x: -1.587161,
|
|
4123
|
+
y: -1.587161
|
|
4037
4124
|
},
|
|
4038
4125
|
{
|
|
4039
4126
|
x: -1.8,
|
|
4040
|
-
y: -1.
|
|
4127
|
+
y: -1.374322
|
|
4041
4128
|
},
|
|
4042
4129
|
{
|
|
4043
4130
|
x: -1.8,
|
|
@@ -4049,19 +4136,11 @@ var via_tile_default = {
|
|
|
4049
4136
|
},
|
|
4050
4137
|
{
|
|
4051
4138
|
x: -1.80003,
|
|
4052
|
-
y: -0.
|
|
4139
|
+
y: -0.6
|
|
4053
4140
|
},
|
|
4054
4141
|
{
|
|
4055
|
-
x: -1.
|
|
4056
|
-
y:
|
|
4057
|
-
},
|
|
4058
|
-
{
|
|
4059
|
-
x: -1.8,
|
|
4060
|
-
y: 0.877363
|
|
4061
|
-
},
|
|
4062
|
-
{
|
|
4063
|
-
x: -1.614161,
|
|
4064
|
-
y: 1.063202
|
|
4142
|
+
x: -1.80003,
|
|
4143
|
+
y: 0.488423
|
|
4065
4144
|
},
|
|
4066
4145
|
{
|
|
4067
4146
|
x: -1.419706,
|
|
@@ -4976,64 +5055,28 @@ function generateDefaultViaTopologyGrid(opts) {
|
|
|
4976
5055
|
}
|
|
4977
5056
|
|
|
4978
5057
|
// lib/ViaGraphSolver/polygonPerimeterUtils.ts
|
|
4979
|
-
function polygonPerimeterT(p, polygon2) {
|
|
4980
|
-
const n = polygon2.length;
|
|
4981
|
-
let bestDist = Infinity;
|
|
4982
|
-
let bestEdgeIndex = 0;
|
|
4983
|
-
let bestT = 0;
|
|
4984
|
-
for (let i = 0; i < n; i++) {
|
|
4985
|
-
const a2 = polygon2[i];
|
|
4986
|
-
const b2 = polygon2[(i + 1) % n];
|
|
4987
|
-
const dx = b2.x - a2.x;
|
|
4988
|
-
const dy = b2.y - a2.y;
|
|
4989
|
-
const lenSq = dx * dx + dy * dy;
|
|
4990
|
-
if (lenSq < 1e-10) continue;
|
|
4991
|
-
const t = Math.max(
|
|
4992
|
-
0,
|
|
4993
|
-
Math.min(1, ((p.x - a2.x) * dx + (p.y - a2.y) * dy) / lenSq)
|
|
4994
|
-
);
|
|
4995
|
-
const projX = a2.x + t * dx;
|
|
4996
|
-
const projY = a2.y + t * dy;
|
|
4997
|
-
const dist = Math.sqrt((p.x - projX) ** 2 + (p.y - projY) ** 2);
|
|
4998
|
-
if (dist < bestDist) {
|
|
4999
|
-
bestDist = dist;
|
|
5000
|
-
bestEdgeIndex = i;
|
|
5001
|
-
bestT = t;
|
|
5002
|
-
}
|
|
5003
|
-
}
|
|
5004
|
-
let cumulative = 0;
|
|
5005
|
-
for (let i = 0; i < bestEdgeIndex; i++) {
|
|
5006
|
-
const a2 = polygon2[i];
|
|
5007
|
-
const b2 = polygon2[(i + 1) % n];
|
|
5008
|
-
cumulative += Math.sqrt((b2.x - a2.x) ** 2 + (b2.y - a2.y) ** 2);
|
|
5009
|
-
}
|
|
5010
|
-
const a = polygon2[bestEdgeIndex];
|
|
5011
|
-
const b = polygon2[(bestEdgeIndex + 1) % n];
|
|
5012
|
-
const edgeLen = Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2);
|
|
5013
|
-
cumulative += bestT * edgeLen;
|
|
5014
|
-
return cumulative;
|
|
5015
|
-
}
|
|
5016
5058
|
function computeDifferentNetCrossingsForPolygon(region, port1, port2) {
|
|
5017
5059
|
const polygon2 = region.d.polygon;
|
|
5018
5060
|
if (!polygon2 || polygon2.length < 3) {
|
|
5019
5061
|
return 0;
|
|
5020
5062
|
}
|
|
5021
|
-
const
|
|
5022
|
-
const
|
|
5063
|
+
const perimeter = getRegionPerimeter(region);
|
|
5064
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
5065
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
5023
5066
|
const newChord = [t1, t2];
|
|
5024
5067
|
let crossings = 0;
|
|
5025
5068
|
const assignments = region.assignments ?? [];
|
|
5026
5069
|
for (const assignment of assignments) {
|
|
5027
|
-
const existingT1 =
|
|
5028
|
-
assignment.regionPort1
|
|
5029
|
-
|
|
5070
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
5071
|
+
assignment.regionPort1,
|
|
5072
|
+
region
|
|
5030
5073
|
);
|
|
5031
|
-
const existingT2 =
|
|
5032
|
-
assignment.regionPort2
|
|
5033
|
-
|
|
5074
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
5075
|
+
assignment.regionPort2,
|
|
5076
|
+
region
|
|
5034
5077
|
);
|
|
5035
5078
|
const existingChord = [existingT1, existingT2];
|
|
5036
|
-
if (chordsCross(newChord, existingChord)) {
|
|
5079
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
5037
5080
|
crossings++;
|
|
5038
5081
|
}
|
|
5039
5082
|
}
|
|
@@ -5044,28 +5087,170 @@ function computeCrossingAssignmentsForPolygon(region, port1, port2) {
|
|
|
5044
5087
|
if (!polygon2 || polygon2.length < 3) {
|
|
5045
5088
|
return [];
|
|
5046
5089
|
}
|
|
5047
|
-
const
|
|
5048
|
-
const
|
|
5090
|
+
const perimeter = getRegionPerimeter(region);
|
|
5091
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
5092
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
5049
5093
|
const newChord = [t1, t2];
|
|
5050
5094
|
const crossingAssignments = [];
|
|
5051
5095
|
const assignments = region.assignments ?? [];
|
|
5052
5096
|
for (const assignment of assignments) {
|
|
5053
|
-
const existingT1 =
|
|
5054
|
-
assignment.regionPort1
|
|
5055
|
-
|
|
5097
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
5098
|
+
assignment.regionPort1,
|
|
5099
|
+
region
|
|
5056
5100
|
);
|
|
5057
|
-
const existingT2 =
|
|
5058
|
-
assignment.regionPort2
|
|
5059
|
-
|
|
5101
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
5102
|
+
assignment.regionPort2,
|
|
5103
|
+
region
|
|
5060
5104
|
);
|
|
5061
5105
|
const existingChord = [existingT1, existingT2];
|
|
5062
|
-
if (chordsCross(newChord, existingChord)) {
|
|
5106
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
5063
5107
|
crossingAssignments.push(assignment);
|
|
5064
5108
|
}
|
|
5065
5109
|
}
|
|
5066
5110
|
return crossingAssignments;
|
|
5067
5111
|
}
|
|
5068
5112
|
|
|
5113
|
+
// lib/ViaGraphSolver/resolveSolvedRoutePoints.ts
|
|
5114
|
+
var POINT_EPSILON = 1e-6;
|
|
5115
|
+
function arePointsEqual(a, b) {
|
|
5116
|
+
return Math.abs(a.x - b.x) <= POINT_EPSILON && Math.abs(a.y - b.y) <= POINT_EPSILON;
|
|
5117
|
+
}
|
|
5118
|
+
function appendPoint(points, point2) {
|
|
5119
|
+
const lastPoint = points[points.length - 1];
|
|
5120
|
+
if (lastPoint && arePointsEqual(lastPoint, point2)) return;
|
|
5121
|
+
points.push(point2);
|
|
5122
|
+
}
|
|
5123
|
+
function findNearestVia(vias, point2) {
|
|
5124
|
+
let bestVia = null;
|
|
5125
|
+
let bestDistance = Infinity;
|
|
5126
|
+
for (const via of vias) {
|
|
5127
|
+
const dx = via.position.x - point2.x;
|
|
5128
|
+
const dy = via.position.y - point2.y;
|
|
5129
|
+
const distance3 = Math.hypot(dx, dy);
|
|
5130
|
+
if (distance3 < bestDistance) {
|
|
5131
|
+
bestDistance = distance3;
|
|
5132
|
+
bestVia = via;
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
return bestVia;
|
|
5136
|
+
}
|
|
5137
|
+
function parseViaRegionNetName(regionId) {
|
|
5138
|
+
const marker = ":v:";
|
|
5139
|
+
const markerIndex = regionId.lastIndexOf(marker);
|
|
5140
|
+
if (markerIndex !== -1) {
|
|
5141
|
+
return regionId.slice(markerIndex + marker.length);
|
|
5142
|
+
}
|
|
5143
|
+
const lastColonIndex = regionId.lastIndexOf(":");
|
|
5144
|
+
if (lastColonIndex === -1) return regionId;
|
|
5145
|
+
return regionId.slice(lastColonIndex + 1);
|
|
5146
|
+
}
|
|
5147
|
+
function parseViaRegionTilePrefix(regionId) {
|
|
5148
|
+
const marker = ":v:";
|
|
5149
|
+
const markerIndex = regionId.lastIndexOf(marker);
|
|
5150
|
+
if (markerIndex <= 0) return null;
|
|
5151
|
+
return regionId.slice(0, markerIndex);
|
|
5152
|
+
}
|
|
5153
|
+
function getBottomRouteSegmentsForVias(viaTile, vias) {
|
|
5154
|
+
const viaIdSet = new Set(vias.map((via) => via.viaId));
|
|
5155
|
+
return viaTile.routeSegments.filter(
|
|
5156
|
+
(routeSegment) => routeSegment.layer === "bottom" && viaIdSet.has(routeSegment.fromPort) && viaIdSet.has(routeSegment.toPort) && routeSegment.segments.length >= 2
|
|
5157
|
+
);
|
|
5158
|
+
}
|
|
5159
|
+
function selectViasForTraversedRegion(viaTile, viaRegion) {
|
|
5160
|
+
const netName = parseViaRegionNetName(viaRegion.regionId);
|
|
5161
|
+
if (!netName) return [];
|
|
5162
|
+
const viasForNet = viaTile.viasByNet[netName];
|
|
5163
|
+
if (!viasForNet || viasForNet.length === 0) return [];
|
|
5164
|
+
const tilePrefix = parseViaRegionTilePrefix(viaRegion.regionId);
|
|
5165
|
+
if (!tilePrefix) return viasForNet;
|
|
5166
|
+
const tileScopedVias = viasForNet.filter(
|
|
5167
|
+
(via) => via.viaId.startsWith(`${tilePrefix}:`)
|
|
5168
|
+
);
|
|
5169
|
+
return tileScopedVias.length > 0 ? tileScopedVias : viasForNet;
|
|
5170
|
+
}
|
|
5171
|
+
function normalizeSegmentPoints(points) {
|
|
5172
|
+
const normalized = [];
|
|
5173
|
+
for (const point2 of points) appendPoint(normalized, point2);
|
|
5174
|
+
return normalized;
|
|
5175
|
+
}
|
|
5176
|
+
function appendLineSegment(lineSegments, points, layer) {
|
|
5177
|
+
const normalized = normalizeSegmentPoints(points);
|
|
5178
|
+
if (normalized.length < 2) return;
|
|
5179
|
+
const lastLine = lineSegments[lineSegments.length - 1];
|
|
5180
|
+
if (!lastLine || lastLine.layer !== layer) {
|
|
5181
|
+
lineSegments.push({ points: normalized, layer });
|
|
5182
|
+
return;
|
|
5183
|
+
}
|
|
5184
|
+
const lastPoint = lastLine.points[lastLine.points.length - 1];
|
|
5185
|
+
const firstPoint = normalized[0];
|
|
5186
|
+
if (!lastPoint || !firstPoint || !arePointsEqual(lastPoint, firstPoint)) {
|
|
5187
|
+
lineSegments.push({ points: normalized, layer });
|
|
5188
|
+
return;
|
|
5189
|
+
}
|
|
5190
|
+
const continuation = normalized.slice(1);
|
|
5191
|
+
for (const point2 of continuation) {
|
|
5192
|
+
appendPoint(lastLine.points, point2);
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
function flattenLineSegments(lineSegments) {
|
|
5196
|
+
const points = [];
|
|
5197
|
+
for (const lineSegment of lineSegments) {
|
|
5198
|
+
for (const point2 of lineSegment.points) {
|
|
5199
|
+
appendPoint(points, point2);
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
return points;
|
|
5203
|
+
}
|
|
5204
|
+
function resolveSolvedRouteLineSegments(solvedRoute, viaTile) {
|
|
5205
|
+
if (solvedRoute.path.length === 0) return [];
|
|
5206
|
+
const path = solvedRoute.path;
|
|
5207
|
+
const lineSegments = [];
|
|
5208
|
+
const drawnViaRegionIds = /* @__PURE__ */ new Set();
|
|
5209
|
+
for (let index = 1; index < path.length; index++) {
|
|
5210
|
+
const previousCandidate = path[index - 1];
|
|
5211
|
+
const currentCandidate = path[index];
|
|
5212
|
+
const previousPoint = {
|
|
5213
|
+
x: previousCandidate.port.d.x,
|
|
5214
|
+
y: previousCandidate.port.d.y
|
|
5215
|
+
};
|
|
5216
|
+
const currentPoint = {
|
|
5217
|
+
x: currentCandidate.port.d.x,
|
|
5218
|
+
y: currentCandidate.port.d.y
|
|
5219
|
+
};
|
|
5220
|
+
const traversedRegion = currentCandidate.lastRegion;
|
|
5221
|
+
const isViaRegionTraversal = !!viaTile && !!traversedRegion?.d?.isViaRegion;
|
|
5222
|
+
if (!isViaRegionTraversal) {
|
|
5223
|
+
appendLineSegment(lineSegments, [previousPoint, currentPoint], "top");
|
|
5224
|
+
continue;
|
|
5225
|
+
}
|
|
5226
|
+
const viasForRegion = selectViasForTraversedRegion(viaTile, traversedRegion);
|
|
5227
|
+
if (viasForRegion.length === 0) continue;
|
|
5228
|
+
const entryVia = findNearestVia(viasForRegion, previousPoint);
|
|
5229
|
+
const exitVia = findNearestVia(viasForRegion, currentPoint);
|
|
5230
|
+
if (entryVia) {
|
|
5231
|
+
appendLineSegment(lineSegments, [previousPoint, entryVia.position], "top");
|
|
5232
|
+
}
|
|
5233
|
+
const bottomRouteSegments = getBottomRouteSegmentsForVias(
|
|
5234
|
+
viaTile,
|
|
5235
|
+
viasForRegion
|
|
5236
|
+
);
|
|
5237
|
+
if (bottomRouteSegments.length > 0 && !drawnViaRegionIds.has(traversedRegion.regionId)) {
|
|
5238
|
+
drawnViaRegionIds.add(traversedRegion.regionId);
|
|
5239
|
+
for (const routeSegment of bottomRouteSegments) {
|
|
5240
|
+
appendLineSegment(lineSegments, routeSegment.segments, "bottom");
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
if (exitVia) {
|
|
5244
|
+
appendLineSegment(lineSegments, [exitVia.position, currentPoint], "top");
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
return lineSegments;
|
|
5248
|
+
}
|
|
5249
|
+
function resolveSolvedRoutePoints(solvedRoute, viaTile) {
|
|
5250
|
+
const lineSegments = resolveSolvedRouteLineSegments(solvedRoute, viaTile);
|
|
5251
|
+
return flattenLineSegments(lineSegments);
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5069
5254
|
// lib/ViaGraphSolver/visualizeViaGraphSolver.ts
|
|
5070
5255
|
var getConnectionColor2 = (connectionId, alpha = 0.8) => {
|
|
5071
5256
|
let hash = 0;
|
|
@@ -5093,6 +5278,8 @@ var NET_COLOR_PALETTE = [
|
|
|
5093
5278
|
"rgba(230, 126, 34, 0.35)"
|
|
5094
5279
|
// dark orange
|
|
5095
5280
|
];
|
|
5281
|
+
var BOTTOM_LAYER_TRACE_COLOR = "rgba(52, 152, 219, 0.95)";
|
|
5282
|
+
var BOTTOM_LAYER_TRACE_DASH = "3 2";
|
|
5096
5283
|
var visualizeViaGraphSolver = (solver) => {
|
|
5097
5284
|
const graph = {
|
|
5098
5285
|
regions: solver.graph.regions,
|
|
@@ -5106,6 +5293,12 @@ var visualizeViaGraphSolver = (solver) => {
|
|
|
5106
5293
|
hidePortPoints: true
|
|
5107
5294
|
} : {}
|
|
5108
5295
|
});
|
|
5296
|
+
if (solver.iterations === 0) {
|
|
5297
|
+
for (const polygon2 of graphics.polygons) {
|
|
5298
|
+
polygon2.stroke = "rgba(128, 128, 128, 0.5)";
|
|
5299
|
+
polygon2.strokeWidth = 0.03;
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5109
5302
|
const outerIds = /* @__PURE__ */ new Set(["T", "B", "L", "R"]);
|
|
5110
5303
|
let netColorIndex = 0;
|
|
5111
5304
|
const netColorMap = /* @__PURE__ */ new Map();
|
|
@@ -5166,15 +5359,13 @@ var visualizeViaGraphSolver = (solver) => {
|
|
|
5166
5359
|
const connectionColor = getConnectionColor2(
|
|
5167
5360
|
solvedRoute.connection.connectionId
|
|
5168
5361
|
);
|
|
5169
|
-
const
|
|
5170
|
-
for (const
|
|
5171
|
-
const
|
|
5172
|
-
pathPoints.push({ x: port.d.x, y: port.d.y });
|
|
5173
|
-
}
|
|
5174
|
-
if (pathPoints.length > 0) {
|
|
5362
|
+
const lineSegments = solver.getSolvedRouteLineSegments(solvedRoute);
|
|
5363
|
+
for (const lineSegment of lineSegments) {
|
|
5364
|
+
const isBottomLayer = lineSegment.layer === "bottom";
|
|
5175
5365
|
graphics.lines.push({
|
|
5176
|
-
points:
|
|
5177
|
-
strokeColor: connectionColor
|
|
5366
|
+
points: lineSegment.points,
|
|
5367
|
+
strokeColor: isBottomLayer ? BOTTOM_LAYER_TRACE_COLOR : connectionColor,
|
|
5368
|
+
...isBottomLayer ? { strokeDash: BOTTOM_LAYER_TRACE_DASH } : {}
|
|
5178
5369
|
});
|
|
5179
5370
|
}
|
|
5180
5371
|
}
|
|
@@ -5346,6 +5537,12 @@ var ViaGraphSolver = class extends HyperGraphSolver {
|
|
|
5346
5537
|
}
|
|
5347
5538
|
routeSolvedHook(solvedRoute) {
|
|
5348
5539
|
}
|
|
5540
|
+
getSolvedRoutePoints(solvedRoute) {
|
|
5541
|
+
return resolveSolvedRoutePoints(solvedRoute, this.viaTile);
|
|
5542
|
+
}
|
|
5543
|
+
getSolvedRouteLineSegments(solvedRoute) {
|
|
5544
|
+
return resolveSolvedRouteLineSegments(solvedRoute, this.viaTile);
|
|
5545
|
+
}
|
|
5349
5546
|
routeStartedHook(connection) {
|
|
5350
5547
|
}
|
|
5351
5548
|
visualize() {
|
|
@@ -14646,6 +14843,35 @@ function centroid(points) {
|
|
|
14646
14843
|
}
|
|
14647
14844
|
return { x: cx / points.length, y: cy / points.length };
|
|
14648
14845
|
}
|
|
14846
|
+
function classifySideFromBounds(point2, bounds) {
|
|
14847
|
+
const distances = {
|
|
14848
|
+
left: Math.abs(point2.x - bounds.minX),
|
|
14849
|
+
right: Math.abs(point2.x - bounds.maxX),
|
|
14850
|
+
bottom: Math.abs(point2.y - bounds.minY),
|
|
14851
|
+
top: Math.abs(point2.y - bounds.maxY)
|
|
14852
|
+
};
|
|
14853
|
+
let bestSide = "left";
|
|
14854
|
+
let bestDistance = distances.left;
|
|
14855
|
+
for (const side of ["right", "bottom", "top"]) {
|
|
14856
|
+
if (distances[side] < bestDistance) {
|
|
14857
|
+
bestSide = side;
|
|
14858
|
+
bestDistance = distances[side];
|
|
14859
|
+
}
|
|
14860
|
+
}
|
|
14861
|
+
return bestSide;
|
|
14862
|
+
}
|
|
14863
|
+
function toCandidateKey(regionId, point2) {
|
|
14864
|
+
return `${regionId}:${point2.x.toFixed(6)},${point2.y.toFixed(6)}`;
|
|
14865
|
+
}
|
|
14866
|
+
function compareCandidateQuality(a, b) {
|
|
14867
|
+
if (Math.abs(a.primaryDistance - b.primaryDistance) > 1e-6) {
|
|
14868
|
+
return b.primaryDistance - a.primaryDistance;
|
|
14869
|
+
}
|
|
14870
|
+
if (Math.abs(a.orthDistance - b.orthDistance) > 1e-6) {
|
|
14871
|
+
return a.orthDistance - b.orthDistance;
|
|
14872
|
+
}
|
|
14873
|
+
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
14874
|
+
}
|
|
14649
14875
|
function createRegionFromPolygon(regionId, polygon2, opts) {
|
|
14650
14876
|
const bounds = boundsFromPolygon(polygon2);
|
|
14651
14877
|
return {
|
|
@@ -14834,6 +15060,8 @@ function generateConvexViaTopologyRegions(opts) {
|
|
|
14834
15060
|
}
|
|
14835
15061
|
}
|
|
14836
15062
|
for (const viaRegion of viaRegions) {
|
|
15063
|
+
const viaCenter = viaRegion.d.center;
|
|
15064
|
+
const candidates = [];
|
|
14837
15065
|
for (const convexRegion of convexRegions) {
|
|
14838
15066
|
const sharedEdges = findSharedEdges(
|
|
14839
15067
|
viaRegion.d.polygon,
|
|
@@ -14843,18 +15071,55 @@ function generateConvexViaTopologyRegions(opts) {
|
|
|
14843
15071
|
for (const edge of sharedEdges) {
|
|
14844
15072
|
const portPositions = createPortsAlongEdge(edge, portPitch);
|
|
14845
15073
|
for (const pos of portPositions) {
|
|
14846
|
-
const
|
|
14847
|
-
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
|
|
15074
|
+
const dx = pos.x - viaCenter.x;
|
|
15075
|
+
const dy = pos.y - viaCenter.y;
|
|
15076
|
+
const side = classifySideFromBounds(pos, viaRegion.d.bounds);
|
|
15077
|
+
const primaryDistance = side === "left" || side === "right" ? Math.abs(dx) : Math.abs(dy);
|
|
15078
|
+
const orthDistance = side === "left" || side === "right" ? Math.abs(dy) : Math.abs(dx);
|
|
15079
|
+
candidates.push({
|
|
15080
|
+
convexRegion,
|
|
15081
|
+
position: pos,
|
|
15082
|
+
side,
|
|
15083
|
+
primaryDistance,
|
|
15084
|
+
orthDistance,
|
|
15085
|
+
key: toCandidateKey(convexRegion.regionId, pos)
|
|
15086
|
+
});
|
|
14855
15087
|
}
|
|
14856
15088
|
}
|
|
14857
15089
|
}
|
|
15090
|
+
if (candidates.length === 0) continue;
|
|
15091
|
+
const selectedCandidates = [];
|
|
15092
|
+
const selectedKeys = /* @__PURE__ */ new Set();
|
|
15093
|
+
const addCandidate = (candidate) => {
|
|
15094
|
+
if (!candidate) return;
|
|
15095
|
+
if (selectedKeys.has(candidate.key)) return;
|
|
15096
|
+
selectedCandidates.push(candidate);
|
|
15097
|
+
selectedKeys.add(candidate.key);
|
|
15098
|
+
};
|
|
15099
|
+
for (const side of ["top", "bottom", "left", "right"]) {
|
|
15100
|
+
const sideCandidate = [...candidates].filter((candidate) => candidate.side === side).sort(compareCandidateQuality)[0];
|
|
15101
|
+
addCandidate(sideCandidate);
|
|
15102
|
+
}
|
|
15103
|
+
if (selectedCandidates.length < 4) {
|
|
15104
|
+
for (const candidate of [...candidates].sort(compareCandidateQuality)) {
|
|
15105
|
+
addCandidate(candidate);
|
|
15106
|
+
if (selectedCandidates.length >= 4) break;
|
|
15107
|
+
}
|
|
15108
|
+
}
|
|
15109
|
+
for (const selectedCandidate of selectedCandidates.slice(0, 4)) {
|
|
15110
|
+
const port = {
|
|
15111
|
+
portId: `via-convex:${viaRegion.regionId}-${selectedCandidate.convexRegion.regionId}:${portIdCounter++}`,
|
|
15112
|
+
region1: viaRegion,
|
|
15113
|
+
region2: selectedCandidate.convexRegion,
|
|
15114
|
+
d: {
|
|
15115
|
+
x: selectedCandidate.position.x,
|
|
15116
|
+
y: selectedCandidate.position.y
|
|
15117
|
+
}
|
|
15118
|
+
};
|
|
15119
|
+
viaRegion.ports.push(port);
|
|
15120
|
+
selectedCandidate.convexRegion.ports.push(port);
|
|
15121
|
+
allPorts.push(port);
|
|
15122
|
+
}
|
|
14858
15123
|
}
|
|
14859
15124
|
return {
|
|
14860
15125
|
regions: allRegions,
|