@tscircuit/hypergraph 0.0.33 → 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 +23 -0
- package/dist/index.js +521 -277
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -241,6 +241,20 @@ declare class HyperGraphSolver<RegionType extends Region = Region, RegionPortTyp
|
|
|
241
241
|
* a route would require ripping other routes due to problematic crossings.
|
|
242
242
|
*/
|
|
243
243
|
getRipsRequiredForPortUsage(_region: RegionType, _port1: RegionPortType, _port2: RegionPortType): RegionPortAssignment[];
|
|
244
|
+
/**
|
|
245
|
+
* OPTIONALLY OVERRIDE THIS
|
|
246
|
+
*
|
|
247
|
+
* Return true if using the candidate transition should incur ripCost even
|
|
248
|
+
* when there is no direct port-assignment conflict.
|
|
249
|
+
*/
|
|
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;
|
|
244
258
|
computeG(candidate: CandidateType): number;
|
|
245
259
|
/**
|
|
246
260
|
* Return a subset of the candidates for entering a region. These candidates
|
|
@@ -310,6 +324,11 @@ interface JRegion extends Region {
|
|
|
310
324
|
x: number;
|
|
311
325
|
y: number;
|
|
312
326
|
}[];
|
|
327
|
+
polygonPerimeterCache?: {
|
|
328
|
+
edgeLengths: number[];
|
|
329
|
+
cumulative: number[];
|
|
330
|
+
perimeter: number;
|
|
331
|
+
};
|
|
313
332
|
isPad: boolean;
|
|
314
333
|
isThroughJumper?: boolean;
|
|
315
334
|
isConnectionRegion?: boolean;
|
|
@@ -317,6 +336,8 @@ interface JRegion extends Region {
|
|
|
317
336
|
};
|
|
318
337
|
}
|
|
319
338
|
interface JPort extends RegionPort {
|
|
339
|
+
region1T?: number;
|
|
340
|
+
region2T?: number;
|
|
320
341
|
d: {
|
|
321
342
|
x: number;
|
|
322
343
|
y: number;
|
|
@@ -376,8 +397,10 @@ declare class JumperGraphSolver extends HyperGraphSolver<JRegion, JPort> {
|
|
|
376
397
|
private populateDistanceToEndMaps;
|
|
377
398
|
estimateCostToEnd(port: JPort): number;
|
|
378
399
|
getPortUsagePenalty(port: JPort): number;
|
|
400
|
+
isTransitionAllowed(region: JRegion, port1: JPort, port2: JPort): boolean;
|
|
379
401
|
computeIncreasedRegionCostIfPortsAreUsed(region: JRegion, port1: JPort, port2: JPort): number;
|
|
380
402
|
getRipsRequiredForPortUsage(region: JRegion, port1: JPort, port2: JPort): RegionPortAssignment[];
|
|
403
|
+
isRipRequiredForPortUsage(region: JRegion, _port1: JPort, _port2: JPort): boolean;
|
|
381
404
|
routeSolvedHook(solvedRoute: SolvedRoute): void;
|
|
382
405
|
routeStartedHook(connection: Connection): void;
|
|
383
406
|
visualize(): GraphicsObject;
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,28 @@ var convertHyperGraphToSerializedHyperGraph = (graph) => {
|
|
|
31
31
|
// lib/HyperGraphSolver.ts
|
|
32
32
|
import { BaseSolver } from "@tscircuit/solver-utils";
|
|
33
33
|
|
|
34
|
+
// lib/convertSerializedConnectionsToConnections.ts
|
|
35
|
+
var convertSerializedConnectionsToConnections = (inputConnections, graph) => {
|
|
36
|
+
const connections = [];
|
|
37
|
+
for (const inputConn of inputConnections) {
|
|
38
|
+
if ("startRegionId" in inputConn) {
|
|
39
|
+
connections.push({
|
|
40
|
+
connectionId: inputConn.connectionId,
|
|
41
|
+
mutuallyConnectedNetworkId: inputConn.connectionId,
|
|
42
|
+
startRegion: graph.regions.find(
|
|
43
|
+
(region) => region.regionId === inputConn.startRegionId
|
|
44
|
+
),
|
|
45
|
+
endRegion: graph.regions.find(
|
|
46
|
+
(region) => region.regionId === inputConn.endRegionId
|
|
47
|
+
)
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
connections.push(inputConn);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return connections;
|
|
54
|
+
};
|
|
55
|
+
|
|
34
56
|
// lib/convertSerializedHyperGraphToHyperGraph.ts
|
|
35
57
|
var convertSerializedHyperGraphToHyperGraph = (inputGraph) => {
|
|
36
58
|
if (inputGraph.ports.length > 0 && "region1" in inputGraph.ports[0] && typeof inputGraph.ports[0].region1 === "object") {
|
|
@@ -65,28 +87,6 @@ var convertSerializedHyperGraphToHyperGraph = (inputGraph) => {
|
|
|
65
87
|
};
|
|
66
88
|
};
|
|
67
89
|
|
|
68
|
-
// lib/convertSerializedConnectionsToConnections.ts
|
|
69
|
-
var convertSerializedConnectionsToConnections = (inputConnections, graph) => {
|
|
70
|
-
const connections = [];
|
|
71
|
-
for (const inputConn of inputConnections) {
|
|
72
|
-
if ("startRegionId" in inputConn) {
|
|
73
|
-
connections.push({
|
|
74
|
-
connectionId: inputConn.connectionId,
|
|
75
|
-
mutuallyConnectedNetworkId: inputConn.connectionId,
|
|
76
|
-
startRegion: graph.regions.find(
|
|
77
|
-
(region) => region.regionId === inputConn.startRegionId
|
|
78
|
-
),
|
|
79
|
-
endRegion: graph.regions.find(
|
|
80
|
-
(region) => region.regionId === inputConn.endRegionId
|
|
81
|
-
)
|
|
82
|
-
});
|
|
83
|
-
} else {
|
|
84
|
-
connections.push(inputConn);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return connections;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
90
|
// lib/PriorityQueue.ts
|
|
91
91
|
var PriorityQueue = class {
|
|
92
92
|
// Store the heap as an array. Index 0 is the root (highest priority/smallest 'f').
|
|
@@ -328,6 +328,24 @@ var HyperGraphSolver = class extends BaseSolver {
|
|
|
328
328
|
getRipsRequiredForPortUsage(_region, _port1, _port2) {
|
|
329
329
|
return [];
|
|
330
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* OPTIONALLY OVERRIDE THIS
|
|
333
|
+
*
|
|
334
|
+
* Return true if using the candidate transition should incur ripCost even
|
|
335
|
+
* when there is no direct port-assignment conflict.
|
|
336
|
+
*/
|
|
337
|
+
isRipRequiredForPortUsage(_region, _port1, _port2) {
|
|
338
|
+
return false;
|
|
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
|
+
}
|
|
331
349
|
computeG(candidate) {
|
|
332
350
|
return candidate.parent.g + this.computeIncreasedRegionCostIfPortsAreUsed(
|
|
333
351
|
candidate.lastRegion,
|
|
@@ -396,7 +414,18 @@ var HyperGraphSolver = class extends BaseSolver {
|
|
|
396
414
|
const nextCandidatesByRegion = {};
|
|
397
415
|
for (const port of currentRegion.ports) {
|
|
398
416
|
if (port === currentCandidate.port) continue;
|
|
399
|
-
|
|
417
|
+
if (!this.isTransitionAllowed(
|
|
418
|
+
currentRegion,
|
|
419
|
+
currentPort,
|
|
420
|
+
port
|
|
421
|
+
)) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const ripRequired = port.assignment && port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId || this.isRipRequiredForPortUsage(
|
|
425
|
+
currentRegion,
|
|
426
|
+
currentPort,
|
|
427
|
+
port
|
|
428
|
+
);
|
|
400
429
|
const newCandidate = {
|
|
401
430
|
port,
|
|
402
431
|
hops: currentCandidate.hops + 1,
|
|
@@ -758,6 +787,277 @@ var rotateGraph90Degrees = (graph) => {
|
|
|
758
787
|
return applyTransformToGraph(graph, matrix2);
|
|
759
788
|
};
|
|
760
789
|
|
|
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
|
+
}
|
|
802
|
+
function perimeterT(p, xmin, xmax, ymin, ymax) {
|
|
803
|
+
const W = xmax - xmin;
|
|
804
|
+
const H = ymax - ymin;
|
|
805
|
+
const eps = 1e-6;
|
|
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);
|
|
810
|
+
const distTop = Math.abs(p.y - ymax);
|
|
811
|
+
const distRight = Math.abs(p.x - xmax);
|
|
812
|
+
const distBottom = Math.abs(p.y - ymin);
|
|
813
|
+
const distLeft = Math.abs(p.x - xmin);
|
|
814
|
+
const minDist = Math.min(distTop, distRight, distBottom, distLeft);
|
|
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));
|
|
817
|
+
if (minDist === distBottom) {
|
|
818
|
+
return W + H + Math.max(0, Math.min(W, xmax - p.x));
|
|
819
|
+
}
|
|
820
|
+
return 2 * W + H + Math.max(0, Math.min(H, p.y - ymin));
|
|
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
|
+
}
|
|
908
|
+
function areCoincident(t1, t2, eps = 1e-6) {
|
|
909
|
+
return Math.abs(t1 - t2) < eps;
|
|
910
|
+
}
|
|
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
|
+
}
|
|
941
|
+
const [a, b] = chord1[0] < chord1[1] ? chord1 : [chord1[1], chord1[0]];
|
|
942
|
+
const [c, d] = chord2[0] < chord2[1] ? chord2 : [chord2[1], chord2[0]];
|
|
943
|
+
if (areCoincident(a, c) || areCoincident(a, d) || areCoincident(b, c) || areCoincident(b, d)) {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
return a < c && c < b && b < d || c < a && a < d && d < b;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// lib/JumperGraphSolver/computeCrossingAssignments.ts
|
|
950
|
+
function computeCrossingAssignments(region, port1, port2) {
|
|
951
|
+
const perimeter = getRegionPerimeter(region);
|
|
952
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
953
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
954
|
+
const newChord = [t1, t2];
|
|
955
|
+
const crossingAssignments = [];
|
|
956
|
+
const assignments = region.assignments ?? [];
|
|
957
|
+
for (const assignment of assignments) {
|
|
958
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
959
|
+
assignment.regionPort1,
|
|
960
|
+
region
|
|
961
|
+
);
|
|
962
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
963
|
+
assignment.regionPort2,
|
|
964
|
+
region
|
|
965
|
+
);
|
|
966
|
+
const existingChord = [existingT1, existingT2];
|
|
967
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
968
|
+
crossingAssignments.push(assignment);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return crossingAssignments;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// lib/JumperGraphSolver/computeDifferentNetCrossings.ts
|
|
975
|
+
function computeDifferentNetCrossings(region, port1, port2) {
|
|
976
|
+
const perimeter = getRegionPerimeter(region);
|
|
977
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
978
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
979
|
+
const newChord = [t1, t2];
|
|
980
|
+
let crossings = 0;
|
|
981
|
+
const assignments = region.assignments ?? [];
|
|
982
|
+
for (const assignment of assignments) {
|
|
983
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
984
|
+
assignment.regionPort1,
|
|
985
|
+
region
|
|
986
|
+
);
|
|
987
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
988
|
+
assignment.regionPort2,
|
|
989
|
+
region
|
|
990
|
+
);
|
|
991
|
+
const existingChord = [existingT1, existingT2];
|
|
992
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
993
|
+
crossings++;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return crossings;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// lib/JumperGraphSolver/countInputConnectionCrossings.ts
|
|
1000
|
+
function countInputConnectionCrossings(graph, connections) {
|
|
1001
|
+
if (connections.length < 2) {
|
|
1002
|
+
return 0;
|
|
1003
|
+
}
|
|
1004
|
+
let minX = Infinity;
|
|
1005
|
+
let maxX = -Infinity;
|
|
1006
|
+
let minY = Infinity;
|
|
1007
|
+
let maxY = -Infinity;
|
|
1008
|
+
for (const region of graph.regions) {
|
|
1009
|
+
const jRegion = region;
|
|
1010
|
+
if (jRegion.d?.bounds) {
|
|
1011
|
+
minX = Math.min(minX, jRegion.d.bounds.minX);
|
|
1012
|
+
maxX = Math.max(maxX, jRegion.d.bounds.maxX);
|
|
1013
|
+
minY = Math.min(minY, jRegion.d.bounds.minY);
|
|
1014
|
+
maxY = Math.max(maxY, jRegion.d.bounds.maxY);
|
|
1015
|
+
} else if (jRegion.d?.center) {
|
|
1016
|
+
minX = Math.min(minX, jRegion.d.center.x);
|
|
1017
|
+
maxX = Math.max(maxX, jRegion.d.center.x);
|
|
1018
|
+
minY = Math.min(minY, jRegion.d.center.y);
|
|
1019
|
+
maxY = Math.max(maxY, jRegion.d.center.y);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
const regionCenterMap = /* @__PURE__ */ new Map();
|
|
1023
|
+
for (const region of graph.regions) {
|
|
1024
|
+
const jRegion = region;
|
|
1025
|
+
if (jRegion.d?.center) {
|
|
1026
|
+
regionCenterMap.set(region.regionId, jRegion.d.center);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
const chords = [];
|
|
1030
|
+
const perimeter = 2 * (maxX - minX) + 2 * (maxY - minY);
|
|
1031
|
+
for (const conn of connections) {
|
|
1032
|
+
let startCenter;
|
|
1033
|
+
let endCenter;
|
|
1034
|
+
if ("startRegion" in conn && conn.startRegion) {
|
|
1035
|
+
const startRegion = conn.startRegion;
|
|
1036
|
+
const endRegion = conn.endRegion;
|
|
1037
|
+
startCenter = startRegion.d?.center;
|
|
1038
|
+
endCenter = endRegion.d?.center;
|
|
1039
|
+
} else if ("startRegionId" in conn) {
|
|
1040
|
+
startCenter = regionCenterMap.get(conn.startRegionId);
|
|
1041
|
+
endCenter = regionCenterMap.get(conn.endRegionId);
|
|
1042
|
+
}
|
|
1043
|
+
if (!startCenter || !endCenter) {
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
const t1 = perimeterT(startCenter, minX, maxX, minY, maxY);
|
|
1047
|
+
const t2 = perimeterT(endCenter, minX, maxX, minY, maxY);
|
|
1048
|
+
chords.push([t1, t2]);
|
|
1049
|
+
}
|
|
1050
|
+
let crossings = 0;
|
|
1051
|
+
for (let i = 0; i < chords.length; i++) {
|
|
1052
|
+
for (let j = i + 1; j < chords.length; j++) {
|
|
1053
|
+
if (chordsCross(chords[i], chords[j], perimeter)) {
|
|
1054
|
+
crossings++;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return crossings;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
761
1061
|
// lib/JumperGraphSolver/visualizeJumperGraph.ts
|
|
762
1062
|
var visualizeJumperGraph = (graph, options) => {
|
|
763
1063
|
const graphics = {
|
|
@@ -791,9 +1091,7 @@ var visualizeJumperGraph = (graph, options) => {
|
|
|
791
1091
|
const points = polygon2;
|
|
792
1092
|
graphics.polygons.push({
|
|
793
1093
|
points,
|
|
794
|
-
fill
|
|
795
|
-
stroke: "rgba(128, 128, 128, 0.5)",
|
|
796
|
-
strokeWidth: 0.03
|
|
1094
|
+
fill
|
|
797
1095
|
});
|
|
798
1096
|
} else {
|
|
799
1097
|
graphics.rects.push({
|
|
@@ -884,6 +1182,12 @@ var visualizeJumperGraphSolver = (solver) => {
|
|
|
884
1182
|
hidePortPoints: true
|
|
885
1183
|
} : {}
|
|
886
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
|
+
}
|
|
887
1191
|
if (solver.currentConnection && !solver.solved) {
|
|
888
1192
|
const connectionColor = getConnectionColor(
|
|
889
1193
|
solver.currentConnection.connectionId
|
|
@@ -971,174 +1275,6 @@ var visualizeJumperGraphSolver = (solver) => {
|
|
|
971
1275
|
return graphics;
|
|
972
1276
|
};
|
|
973
1277
|
|
|
974
|
-
// lib/JumperGraphSolver/perimeterChordUtils.ts
|
|
975
|
-
function perimeterT(p, xmin, xmax, ymin, ymax) {
|
|
976
|
-
const W = xmax - xmin;
|
|
977
|
-
const H = ymax - ymin;
|
|
978
|
-
const eps = 1e-6;
|
|
979
|
-
if (Math.abs(p.y - ymax) < eps) {
|
|
980
|
-
return p.x - xmin;
|
|
981
|
-
}
|
|
982
|
-
if (Math.abs(p.x - xmax) < eps) {
|
|
983
|
-
return W + (ymax - p.y);
|
|
984
|
-
}
|
|
985
|
-
if (Math.abs(p.y - ymin) < eps) {
|
|
986
|
-
return W + H + (xmax - p.x);
|
|
987
|
-
}
|
|
988
|
-
if (Math.abs(p.x - xmin) < eps) {
|
|
989
|
-
return 2 * W + H + (p.y - ymin);
|
|
990
|
-
}
|
|
991
|
-
const distTop = Math.abs(p.y - ymax);
|
|
992
|
-
const distRight = Math.abs(p.x - xmax);
|
|
993
|
-
const distBottom = Math.abs(p.y - ymin);
|
|
994
|
-
const distLeft = Math.abs(p.x - xmin);
|
|
995
|
-
const minDist = Math.min(distTop, distRight, distBottom, distLeft);
|
|
996
|
-
if (minDist === distTop) {
|
|
997
|
-
return Math.max(0, Math.min(W, p.x - xmin));
|
|
998
|
-
}
|
|
999
|
-
if (minDist === distRight) {
|
|
1000
|
-
return W + Math.max(0, Math.min(H, ymax - p.y));
|
|
1001
|
-
}
|
|
1002
|
-
if (minDist === distBottom) {
|
|
1003
|
-
return W + H + Math.max(0, Math.min(W, xmax - p.x));
|
|
1004
|
-
}
|
|
1005
|
-
return 2 * W + H + Math.max(0, Math.min(H, p.y - ymin));
|
|
1006
|
-
}
|
|
1007
|
-
function areCoincident(t1, t2, eps = 1e-6) {
|
|
1008
|
-
return Math.abs(t1 - t2) < eps;
|
|
1009
|
-
}
|
|
1010
|
-
function chordsCross(chord1, chord2) {
|
|
1011
|
-
const [a, b] = chord1[0] < chord1[1] ? chord1 : [chord1[1], chord1[0]];
|
|
1012
|
-
const [c, d] = chord2[0] < chord2[1] ? chord2 : [chord2[1], chord2[0]];
|
|
1013
|
-
if (areCoincident(a, c) || areCoincident(a, d) || areCoincident(b, c) || areCoincident(b, d)) {
|
|
1014
|
-
return false;
|
|
1015
|
-
}
|
|
1016
|
-
return a < c && c < b && b < d || c < a && a < d && d < b;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
// lib/JumperGraphSolver/computeDifferentNetCrossings.ts
|
|
1020
|
-
function computeDifferentNetCrossings(region, port1, port2) {
|
|
1021
|
-
const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
|
|
1022
|
-
const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
|
|
1023
|
-
const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
|
|
1024
|
-
const newChord = [t1, t2];
|
|
1025
|
-
let crossings = 0;
|
|
1026
|
-
const assignments = region.assignments ?? [];
|
|
1027
|
-
for (const assignment of assignments) {
|
|
1028
|
-
const existingT1 = perimeterT(
|
|
1029
|
-
assignment.regionPort1.d,
|
|
1030
|
-
xmin,
|
|
1031
|
-
xmax,
|
|
1032
|
-
ymin,
|
|
1033
|
-
ymax
|
|
1034
|
-
);
|
|
1035
|
-
const existingT2 = perimeterT(
|
|
1036
|
-
assignment.regionPort2.d,
|
|
1037
|
-
xmin,
|
|
1038
|
-
xmax,
|
|
1039
|
-
ymin,
|
|
1040
|
-
ymax
|
|
1041
|
-
);
|
|
1042
|
-
const existingChord = [existingT1, existingT2];
|
|
1043
|
-
if (chordsCross(newChord, existingChord)) {
|
|
1044
|
-
crossings++;
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
return crossings;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
// lib/JumperGraphSolver/computeCrossingAssignments.ts
|
|
1051
|
-
function computeCrossingAssignments(region, port1, port2) {
|
|
1052
|
-
const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
|
|
1053
|
-
const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
|
|
1054
|
-
const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
|
|
1055
|
-
const newChord = [t1, t2];
|
|
1056
|
-
const crossingAssignments = [];
|
|
1057
|
-
const assignments = region.assignments ?? [];
|
|
1058
|
-
for (const assignment of assignments) {
|
|
1059
|
-
const existingT1 = perimeterT(
|
|
1060
|
-
assignment.regionPort1.d,
|
|
1061
|
-
xmin,
|
|
1062
|
-
xmax,
|
|
1063
|
-
ymin,
|
|
1064
|
-
ymax
|
|
1065
|
-
);
|
|
1066
|
-
const existingT2 = perimeterT(
|
|
1067
|
-
assignment.regionPort2.d,
|
|
1068
|
-
xmin,
|
|
1069
|
-
xmax,
|
|
1070
|
-
ymin,
|
|
1071
|
-
ymax
|
|
1072
|
-
);
|
|
1073
|
-
const existingChord = [existingT1, existingT2];
|
|
1074
|
-
if (chordsCross(newChord, existingChord)) {
|
|
1075
|
-
crossingAssignments.push(assignment);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
return crossingAssignments;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
// lib/JumperGraphSolver/countInputConnectionCrossings.ts
|
|
1082
|
-
function countInputConnectionCrossings(graph, connections) {
|
|
1083
|
-
if (connections.length < 2) {
|
|
1084
|
-
return 0;
|
|
1085
|
-
}
|
|
1086
|
-
let minX = Infinity;
|
|
1087
|
-
let maxX = -Infinity;
|
|
1088
|
-
let minY = Infinity;
|
|
1089
|
-
let maxY = -Infinity;
|
|
1090
|
-
for (const region of graph.regions) {
|
|
1091
|
-
const jRegion = region;
|
|
1092
|
-
if (jRegion.d?.bounds) {
|
|
1093
|
-
minX = Math.min(minX, jRegion.d.bounds.minX);
|
|
1094
|
-
maxX = Math.max(maxX, jRegion.d.bounds.maxX);
|
|
1095
|
-
minY = Math.min(minY, jRegion.d.bounds.minY);
|
|
1096
|
-
maxY = Math.max(maxY, jRegion.d.bounds.maxY);
|
|
1097
|
-
} else if (jRegion.d?.center) {
|
|
1098
|
-
minX = Math.min(minX, jRegion.d.center.x);
|
|
1099
|
-
maxX = Math.max(maxX, jRegion.d.center.x);
|
|
1100
|
-
minY = Math.min(minY, jRegion.d.center.y);
|
|
1101
|
-
maxY = Math.max(maxY, jRegion.d.center.y);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
const regionCenterMap = /* @__PURE__ */ new Map();
|
|
1105
|
-
for (const region of graph.regions) {
|
|
1106
|
-
const jRegion = region;
|
|
1107
|
-
if (jRegion.d?.center) {
|
|
1108
|
-
regionCenterMap.set(region.regionId, jRegion.d.center);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
const chords = [];
|
|
1112
|
-
for (const conn of connections) {
|
|
1113
|
-
let startCenter;
|
|
1114
|
-
let endCenter;
|
|
1115
|
-
if ("startRegion" in conn && conn.startRegion) {
|
|
1116
|
-
const startRegion = conn.startRegion;
|
|
1117
|
-
const endRegion = conn.endRegion;
|
|
1118
|
-
startCenter = startRegion.d?.center;
|
|
1119
|
-
endCenter = endRegion.d?.center;
|
|
1120
|
-
} else if ("startRegionId" in conn) {
|
|
1121
|
-
startCenter = regionCenterMap.get(conn.startRegionId);
|
|
1122
|
-
endCenter = regionCenterMap.get(conn.endRegionId);
|
|
1123
|
-
}
|
|
1124
|
-
if (!startCenter || !endCenter) {
|
|
1125
|
-
continue;
|
|
1126
|
-
}
|
|
1127
|
-
const t1 = perimeterT(startCenter, minX, maxX, minY, maxY);
|
|
1128
|
-
const t2 = perimeterT(endCenter, minX, maxX, minY, maxY);
|
|
1129
|
-
chords.push([t1, t2]);
|
|
1130
|
-
}
|
|
1131
|
-
let crossings = 0;
|
|
1132
|
-
for (let i = 0; i < chords.length; i++) {
|
|
1133
|
-
for (let j = i + 1; j < chords.length; j++) {
|
|
1134
|
-
if (chordsCross(chords[i], chords[j])) {
|
|
1135
|
-
crossings++;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
return crossings;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
1278
|
// lib/JumperGraphSolver/JumperGraphSolver.ts
|
|
1143
1279
|
var JUMPER_GRAPH_SOLVER_DEFAULTS = {
|
|
1144
1280
|
portUsagePenalty: 0.034685181009478865,
|
|
@@ -1217,15 +1353,56 @@ var JumperGraphSolver = class extends HyperGraphSolver {
|
|
|
1217
1353
|
const ripCount = port.ripCount ?? 0;
|
|
1218
1354
|
return ripCount * this.portUsagePenalty + ripCount * this.portUsagePenaltySq;
|
|
1219
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
|
+
}
|
|
1220
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
|
+
}
|
|
1221
1375
|
const crossings = computeDifferentNetCrossings(region, port1, port2);
|
|
1222
1376
|
return crossings * this.crossingPenalty + crossings * this.crossingPenaltySq;
|
|
1223
1377
|
}
|
|
1224
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
|
+
}
|
|
1225
1385
|
const crossingAssignments = computeCrossingAssignments(region, port1, port2);
|
|
1226
|
-
|
|
1386
|
+
const conflictingAssignments = crossingAssignments.filter(
|
|
1227
1387
|
(a) => a.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId
|
|
1228
1388
|
);
|
|
1389
|
+
if (!region.d.isThroughJumper) return conflictingAssignments;
|
|
1390
|
+
for (const assignment of region.assignments ?? []) {
|
|
1391
|
+
if (assignment.connection.mutuallyConnectedNetworkId === this.currentConnection.mutuallyConnectedNetworkId) {
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
conflictingAssignments.push(assignment);
|
|
1395
|
+
}
|
|
1396
|
+
return conflictingAssignments;
|
|
1397
|
+
}
|
|
1398
|
+
isRipRequiredForPortUsage(region, _port1, _port2) {
|
|
1399
|
+
if (!region.d.isThroughJumper && !region.d.isPad) return false;
|
|
1400
|
+
for (const assignment of region.assignments ?? []) {
|
|
1401
|
+
if (assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId) {
|
|
1402
|
+
return true;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return false;
|
|
1229
1406
|
}
|
|
1230
1407
|
routeSolvedHook(solvedRoute) {
|
|
1231
1408
|
}
|
|
@@ -1271,23 +1448,123 @@ var createConnectionRegion = (regionId, x, y) => {
|
|
|
1271
1448
|
};
|
|
1272
1449
|
|
|
1273
1450
|
// lib/JumperGraphSolver/jumper-graph-generator/findBoundaryRegion.ts
|
|
1274
|
-
var
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
+
}
|
|
1283
1504
|
}
|
|
1284
|
-
if (
|
|
1285
|
-
|
|
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
|
+
}
|
|
1286
1536
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
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 };
|
|
1289
1562
|
}
|
|
1290
1563
|
}
|
|
1564
|
+
return best;
|
|
1565
|
+
};
|
|
1566
|
+
var findBoundaryRegion = (x, y, regions, graphBounds) => {
|
|
1567
|
+
const preferredSides = getBoundarySidesForPoint(x, y, graphBounds);
|
|
1291
1568
|
let closestRegion = null;
|
|
1292
1569
|
let closestDistance = Number.POSITIVE_INFINITY;
|
|
1293
1570
|
let closestPortPosition = { x, y };
|
|
@@ -1296,23 +1573,19 @@ var findBoundaryRegion = (x, y, regions, graphBounds) => {
|
|
|
1296
1573
|
const bounds = region.d.bounds;
|
|
1297
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;
|
|
1298
1575
|
if (!isOuterRegion) continue;
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
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);
|
|
1302
1585
|
if (dist < closestDistance) {
|
|
1303
1586
|
closestDistance = dist;
|
|
1304
1587
|
closestRegion = region;
|
|
1305
|
-
|
|
1306
|
-
closestPortPosition = { x: bounds.minX, y: clampedY };
|
|
1307
|
-
} else if (x > bounds.maxX) {
|
|
1308
|
-
closestPortPosition = { x: bounds.maxX, y: clampedY };
|
|
1309
|
-
} else if (y < bounds.minY) {
|
|
1310
|
-
closestPortPosition = { x: clampedX, y: bounds.minY };
|
|
1311
|
-
} else if (y > bounds.maxY) {
|
|
1312
|
-
closestPortPosition = { x: clampedX, y: bounds.maxY };
|
|
1313
|
-
} else {
|
|
1314
|
-
closestPortPosition = { x: clampedX, y: clampedY };
|
|
1315
|
-
}
|
|
1588
|
+
closestPortPosition = { x: projection.x, y: projection.y };
|
|
1316
1589
|
}
|
|
1317
1590
|
}
|
|
1318
1591
|
if (closestRegion) {
|
|
@@ -4946,64 +5219,28 @@ function generateDefaultViaTopologyGrid(opts) {
|
|
|
4946
5219
|
}
|
|
4947
5220
|
|
|
4948
5221
|
// lib/ViaGraphSolver/polygonPerimeterUtils.ts
|
|
4949
|
-
function polygonPerimeterT(p, polygon2) {
|
|
4950
|
-
const n = polygon2.length;
|
|
4951
|
-
let bestDist = Infinity;
|
|
4952
|
-
let bestEdgeIndex = 0;
|
|
4953
|
-
let bestT = 0;
|
|
4954
|
-
for (let i = 0; i < n; i++) {
|
|
4955
|
-
const a2 = polygon2[i];
|
|
4956
|
-
const b2 = polygon2[(i + 1) % n];
|
|
4957
|
-
const dx = b2.x - a2.x;
|
|
4958
|
-
const dy = b2.y - a2.y;
|
|
4959
|
-
const lenSq = dx * dx + dy * dy;
|
|
4960
|
-
if (lenSq < 1e-10) continue;
|
|
4961
|
-
const t = Math.max(
|
|
4962
|
-
0,
|
|
4963
|
-
Math.min(1, ((p.x - a2.x) * dx + (p.y - a2.y) * dy) / lenSq)
|
|
4964
|
-
);
|
|
4965
|
-
const projX = a2.x + t * dx;
|
|
4966
|
-
const projY = a2.y + t * dy;
|
|
4967
|
-
const dist = Math.sqrt((p.x - projX) ** 2 + (p.y - projY) ** 2);
|
|
4968
|
-
if (dist < bestDist) {
|
|
4969
|
-
bestDist = dist;
|
|
4970
|
-
bestEdgeIndex = i;
|
|
4971
|
-
bestT = t;
|
|
4972
|
-
}
|
|
4973
|
-
}
|
|
4974
|
-
let cumulative = 0;
|
|
4975
|
-
for (let i = 0; i < bestEdgeIndex; i++) {
|
|
4976
|
-
const a2 = polygon2[i];
|
|
4977
|
-
const b2 = polygon2[(i + 1) % n];
|
|
4978
|
-
cumulative += Math.sqrt((b2.x - a2.x) ** 2 + (b2.y - a2.y) ** 2);
|
|
4979
|
-
}
|
|
4980
|
-
const a = polygon2[bestEdgeIndex];
|
|
4981
|
-
const b = polygon2[(bestEdgeIndex + 1) % n];
|
|
4982
|
-
const edgeLen = Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2);
|
|
4983
|
-
cumulative += bestT * edgeLen;
|
|
4984
|
-
return cumulative;
|
|
4985
|
-
}
|
|
4986
5222
|
function computeDifferentNetCrossingsForPolygon(region, port1, port2) {
|
|
4987
5223
|
const polygon2 = region.d.polygon;
|
|
4988
5224
|
if (!polygon2 || polygon2.length < 3) {
|
|
4989
5225
|
return 0;
|
|
4990
5226
|
}
|
|
4991
|
-
const
|
|
4992
|
-
const
|
|
5227
|
+
const perimeter = getRegionPerimeter(region);
|
|
5228
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
5229
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
4993
5230
|
const newChord = [t1, t2];
|
|
4994
5231
|
let crossings = 0;
|
|
4995
5232
|
const assignments = region.assignments ?? [];
|
|
4996
5233
|
for (const assignment of assignments) {
|
|
4997
|
-
const existingT1 =
|
|
4998
|
-
assignment.regionPort1
|
|
4999
|
-
|
|
5234
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
5235
|
+
assignment.regionPort1,
|
|
5236
|
+
region
|
|
5000
5237
|
);
|
|
5001
|
-
const existingT2 =
|
|
5002
|
-
assignment.regionPort2
|
|
5003
|
-
|
|
5238
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
5239
|
+
assignment.regionPort2,
|
|
5240
|
+
region
|
|
5004
5241
|
);
|
|
5005
5242
|
const existingChord = [existingT1, existingT2];
|
|
5006
|
-
if (chordsCross(newChord, existingChord)) {
|
|
5243
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
5007
5244
|
crossings++;
|
|
5008
5245
|
}
|
|
5009
5246
|
}
|
|
@@ -5014,22 +5251,23 @@ function computeCrossingAssignmentsForPolygon(region, port1, port2) {
|
|
|
5014
5251
|
if (!polygon2 || polygon2.length < 3) {
|
|
5015
5252
|
return [];
|
|
5016
5253
|
}
|
|
5017
|
-
const
|
|
5018
|
-
const
|
|
5254
|
+
const perimeter = getRegionPerimeter(region);
|
|
5255
|
+
const t1 = getPortPerimeterTInRegion(port1, region);
|
|
5256
|
+
const t2 = getPortPerimeterTInRegion(port2, region);
|
|
5019
5257
|
const newChord = [t1, t2];
|
|
5020
5258
|
const crossingAssignments = [];
|
|
5021
5259
|
const assignments = region.assignments ?? [];
|
|
5022
5260
|
for (const assignment of assignments) {
|
|
5023
|
-
const existingT1 =
|
|
5024
|
-
assignment.regionPort1
|
|
5025
|
-
|
|
5261
|
+
const existingT1 = getPortPerimeterTInRegion(
|
|
5262
|
+
assignment.regionPort1,
|
|
5263
|
+
region
|
|
5026
5264
|
);
|
|
5027
|
-
const existingT2 =
|
|
5028
|
-
assignment.regionPort2
|
|
5029
|
-
|
|
5265
|
+
const existingT2 = getPortPerimeterTInRegion(
|
|
5266
|
+
assignment.regionPort2,
|
|
5267
|
+
region
|
|
5030
5268
|
);
|
|
5031
5269
|
const existingChord = [existingT1, existingT2];
|
|
5032
|
-
if (chordsCross(newChord, existingChord)) {
|
|
5270
|
+
if (chordsCross(newChord, existingChord, perimeter)) {
|
|
5033
5271
|
crossingAssignments.push(assignment);
|
|
5034
5272
|
}
|
|
5035
5273
|
}
|
|
@@ -5076,6 +5314,12 @@ var visualizeViaGraphSolver = (solver) => {
|
|
|
5076
5314
|
hidePortPoints: true
|
|
5077
5315
|
} : {}
|
|
5078
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
|
+
}
|
|
5079
5323
|
const outerIds = /* @__PURE__ */ new Set(["T", "B", "L", "R"]);
|
|
5080
5324
|
let netColorIndex = 0;
|
|
5081
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",
|