@tscircuit/hypergraph 0.0.34 → 0.0.35
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 +15 -0
- package/dist/index.js +352 -138
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -248,6 +248,13 @@ declare class HyperGraphSolver<RegionType extends Region = Region, RegionPortTyp
|
|
|
248
248
|
* when there is no direct port-assignment conflict.
|
|
249
249
|
*/
|
|
250
250
|
isRipRequiredForPortUsage(_region: RegionType, _port1: RegionPortType, _port2: RegionPortType): boolean;
|
|
251
|
+
/**
|
|
252
|
+
* OPTIONALLY OVERRIDE THIS
|
|
253
|
+
*
|
|
254
|
+
* Return false to prevent transitioning through a region from `_port1` to
|
|
255
|
+
* `_port2`.
|
|
256
|
+
*/
|
|
257
|
+
isTransitionAllowed(_region: RegionType, _port1: RegionPortType, _port2: RegionPortType): boolean;
|
|
251
258
|
computeG(candidate: CandidateType): number;
|
|
252
259
|
/**
|
|
253
260
|
* Return a subset of the candidates for entering a region. These candidates
|
|
@@ -317,6 +324,11 @@ interface JRegion extends Region {
|
|
|
317
324
|
x: number;
|
|
318
325
|
y: number;
|
|
319
326
|
}[];
|
|
327
|
+
polygonPerimeterCache?: {
|
|
328
|
+
edgeLengths: number[];
|
|
329
|
+
cumulative: number[];
|
|
330
|
+
perimeter: number;
|
|
331
|
+
};
|
|
320
332
|
isPad: boolean;
|
|
321
333
|
isThroughJumper?: boolean;
|
|
322
334
|
isConnectionRegion?: boolean;
|
|
@@ -324,6 +336,8 @@ interface JRegion extends Region {
|
|
|
324
336
|
};
|
|
325
337
|
}
|
|
326
338
|
interface JPort extends RegionPort {
|
|
339
|
+
region1T?: number;
|
|
340
|
+
region2T?: number;
|
|
327
341
|
d: {
|
|
328
342
|
x: number;
|
|
329
343
|
y: number;
|
|
@@ -383,6 +397,7 @@ declare class JumperGraphSolver extends HyperGraphSolver<JRegion, JPort> {
|
|
|
383
397
|
private populateDistanceToEndMaps;
|
|
384
398
|
estimateCostToEnd(port: JPort): number;
|
|
385
399
|
getPortUsagePenalty(port: JPort): number;
|
|
400
|
+
isTransitionAllowed(region: JRegion, port1: JPort, port2: JPort): boolean;
|
|
386
401
|
computeIncreasedRegionCostIfPortsAreUsed(region: JRegion, port1: JPort, port2: JPort): number;
|
|
387
402
|
getRipsRequiredForPortUsage(region: JRegion, port1: JPort, port2: JPort): RegionPortAssignment[];
|
|
388
403
|
isRipRequiredForPortUsage(region: JRegion, _port1: JPort, _port2: JPort): boolean;
|
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) {
|
|
@@ -4976,64 +5219,28 @@ function generateDefaultViaTopologyGrid(opts) {
|
|
|
4976
5219
|
}
|
|
4977
5220
|
|
|
4978
5221
|
// 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
5222
|
function computeDifferentNetCrossingsForPolygon(region, port1, port2) {
|
|
5017
5223
|
const polygon2 = region.d.polygon;
|
|
5018
5224
|
if (!polygon2 || polygon2.length < 3) {
|
|
5019
5225
|
return 0;
|
|
5020
5226
|
}
|
|
5021
|
-
const
|
|
5022
|
-
const
|
|
5227
|
+
const perimeter = getRegionPerimeter(region);
|
|
5228
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
5229
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
5023
5230
|
const newChord = [t1, t2];
|
|
5024
5231
|
let crossings = 0;
|
|
5025
5232
|
const assignments = region.assignments ?? [];
|
|
5026
5233
|
for (const assignment of assignments) {
|
|
5027
|
-
const existingT1 =
|
|
5028
|
-
assignment.regionPort1
|
|
5029
|
-
|
|
5234
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
5235
|
+
assignment.regionPort1,
|
|
5236
|
+
region
|
|
5030
5237
|
);
|
|
5031
|
-
const existingT2 =
|
|
5032
|
-
assignment.regionPort2
|
|
5033
|
-
|
|
5238
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
5239
|
+
assignment.regionPort2,
|
|
5240
|
+
region
|
|
5034
5241
|
);
|
|
5035
5242
|
const existingChord = [existingT1, existingT2];
|
|
5036
|
-
if (chordsCross(newChord, existingChord)) {
|
|
5243
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
5037
5244
|
crossings++;
|
|
5038
5245
|
}
|
|
5039
5246
|
}
|
|
@@ -5044,22 +5251,23 @@ function computeCrossingAssignmentsForPolygon(region, port1, port2) {
|
|
|
5044
5251
|
if (!polygon2 || polygon2.length < 3) {
|
|
5045
5252
|
return [];
|
|
5046
5253
|
}
|
|
5047
|
-
const
|
|
5048
|
-
const
|
|
5254
|
+
const perimeter = getRegionPerimeter(region);
|
|
5255
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
5256
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
5049
5257
|
const newChord = [t1, t2];
|
|
5050
5258
|
const crossingAssignments = [];
|
|
5051
5259
|
const assignments = region.assignments ?? [];
|
|
5052
5260
|
for (const assignment of assignments) {
|
|
5053
|
-
const existingT1 =
|
|
5054
|
-
assignment.regionPort1
|
|
5055
|
-
|
|
5261
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
5262
|
+
assignment.regionPort1,
|
|
5263
|
+
region
|
|
5056
5264
|
);
|
|
5057
|
-
const existingT2 =
|
|
5058
|
-
assignment.regionPort2
|
|
5059
|
-
|
|
5265
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
5266
|
+
assignment.regionPort2,
|
|
5267
|
+
region
|
|
5060
5268
|
);
|
|
5061
5269
|
const existingChord = [existingT1, existingT2];
|
|
5062
|
-
if (chordsCross(newChord, existingChord)) {
|
|
5270
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
5063
5271
|
crossingAssignments.push(assignment);
|
|
5064
5272
|
}
|
|
5065
5273
|
}
|
|
@@ -5106,6 +5314,12 @@ var visualizeViaGraphSolver = (solver) => {
|
|
|
5106
5314
|
hidePortPoints: true
|
|
5107
5315
|
} : {}
|
|
5108
5316
|
});
|
|
5317
|
+
if (solver.iterations === 0) {
|
|
5318
|
+
for (const polygon2 of graphics.polygons) {
|
|
5319
|
+
polygon2.stroke = "rgba(128, 128, 128, 0.5)";
|
|
5320
|
+
polygon2.strokeWidth = 0.03;
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5109
5323
|
const outerIds = /* @__PURE__ */ new Set(["T", "B", "L", "R"]);
|
|
5110
5324
|
let netColorIndex = 0;
|
|
5111
5325
|
const netColorMap = /* @__PURE__ */ new Map();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/hypergraph",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.35",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "cosmos",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@biomejs/biome": "^2.3.11",
|
|
19
19
|
"@tscircuit/find-convex-regions": "^0.0.7",
|
|
20
|
-
"@tscircuit/jumper-topology-generator": "^0.0.
|
|
20
|
+
"@tscircuit/jumper-topology-generator": "^0.0.2",
|
|
21
21
|
"@tscircuit/math-utils": "^0.0.29",
|
|
22
22
|
"@types/bun": "latest",
|
|
23
23
|
"bun-match-svg": "^0.0.15",
|