@tscircuit/hypergraph 0.0.1
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/LICENSE +21 -0
- package/README.md +224 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.js +2318 -0
- package/package.json +32 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2318 @@
|
|
|
1
|
+
// lib/JumperGraphSolver/geometry/getBoundsCenter.ts
|
|
2
|
+
var computeBoundsCenter = (bounds) => {
|
|
3
|
+
return {
|
|
4
|
+
x: (bounds.minX + bounds.maxX) / 2,
|
|
5
|
+
y: (bounds.minY + bounds.maxY) / 2
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// lib/JumperGraphSolver/jumper-graph-generator/generateSingleJumperX4Regions.ts
|
|
10
|
+
var dims1206x4 = {
|
|
11
|
+
padWidth: 0.8,
|
|
12
|
+
// X direction (horizontal)
|
|
13
|
+
padHeight: 0.5,
|
|
14
|
+
// Y direction (vertical)
|
|
15
|
+
leftPadCenterX: -1.35,
|
|
16
|
+
// X position of left pads (P1, P2, P3, P4)
|
|
17
|
+
rightPadCenterX: 1.35,
|
|
18
|
+
// X position of right pads (P5, P6, P7, P8)
|
|
19
|
+
row1CenterY: 1.905,
|
|
20
|
+
// Y position of row 1 (P1, P8)
|
|
21
|
+
row2CenterY: 0.635,
|
|
22
|
+
// Y position of row 2 (P2, P7)
|
|
23
|
+
row3CenterY: -0.635,
|
|
24
|
+
// Y position of row 3 (P3, P6)
|
|
25
|
+
row4CenterY: -1.905
|
|
26
|
+
// Y position of row 4 (P4, P5)
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// lib/JumperGraphSolver/jumper-graph-generator/calculateGraphBounds.ts
|
|
30
|
+
var calculateGraphBounds = (regions) => {
|
|
31
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
32
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
33
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
34
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
35
|
+
for (const region of regions) {
|
|
36
|
+
const { bounds } = region.d;
|
|
37
|
+
minX = Math.min(minX, bounds.minX);
|
|
38
|
+
maxX = Math.max(maxX, bounds.maxX);
|
|
39
|
+
minY = Math.min(minY, bounds.minY);
|
|
40
|
+
maxY = Math.max(maxY, bounds.maxY);
|
|
41
|
+
}
|
|
42
|
+
return { minX, maxX, minY, maxY };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// node_modules/transformation-matrix/src/applyToPoint.js
|
|
46
|
+
function applyToPoint(matrix, point) {
|
|
47
|
+
return Array.isArray(point) ? [
|
|
48
|
+
matrix.a * point[0] + matrix.c * point[1] + matrix.e,
|
|
49
|
+
matrix.b * point[0] + matrix.d * point[1] + matrix.f
|
|
50
|
+
] : {
|
|
51
|
+
x: matrix.a * point.x + matrix.c * point.y + matrix.e,
|
|
52
|
+
y: matrix.b * point.x + matrix.d * point.y + matrix.f
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// node_modules/transformation-matrix/src/utils.js
|
|
57
|
+
function isUndefined(val) {
|
|
58
|
+
return typeof val === "undefined";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// node_modules/transformation-matrix/src/translate.js
|
|
62
|
+
function translate(tx, ty = 0) {
|
|
63
|
+
return {
|
|
64
|
+
a: 1,
|
|
65
|
+
c: 0,
|
|
66
|
+
e: tx,
|
|
67
|
+
b: 0,
|
|
68
|
+
d: 1,
|
|
69
|
+
f: ty
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// node_modules/transformation-matrix/src/transform.js
|
|
74
|
+
function transform(...matrices) {
|
|
75
|
+
matrices = Array.isArray(matrices[0]) ? matrices[0] : matrices;
|
|
76
|
+
const multiply = (m1, m2) => {
|
|
77
|
+
return {
|
|
78
|
+
a: m1.a * m2.a + m1.c * m2.b,
|
|
79
|
+
c: m1.a * m2.c + m1.c * m2.d,
|
|
80
|
+
e: m1.a * m2.e + m1.c * m2.f + m1.e,
|
|
81
|
+
b: m1.b * m2.a + m1.d * m2.b,
|
|
82
|
+
d: m1.b * m2.c + m1.d * m2.d,
|
|
83
|
+
f: m1.b * m2.e + m1.d * m2.f + m1.f
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
switch (matrices.length) {
|
|
87
|
+
case 0:
|
|
88
|
+
throw new Error("no matrices provided");
|
|
89
|
+
case 1:
|
|
90
|
+
return matrices[0];
|
|
91
|
+
case 2:
|
|
92
|
+
return multiply(matrices[0], matrices[1]);
|
|
93
|
+
default: {
|
|
94
|
+
const [m1, m2, ...rest] = matrices;
|
|
95
|
+
const m = multiply(m1, m2);
|
|
96
|
+
return transform(m, ...rest);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function compose(...matrices) {
|
|
101
|
+
return transform(...matrices);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// node_modules/transformation-matrix/src/rotate.js
|
|
105
|
+
var { cos, sin, PI } = Math;
|
|
106
|
+
function rotate(angle, cx, cy) {
|
|
107
|
+
const cosAngle = cos(angle);
|
|
108
|
+
const sinAngle = sin(angle);
|
|
109
|
+
const rotationMatrix = {
|
|
110
|
+
a: cosAngle,
|
|
111
|
+
c: -sinAngle,
|
|
112
|
+
e: 0,
|
|
113
|
+
b: sinAngle,
|
|
114
|
+
d: cosAngle,
|
|
115
|
+
f: 0
|
|
116
|
+
};
|
|
117
|
+
if (isUndefined(cx) || isUndefined(cy)) {
|
|
118
|
+
return rotationMatrix;
|
|
119
|
+
}
|
|
120
|
+
return transform([
|
|
121
|
+
translate(cx, cy),
|
|
122
|
+
rotationMatrix,
|
|
123
|
+
translate(-cx, -cy)
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// node_modules/transformation-matrix/src/skew.js
|
|
128
|
+
var { tan } = Math;
|
|
129
|
+
|
|
130
|
+
// lib/JumperGraphSolver/geometry/applyTransformToGraph.ts
|
|
131
|
+
var applyTransformToGraph = (graph, matrix) => {
|
|
132
|
+
const transformedRegions = graph.regions.map((region) => {
|
|
133
|
+
const { bounds, center, ...rest } = region.d;
|
|
134
|
+
const corners = [
|
|
135
|
+
{ x: bounds.minX, y: bounds.minY },
|
|
136
|
+
{ x: bounds.maxX, y: bounds.minY },
|
|
137
|
+
{ x: bounds.minX, y: bounds.maxY },
|
|
138
|
+
{ x: bounds.maxX, y: bounds.maxY }
|
|
139
|
+
].map((corner) => applyToPoint(matrix, corner));
|
|
140
|
+
const newBounds = {
|
|
141
|
+
minX: Math.min(...corners.map((c) => c.x)),
|
|
142
|
+
maxX: Math.max(...corners.map((c) => c.x)),
|
|
143
|
+
minY: Math.min(...corners.map((c) => c.y)),
|
|
144
|
+
maxY: Math.max(...corners.map((c) => c.y))
|
|
145
|
+
};
|
|
146
|
+
const newCenter = applyToPoint(matrix, center);
|
|
147
|
+
return {
|
|
148
|
+
...region,
|
|
149
|
+
// Clear ports array - will be rebuilt with new port objects
|
|
150
|
+
ports: [],
|
|
151
|
+
d: {
|
|
152
|
+
...rest,
|
|
153
|
+
bounds: newBounds,
|
|
154
|
+
center: newCenter
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
const regionMap = /* @__PURE__ */ new Map();
|
|
159
|
+
for (let i = 0; i < graph.regions.length; i++) {
|
|
160
|
+
regionMap.set(graph.regions[i], transformedRegions[i]);
|
|
161
|
+
}
|
|
162
|
+
const transformedPorts = graph.ports.map((port) => {
|
|
163
|
+
const newPosition = applyToPoint(matrix, port.d);
|
|
164
|
+
const newRegion1 = regionMap.get(port.region1);
|
|
165
|
+
const newRegion2 = regionMap.get(port.region2);
|
|
166
|
+
const newPort = {
|
|
167
|
+
...port,
|
|
168
|
+
region1: newRegion1,
|
|
169
|
+
region2: newRegion2,
|
|
170
|
+
d: newPosition
|
|
171
|
+
};
|
|
172
|
+
newRegion1.ports.push(newPort);
|
|
173
|
+
newRegion2.ports.push(newPort);
|
|
174
|
+
return newPort;
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
regions: transformedRegions,
|
|
178
|
+
ports: transformedPorts
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
var rotateGraph90Degrees = (graph) => {
|
|
182
|
+
const bounds = calculateGraphBounds(graph.regions);
|
|
183
|
+
const center = computeBoundsCenter(bounds);
|
|
184
|
+
const matrix = compose(
|
|
185
|
+
translate(center.x, center.y),
|
|
186
|
+
rotate(-Math.PI / 2),
|
|
187
|
+
// -90 degrees (clockwise)
|
|
188
|
+
translate(-center.x, -center.y)
|
|
189
|
+
);
|
|
190
|
+
return applyTransformToGraph(graph, matrix);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// lib/JumperGraphSolver/jumper-graph-generator/generateJumperX4Grid.ts
|
|
194
|
+
var generateJumperX4Grid = ({
|
|
195
|
+
cols,
|
|
196
|
+
rows,
|
|
197
|
+
marginX,
|
|
198
|
+
marginY,
|
|
199
|
+
innerColChannelPointCount = 1,
|
|
200
|
+
innerRowChannelPointCount = 1,
|
|
201
|
+
regionsBetweenPads = false,
|
|
202
|
+
outerPaddingX = 0.5,
|
|
203
|
+
outerPaddingY = 0.5,
|
|
204
|
+
outerChannelXPointCount,
|
|
205
|
+
outerChannelYPointCount,
|
|
206
|
+
orientation: orientation2 = "vertical",
|
|
207
|
+
center
|
|
208
|
+
}) => {
|
|
209
|
+
const effectiveOuterChannelXPoints = outerChannelXPointCount ?? Math.max(1, Math.floor(outerPaddingX / 0.4));
|
|
210
|
+
const effectiveOuterChannelYPoints = outerChannelYPointCount ?? Math.max(1, Math.floor(outerPaddingY / 0.4));
|
|
211
|
+
const regions = [];
|
|
212
|
+
const ports = [];
|
|
213
|
+
const {
|
|
214
|
+
padWidth,
|
|
215
|
+
padHeight,
|
|
216
|
+
leftPadCenterX,
|
|
217
|
+
rightPadCenterX,
|
|
218
|
+
row1CenterY,
|
|
219
|
+
row2CenterY,
|
|
220
|
+
row3CenterY,
|
|
221
|
+
row4CenterY
|
|
222
|
+
} = dims1206x4;
|
|
223
|
+
const padHalfWidth = padWidth / 2;
|
|
224
|
+
const padHalfHeight = padHeight / 2;
|
|
225
|
+
const cellWidth = rightPadCenterX - leftPadCenterX + padWidth;
|
|
226
|
+
const horizontalSpacing = cellWidth + marginX;
|
|
227
|
+
const cellHeight = row1CenterY - row4CenterY + padHeight;
|
|
228
|
+
const verticalSpacing = cellHeight + marginY;
|
|
229
|
+
const cells = [];
|
|
230
|
+
const createRegion = (id, bounds, isPad, isThroughJumper) => ({
|
|
231
|
+
regionId: id,
|
|
232
|
+
ports: [],
|
|
233
|
+
d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper }
|
|
234
|
+
});
|
|
235
|
+
const createPort = (id, region1, region2) => {
|
|
236
|
+
const b1 = region1.d.bounds;
|
|
237
|
+
const b2 = region2.d.bounds;
|
|
238
|
+
let x;
|
|
239
|
+
let y;
|
|
240
|
+
if (Math.abs(b1.maxX - b2.minX) < 1e-3) {
|
|
241
|
+
x = b1.maxX;
|
|
242
|
+
y = (Math.max(b1.minY, b2.minY) + Math.min(b1.maxY, b2.maxY)) / 2;
|
|
243
|
+
} else if (Math.abs(b1.minX - b2.maxX) < 1e-3) {
|
|
244
|
+
x = b1.minX;
|
|
245
|
+
y = (Math.max(b1.minY, b2.minY) + Math.min(b1.maxY, b2.maxY)) / 2;
|
|
246
|
+
} else if (Math.abs(b1.maxY - b2.minY) < 1e-3) {
|
|
247
|
+
x = (Math.max(b1.minX, b2.minX) + Math.min(b1.maxX, b2.maxX)) / 2;
|
|
248
|
+
y = b1.maxY;
|
|
249
|
+
} else {
|
|
250
|
+
x = (Math.max(b1.minX, b2.minX) + Math.min(b1.maxX, b2.maxX)) / 2;
|
|
251
|
+
y = b1.minY;
|
|
252
|
+
}
|
|
253
|
+
const port = {
|
|
254
|
+
portId: id,
|
|
255
|
+
region1,
|
|
256
|
+
region2,
|
|
257
|
+
d: { x, y }
|
|
258
|
+
};
|
|
259
|
+
region1.ports.push(port);
|
|
260
|
+
region2.ports.push(port);
|
|
261
|
+
return port;
|
|
262
|
+
};
|
|
263
|
+
const createMultiplePorts = (idPrefix, region1, region2, count) => {
|
|
264
|
+
if (count <= 0) return [];
|
|
265
|
+
if (count === 1) {
|
|
266
|
+
return [createPort(idPrefix, region1, region2)];
|
|
267
|
+
}
|
|
268
|
+
const b1 = region1.d.bounds;
|
|
269
|
+
const b2 = region2.d.bounds;
|
|
270
|
+
const result = [];
|
|
271
|
+
let isVerticalBoundary;
|
|
272
|
+
let boundaryCoord;
|
|
273
|
+
let sharedMin;
|
|
274
|
+
let sharedMax;
|
|
275
|
+
if (Math.abs(b1.maxX - b2.minX) < 1e-3) {
|
|
276
|
+
isVerticalBoundary = true;
|
|
277
|
+
boundaryCoord = b1.maxX;
|
|
278
|
+
sharedMin = Math.max(b1.minY, b2.minY);
|
|
279
|
+
sharedMax = Math.min(b1.maxY, b2.maxY);
|
|
280
|
+
} else if (Math.abs(b1.minX - b2.maxX) < 1e-3) {
|
|
281
|
+
isVerticalBoundary = true;
|
|
282
|
+
boundaryCoord = b1.minX;
|
|
283
|
+
sharedMin = Math.max(b1.minY, b2.minY);
|
|
284
|
+
sharedMax = Math.min(b1.maxY, b2.maxY);
|
|
285
|
+
} else if (Math.abs(b1.maxY - b2.minY) < 1e-3) {
|
|
286
|
+
isVerticalBoundary = false;
|
|
287
|
+
boundaryCoord = b1.maxY;
|
|
288
|
+
sharedMin = Math.max(b1.minX, b2.minX);
|
|
289
|
+
sharedMax = Math.min(b1.maxX, b2.maxX);
|
|
290
|
+
} else {
|
|
291
|
+
isVerticalBoundary = false;
|
|
292
|
+
boundaryCoord = b1.minY;
|
|
293
|
+
sharedMin = Math.max(b1.minX, b2.minX);
|
|
294
|
+
sharedMax = Math.min(b1.maxX, b2.maxX);
|
|
295
|
+
}
|
|
296
|
+
for (let i = 0; i < count; i++) {
|
|
297
|
+
const t = (i + 0.5) / count;
|
|
298
|
+
const coord = sharedMin + t * (sharedMax - sharedMin);
|
|
299
|
+
const x = isVerticalBoundary ? boundaryCoord : coord;
|
|
300
|
+
const y = isVerticalBoundary ? coord : boundaryCoord;
|
|
301
|
+
const port = {
|
|
302
|
+
portId: `${idPrefix}:${i}`,
|
|
303
|
+
region1,
|
|
304
|
+
region2,
|
|
305
|
+
d: { x, y }
|
|
306
|
+
};
|
|
307
|
+
region1.ports.push(port);
|
|
308
|
+
region2.ports.push(port);
|
|
309
|
+
result.push(port);
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
};
|
|
313
|
+
for (let row = 0; row < rows; row++) {
|
|
314
|
+
cells[row] = [];
|
|
315
|
+
for (let col = 0; col < cols; col++) {
|
|
316
|
+
const idPrefix = `cell_${row}_${col}`;
|
|
317
|
+
const centerX = col * horizontalSpacing;
|
|
318
|
+
const centerY = -row * verticalSpacing;
|
|
319
|
+
const p1CenterX = centerX + leftPadCenterX;
|
|
320
|
+
const p1CenterY = centerY + row1CenterY;
|
|
321
|
+
const p2CenterX = centerX + leftPadCenterX;
|
|
322
|
+
const p2CenterY = centerY + row2CenterY;
|
|
323
|
+
const p3CenterX = centerX + leftPadCenterX;
|
|
324
|
+
const p3CenterY = centerY + row3CenterY;
|
|
325
|
+
const p4CenterX = centerX + leftPadCenterX;
|
|
326
|
+
const p4CenterY = centerY + row4CenterY;
|
|
327
|
+
const p5CenterX = centerX + rightPadCenterX;
|
|
328
|
+
const p5CenterY = centerY + row4CenterY;
|
|
329
|
+
const p6CenterX = centerX + rightPadCenterX;
|
|
330
|
+
const p6CenterY = centerY + row3CenterY;
|
|
331
|
+
const p7CenterX = centerX + rightPadCenterX;
|
|
332
|
+
const p7CenterY = centerY + row2CenterY;
|
|
333
|
+
const p8CenterX = centerX + rightPadCenterX;
|
|
334
|
+
const p8CenterY = centerY + row1CenterY;
|
|
335
|
+
const createPadBounds = (padCenterX, padCenterY) => ({
|
|
336
|
+
minX: padCenterX - padHalfWidth,
|
|
337
|
+
maxX: padCenterX + padHalfWidth,
|
|
338
|
+
minY: padCenterY - padHalfHeight,
|
|
339
|
+
maxY: padCenterY + padHalfHeight
|
|
340
|
+
});
|
|
341
|
+
const pad1Bounds = createPadBounds(p1CenterX, p1CenterY);
|
|
342
|
+
const pad2Bounds = createPadBounds(p2CenterX, p2CenterY);
|
|
343
|
+
const pad3Bounds = createPadBounds(p3CenterX, p3CenterY);
|
|
344
|
+
const pad4Bounds = createPadBounds(p4CenterX, p4CenterY);
|
|
345
|
+
const pad5Bounds = createPadBounds(p5CenterX, p5CenterY);
|
|
346
|
+
const pad6Bounds = createPadBounds(p6CenterX, p6CenterY);
|
|
347
|
+
const pad7Bounds = createPadBounds(p7CenterX, p7CenterY);
|
|
348
|
+
const pad8Bounds = createPadBounds(p8CenterX, p8CenterY);
|
|
349
|
+
const underjumperBounds = {
|
|
350
|
+
minX: pad1Bounds.maxX,
|
|
351
|
+
maxX: pad8Bounds.minX,
|
|
352
|
+
minY: pad4Bounds.minY,
|
|
353
|
+
maxY: pad1Bounds.maxY
|
|
354
|
+
};
|
|
355
|
+
const throughjumperHeight = 0.3;
|
|
356
|
+
const throughjumper1Bounds = {
|
|
357
|
+
minX: p1CenterX,
|
|
358
|
+
maxX: p8CenterX,
|
|
359
|
+
minY: p1CenterY - throughjumperHeight / 2,
|
|
360
|
+
maxY: p1CenterY + throughjumperHeight / 2
|
|
361
|
+
};
|
|
362
|
+
const throughjumper2Bounds = {
|
|
363
|
+
minX: p2CenterX,
|
|
364
|
+
maxX: p7CenterX,
|
|
365
|
+
minY: p2CenterY - throughjumperHeight / 2,
|
|
366
|
+
maxY: p2CenterY + throughjumperHeight / 2
|
|
367
|
+
};
|
|
368
|
+
const throughjumper3Bounds = {
|
|
369
|
+
minX: p3CenterX,
|
|
370
|
+
maxX: p6CenterX,
|
|
371
|
+
minY: p3CenterY - throughjumperHeight / 2,
|
|
372
|
+
maxY: p3CenterY + throughjumperHeight / 2
|
|
373
|
+
};
|
|
374
|
+
const throughjumper4Bounds = {
|
|
375
|
+
minX: p4CenterX,
|
|
376
|
+
maxX: p5CenterX,
|
|
377
|
+
minY: p4CenterY - throughjumperHeight / 2,
|
|
378
|
+
maxY: p4CenterY + throughjumperHeight / 2
|
|
379
|
+
};
|
|
380
|
+
const mainMinX = pad1Bounds.minX;
|
|
381
|
+
const mainMaxX = pad8Bounds.maxX;
|
|
382
|
+
const mainMinY = pad4Bounds.minY;
|
|
383
|
+
const mainMaxY = pad1Bounds.maxY;
|
|
384
|
+
const pad1 = createRegion(`${idPrefix}:pad1`, pad1Bounds, true);
|
|
385
|
+
const pad2 = createRegion(`${idPrefix}:pad2`, pad2Bounds, true);
|
|
386
|
+
const pad3 = createRegion(`${idPrefix}:pad3`, pad3Bounds, true);
|
|
387
|
+
const pad4 = createRegion(`${idPrefix}:pad4`, pad4Bounds, true);
|
|
388
|
+
const pad5 = createRegion(`${idPrefix}:pad5`, pad5Bounds, true);
|
|
389
|
+
const pad6 = createRegion(`${idPrefix}:pad6`, pad6Bounds, true);
|
|
390
|
+
const pad7 = createRegion(`${idPrefix}:pad7`, pad7Bounds, true);
|
|
391
|
+
const pad8 = createRegion(`${idPrefix}:pad8`, pad8Bounds, true);
|
|
392
|
+
const underjumper = createRegion(
|
|
393
|
+
`${idPrefix}:underjumper`,
|
|
394
|
+
underjumperBounds,
|
|
395
|
+
false
|
|
396
|
+
);
|
|
397
|
+
const throughjumper1 = createRegion(
|
|
398
|
+
`${idPrefix}:throughjumper1`,
|
|
399
|
+
throughjumper1Bounds,
|
|
400
|
+
false,
|
|
401
|
+
true
|
|
402
|
+
);
|
|
403
|
+
const throughjumper2 = createRegion(
|
|
404
|
+
`${idPrefix}:throughjumper2`,
|
|
405
|
+
throughjumper2Bounds,
|
|
406
|
+
false,
|
|
407
|
+
true
|
|
408
|
+
);
|
|
409
|
+
const throughjumper3 = createRegion(
|
|
410
|
+
`${idPrefix}:throughjumper3`,
|
|
411
|
+
throughjumper3Bounds,
|
|
412
|
+
false,
|
|
413
|
+
true
|
|
414
|
+
);
|
|
415
|
+
const throughjumper4 = createRegion(
|
|
416
|
+
`${idPrefix}:throughjumper4`,
|
|
417
|
+
throughjumper4Bounds,
|
|
418
|
+
false,
|
|
419
|
+
true
|
|
420
|
+
);
|
|
421
|
+
regions.push(
|
|
422
|
+
pad1,
|
|
423
|
+
pad2,
|
|
424
|
+
pad3,
|
|
425
|
+
pad4,
|
|
426
|
+
pad5,
|
|
427
|
+
pad6,
|
|
428
|
+
pad7,
|
|
429
|
+
pad8,
|
|
430
|
+
underjumper,
|
|
431
|
+
throughjumper1,
|
|
432
|
+
throughjumper2,
|
|
433
|
+
throughjumper3,
|
|
434
|
+
throughjumper4
|
|
435
|
+
);
|
|
436
|
+
let leftBP12 = null;
|
|
437
|
+
let leftBP23 = null;
|
|
438
|
+
let leftBP34 = null;
|
|
439
|
+
let rightBP87 = null;
|
|
440
|
+
let rightBP76 = null;
|
|
441
|
+
let rightBP65 = null;
|
|
442
|
+
if (regionsBetweenPads) {
|
|
443
|
+
leftBP12 = createRegion(
|
|
444
|
+
`${idPrefix}:L-BP12`,
|
|
445
|
+
{
|
|
446
|
+
minX: pad1Bounds.minX,
|
|
447
|
+
maxX: pad1Bounds.maxX,
|
|
448
|
+
minY: pad2Bounds.maxY,
|
|
449
|
+
maxY: pad1Bounds.minY
|
|
450
|
+
},
|
|
451
|
+
false
|
|
452
|
+
);
|
|
453
|
+
leftBP23 = createRegion(
|
|
454
|
+
`${idPrefix}:L-BP23`,
|
|
455
|
+
{
|
|
456
|
+
minX: pad2Bounds.minX,
|
|
457
|
+
maxX: pad2Bounds.maxX,
|
|
458
|
+
minY: pad3Bounds.maxY,
|
|
459
|
+
maxY: pad2Bounds.minY
|
|
460
|
+
},
|
|
461
|
+
false
|
|
462
|
+
);
|
|
463
|
+
leftBP34 = createRegion(
|
|
464
|
+
`${idPrefix}:L-BP34`,
|
|
465
|
+
{
|
|
466
|
+
minX: pad3Bounds.minX,
|
|
467
|
+
maxX: pad3Bounds.maxX,
|
|
468
|
+
minY: pad4Bounds.maxY,
|
|
469
|
+
maxY: pad3Bounds.minY
|
|
470
|
+
},
|
|
471
|
+
false
|
|
472
|
+
);
|
|
473
|
+
rightBP87 = createRegion(
|
|
474
|
+
`${idPrefix}:R-BP87`,
|
|
475
|
+
{
|
|
476
|
+
minX: pad8Bounds.minX,
|
|
477
|
+
maxX: pad8Bounds.maxX,
|
|
478
|
+
minY: pad7Bounds.maxY,
|
|
479
|
+
maxY: pad8Bounds.minY
|
|
480
|
+
},
|
|
481
|
+
false
|
|
482
|
+
);
|
|
483
|
+
rightBP76 = createRegion(
|
|
484
|
+
`${idPrefix}:R-BP76`,
|
|
485
|
+
{
|
|
486
|
+
minX: pad7Bounds.minX,
|
|
487
|
+
maxX: pad7Bounds.maxX,
|
|
488
|
+
minY: pad6Bounds.maxY,
|
|
489
|
+
maxY: pad7Bounds.minY
|
|
490
|
+
},
|
|
491
|
+
false
|
|
492
|
+
);
|
|
493
|
+
rightBP65 = createRegion(
|
|
494
|
+
`${idPrefix}:R-BP65`,
|
|
495
|
+
{
|
|
496
|
+
minX: pad6Bounds.minX,
|
|
497
|
+
maxX: pad6Bounds.maxX,
|
|
498
|
+
minY: pad5Bounds.maxY,
|
|
499
|
+
maxY: pad6Bounds.minY
|
|
500
|
+
},
|
|
501
|
+
false
|
|
502
|
+
);
|
|
503
|
+
regions.push(
|
|
504
|
+
leftBP12,
|
|
505
|
+
leftBP23,
|
|
506
|
+
leftBP34,
|
|
507
|
+
rightBP87,
|
|
508
|
+
rightBP76,
|
|
509
|
+
rightBP65
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
const isFirstRow = row === 0;
|
|
513
|
+
const isFirstCol = col === 0;
|
|
514
|
+
const isLastRow = row === rows - 1;
|
|
515
|
+
const isLastCol = col === cols - 1;
|
|
516
|
+
let frameRightEdge;
|
|
517
|
+
if (isLastCol) {
|
|
518
|
+
frameRightEdge = mainMaxX + outerPaddingX;
|
|
519
|
+
} else {
|
|
520
|
+
const nextCenterX = (col + 1) * horizontalSpacing;
|
|
521
|
+
const nextP1CenterX = nextCenterX + leftPadCenterX;
|
|
522
|
+
frameRightEdge = nextP1CenterX - padHalfWidth;
|
|
523
|
+
}
|
|
524
|
+
let top = null;
|
|
525
|
+
if (isFirstRow) {
|
|
526
|
+
top = createRegion(
|
|
527
|
+
`${idPrefix}:T`,
|
|
528
|
+
{
|
|
529
|
+
minX: isFirstCol ? mainMinX - outerPaddingX : mainMinX,
|
|
530
|
+
maxX: frameRightEdge,
|
|
531
|
+
minY: mainMaxY,
|
|
532
|
+
maxY: mainMaxY + outerPaddingY
|
|
533
|
+
},
|
|
534
|
+
false
|
|
535
|
+
);
|
|
536
|
+
regions.push(top);
|
|
537
|
+
}
|
|
538
|
+
let bottom = null;
|
|
539
|
+
const bottomHeight = isLastRow ? outerPaddingY : marginY;
|
|
540
|
+
bottom = createRegion(
|
|
541
|
+
`${idPrefix}:B`,
|
|
542
|
+
{
|
|
543
|
+
minX: isFirstCol ? mainMinX - outerPaddingX : mainMinX,
|
|
544
|
+
maxX: frameRightEdge,
|
|
545
|
+
minY: mainMinY - bottomHeight,
|
|
546
|
+
maxY: mainMinY
|
|
547
|
+
},
|
|
548
|
+
false
|
|
549
|
+
);
|
|
550
|
+
regions.push(bottom);
|
|
551
|
+
let left = null;
|
|
552
|
+
if (isFirstCol) {
|
|
553
|
+
left = createRegion(
|
|
554
|
+
`${idPrefix}:L`,
|
|
555
|
+
{
|
|
556
|
+
minX: mainMinX - outerPaddingX,
|
|
557
|
+
maxX: mainMinX,
|
|
558
|
+
minY: mainMinY,
|
|
559
|
+
maxY: mainMaxY
|
|
560
|
+
},
|
|
561
|
+
false
|
|
562
|
+
);
|
|
563
|
+
regions.push(left);
|
|
564
|
+
}
|
|
565
|
+
const right = createRegion(
|
|
566
|
+
`${idPrefix}:R`,
|
|
567
|
+
{
|
|
568
|
+
minX: mainMaxX,
|
|
569
|
+
maxX: frameRightEdge,
|
|
570
|
+
minY: mainMinY,
|
|
571
|
+
maxY: mainMaxY
|
|
572
|
+
},
|
|
573
|
+
false
|
|
574
|
+
);
|
|
575
|
+
regions.push(right);
|
|
576
|
+
cells[row][col] = {
|
|
577
|
+
pad1,
|
|
578
|
+
pad2,
|
|
579
|
+
pad3,
|
|
580
|
+
pad4,
|
|
581
|
+
pad5,
|
|
582
|
+
pad6,
|
|
583
|
+
pad7,
|
|
584
|
+
pad8,
|
|
585
|
+
underjumper,
|
|
586
|
+
throughjumper1,
|
|
587
|
+
throughjumper2,
|
|
588
|
+
throughjumper3,
|
|
589
|
+
throughjumper4,
|
|
590
|
+
top,
|
|
591
|
+
bottom,
|
|
592
|
+
left,
|
|
593
|
+
right,
|
|
594
|
+
leftBP12,
|
|
595
|
+
leftBP23,
|
|
596
|
+
leftBP34,
|
|
597
|
+
rightBP87,
|
|
598
|
+
rightBP76,
|
|
599
|
+
rightBP65
|
|
600
|
+
};
|
|
601
|
+
if (top) {
|
|
602
|
+
if (left) {
|
|
603
|
+
ports.push(
|
|
604
|
+
...createMultiplePorts(
|
|
605
|
+
`${idPrefix}:T-L`,
|
|
606
|
+
top,
|
|
607
|
+
left,
|
|
608
|
+
effectiveOuterChannelXPoints
|
|
609
|
+
)
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
ports.push(
|
|
613
|
+
...createMultiplePorts(
|
|
614
|
+
`${idPrefix}:T-R`,
|
|
615
|
+
top,
|
|
616
|
+
right,
|
|
617
|
+
isLastCol ? effectiveOuterChannelXPoints : innerColChannelPointCount
|
|
618
|
+
)
|
|
619
|
+
);
|
|
620
|
+
ports.push(createPort(`${idPrefix}:T-P1`, top, pad1));
|
|
621
|
+
ports.push(createPort(`${idPrefix}:T-P8`, top, pad8));
|
|
622
|
+
if (regionsBetweenPads) {
|
|
623
|
+
const ujBounds = underjumper.d.bounds;
|
|
624
|
+
const ujWidth = ujBounds.maxX - ujBounds.minX;
|
|
625
|
+
const portSpacing = ujWidth / 5;
|
|
626
|
+
for (let i = 1; i <= 4; i++) {
|
|
627
|
+
const portX = ujBounds.minX + portSpacing * i;
|
|
628
|
+
const topUJPort = {
|
|
629
|
+
portId: `${idPrefix}:T-UJ${i}`,
|
|
630
|
+
region1: top,
|
|
631
|
+
region2: underjumper,
|
|
632
|
+
d: { x: portX, y: ujBounds.maxY }
|
|
633
|
+
};
|
|
634
|
+
top.ports.push(topUJPort);
|
|
635
|
+
underjumper.ports.push(topUJPort);
|
|
636
|
+
ports.push(topUJPort);
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
ports.push(createPort(`${idPrefix}:T-UJ`, top, underjumper));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (bottom) {
|
|
643
|
+
if (left) {
|
|
644
|
+
ports.push(
|
|
645
|
+
...createMultiplePorts(
|
|
646
|
+
`${idPrefix}:B-L`,
|
|
647
|
+
bottom,
|
|
648
|
+
left,
|
|
649
|
+
effectiveOuterChannelXPoints
|
|
650
|
+
)
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
ports.push(
|
|
654
|
+
...createMultiplePorts(
|
|
655
|
+
`${idPrefix}:B-R`,
|
|
656
|
+
bottom,
|
|
657
|
+
right,
|
|
658
|
+
isLastCol ? effectiveOuterChannelXPoints : innerColChannelPointCount
|
|
659
|
+
)
|
|
660
|
+
);
|
|
661
|
+
ports.push(createPort(`${idPrefix}:B-P4`, bottom, pad4));
|
|
662
|
+
ports.push(createPort(`${idPrefix}:B-P5`, bottom, pad5));
|
|
663
|
+
if (regionsBetweenPads) {
|
|
664
|
+
const ujBounds = underjumper.d.bounds;
|
|
665
|
+
const ujWidth = ujBounds.maxX - ujBounds.minX;
|
|
666
|
+
const portSpacing = ujWidth / 5;
|
|
667
|
+
for (let i = 1; i <= 4; i++) {
|
|
668
|
+
const portX = ujBounds.minX + portSpacing * i;
|
|
669
|
+
const bottomUJPort = {
|
|
670
|
+
portId: `${idPrefix}:B-UJ${i}`,
|
|
671
|
+
region1: bottom,
|
|
672
|
+
region2: underjumper,
|
|
673
|
+
d: { x: portX, y: ujBounds.minY }
|
|
674
|
+
};
|
|
675
|
+
bottom.ports.push(bottomUJPort);
|
|
676
|
+
underjumper.ports.push(bottomUJPort);
|
|
677
|
+
ports.push(bottomUJPort);
|
|
678
|
+
}
|
|
679
|
+
} else {
|
|
680
|
+
ports.push(createPort(`${idPrefix}:B-UJ`, bottom, underjumper));
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (left) {
|
|
684
|
+
ports.push(createPort(`${idPrefix}:L-P1`, left, pad1));
|
|
685
|
+
ports.push(createPort(`${idPrefix}:L-P2`, left, pad2));
|
|
686
|
+
ports.push(createPort(`${idPrefix}:L-P3`, left, pad3));
|
|
687
|
+
ports.push(createPort(`${idPrefix}:L-P4`, left, pad4));
|
|
688
|
+
}
|
|
689
|
+
ports.push(createPort(`${idPrefix}:R-P5`, right, pad5));
|
|
690
|
+
ports.push(createPort(`${idPrefix}:R-P6`, right, pad6));
|
|
691
|
+
ports.push(createPort(`${idPrefix}:R-P7`, right, pad7));
|
|
692
|
+
ports.push(createPort(`${idPrefix}:R-P8`, right, pad8));
|
|
693
|
+
if (regionsBetweenPads) {
|
|
694
|
+
if (left) {
|
|
695
|
+
ports.push(createPort(`${idPrefix}:L-BP12`, left, leftBP12));
|
|
696
|
+
ports.push(createPort(`${idPrefix}:L-BP23`, left, leftBP23));
|
|
697
|
+
ports.push(createPort(`${idPrefix}:L-BP34`, left, leftBP34));
|
|
698
|
+
}
|
|
699
|
+
ports.push(createPort(`${idPrefix}:UJ-LBP12`, leftBP12, underjumper));
|
|
700
|
+
ports.push(createPort(`${idPrefix}:UJ-LBP23`, leftBP23, underjumper));
|
|
701
|
+
ports.push(createPort(`${idPrefix}:UJ-LBP34`, leftBP34, underjumper));
|
|
702
|
+
ports.push(createPort(`${idPrefix}:R-BP87`, right, rightBP87));
|
|
703
|
+
ports.push(createPort(`${idPrefix}:R-BP76`, right, rightBP76));
|
|
704
|
+
ports.push(createPort(`${idPrefix}:R-BP65`, right, rightBP65));
|
|
705
|
+
ports.push(createPort(`${idPrefix}:UJ-RBP87`, rightBP87, underjumper));
|
|
706
|
+
ports.push(createPort(`${idPrefix}:UJ-RBP76`, rightBP76, underjumper));
|
|
707
|
+
ports.push(createPort(`${idPrefix}:UJ-RBP65`, rightBP65, underjumper));
|
|
708
|
+
}
|
|
709
|
+
const tj1LeftPort = {
|
|
710
|
+
portId: `${idPrefix}:TJ1-P1`,
|
|
711
|
+
region1: throughjumper1,
|
|
712
|
+
region2: pad1,
|
|
713
|
+
d: { x: p1CenterX, y: p1CenterY }
|
|
714
|
+
};
|
|
715
|
+
throughjumper1.ports.push(tj1LeftPort);
|
|
716
|
+
pad1.ports.push(tj1LeftPort);
|
|
717
|
+
ports.push(tj1LeftPort);
|
|
718
|
+
const tj1RightPort = {
|
|
719
|
+
portId: `${idPrefix}:TJ1-P8`,
|
|
720
|
+
region1: throughjumper1,
|
|
721
|
+
region2: pad8,
|
|
722
|
+
d: { x: p8CenterX, y: p8CenterY }
|
|
723
|
+
};
|
|
724
|
+
throughjumper1.ports.push(tj1RightPort);
|
|
725
|
+
pad8.ports.push(tj1RightPort);
|
|
726
|
+
ports.push(tj1RightPort);
|
|
727
|
+
const tj2LeftPort = {
|
|
728
|
+
portId: `${idPrefix}:TJ2-P2`,
|
|
729
|
+
region1: throughjumper2,
|
|
730
|
+
region2: pad2,
|
|
731
|
+
d: { x: p2CenterX, y: p2CenterY }
|
|
732
|
+
};
|
|
733
|
+
throughjumper2.ports.push(tj2LeftPort);
|
|
734
|
+
pad2.ports.push(tj2LeftPort);
|
|
735
|
+
ports.push(tj2LeftPort);
|
|
736
|
+
const tj2RightPort = {
|
|
737
|
+
portId: `${idPrefix}:TJ2-P7`,
|
|
738
|
+
region1: throughjumper2,
|
|
739
|
+
region2: pad7,
|
|
740
|
+
d: { x: p7CenterX, y: p7CenterY }
|
|
741
|
+
};
|
|
742
|
+
throughjumper2.ports.push(tj2RightPort);
|
|
743
|
+
pad7.ports.push(tj2RightPort);
|
|
744
|
+
ports.push(tj2RightPort);
|
|
745
|
+
const tj3LeftPort = {
|
|
746
|
+
portId: `${idPrefix}:TJ3-P3`,
|
|
747
|
+
region1: throughjumper3,
|
|
748
|
+
region2: pad3,
|
|
749
|
+
d: { x: p3CenterX, y: p3CenterY }
|
|
750
|
+
};
|
|
751
|
+
throughjumper3.ports.push(tj3LeftPort);
|
|
752
|
+
pad3.ports.push(tj3LeftPort);
|
|
753
|
+
ports.push(tj3LeftPort);
|
|
754
|
+
const tj3RightPort = {
|
|
755
|
+
portId: `${idPrefix}:TJ3-P6`,
|
|
756
|
+
region1: throughjumper3,
|
|
757
|
+
region2: pad6,
|
|
758
|
+
d: { x: p6CenterX, y: p6CenterY }
|
|
759
|
+
};
|
|
760
|
+
throughjumper3.ports.push(tj3RightPort);
|
|
761
|
+
pad6.ports.push(tj3RightPort);
|
|
762
|
+
ports.push(tj3RightPort);
|
|
763
|
+
const tj4LeftPort = {
|
|
764
|
+
portId: `${idPrefix}:TJ4-P4`,
|
|
765
|
+
region1: throughjumper4,
|
|
766
|
+
region2: pad4,
|
|
767
|
+
d: { x: p4CenterX, y: p4CenterY }
|
|
768
|
+
};
|
|
769
|
+
throughjumper4.ports.push(tj4LeftPort);
|
|
770
|
+
pad4.ports.push(tj4LeftPort);
|
|
771
|
+
ports.push(tj4LeftPort);
|
|
772
|
+
const tj4RightPort = {
|
|
773
|
+
portId: `${idPrefix}:TJ4-P5`,
|
|
774
|
+
region1: throughjumper4,
|
|
775
|
+
region2: pad5,
|
|
776
|
+
d: { x: p5CenterX, y: p5CenterY }
|
|
777
|
+
};
|
|
778
|
+
throughjumper4.ports.push(tj4RightPort);
|
|
779
|
+
pad5.ports.push(tj4RightPort);
|
|
780
|
+
ports.push(tj4RightPort);
|
|
781
|
+
if (col > 0) {
|
|
782
|
+
const prevCell = cells[row][col - 1];
|
|
783
|
+
ports.push(
|
|
784
|
+
createPort(
|
|
785
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:R-P1`,
|
|
786
|
+
prevCell.right,
|
|
787
|
+
pad1
|
|
788
|
+
)
|
|
789
|
+
);
|
|
790
|
+
ports.push(
|
|
791
|
+
createPort(
|
|
792
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:R-P2`,
|
|
793
|
+
prevCell.right,
|
|
794
|
+
pad2
|
|
795
|
+
)
|
|
796
|
+
);
|
|
797
|
+
ports.push(
|
|
798
|
+
createPort(
|
|
799
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:R-P3`,
|
|
800
|
+
prevCell.right,
|
|
801
|
+
pad3
|
|
802
|
+
)
|
|
803
|
+
);
|
|
804
|
+
ports.push(
|
|
805
|
+
createPort(
|
|
806
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:R-P4`,
|
|
807
|
+
prevCell.right,
|
|
808
|
+
pad4
|
|
809
|
+
)
|
|
810
|
+
);
|
|
811
|
+
if (regionsBetweenPads) {
|
|
812
|
+
ports.push(
|
|
813
|
+
createPort(
|
|
814
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:R-LBP12`,
|
|
815
|
+
prevCell.right,
|
|
816
|
+
leftBP12
|
|
817
|
+
)
|
|
818
|
+
);
|
|
819
|
+
ports.push(
|
|
820
|
+
createPort(
|
|
821
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:R-LBP23`,
|
|
822
|
+
prevCell.right,
|
|
823
|
+
leftBP23
|
|
824
|
+
)
|
|
825
|
+
);
|
|
826
|
+
ports.push(
|
|
827
|
+
createPort(
|
|
828
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:R-LBP34`,
|
|
829
|
+
prevCell.right,
|
|
830
|
+
leftBP34
|
|
831
|
+
)
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
if (top && prevCell.top) {
|
|
835
|
+
ports.push(
|
|
836
|
+
...createMultiplePorts(
|
|
837
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:T-T`,
|
|
838
|
+
prevCell.top,
|
|
839
|
+
top,
|
|
840
|
+
innerRowChannelPointCount
|
|
841
|
+
)
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
if (bottom && prevCell.bottom) {
|
|
845
|
+
ports.push(
|
|
846
|
+
...createMultiplePorts(
|
|
847
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:B-B`,
|
|
848
|
+
prevCell.bottom,
|
|
849
|
+
bottom,
|
|
850
|
+
innerRowChannelPointCount
|
|
851
|
+
)
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (row > 0) {
|
|
856
|
+
const aboveCell = cells[row - 1][col];
|
|
857
|
+
if (left) {
|
|
858
|
+
ports.push(
|
|
859
|
+
...createMultiplePorts(
|
|
860
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-L`,
|
|
861
|
+
aboveCell.bottom,
|
|
862
|
+
left,
|
|
863
|
+
effectiveOuterChannelXPoints
|
|
864
|
+
)
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
ports.push(
|
|
868
|
+
createPort(
|
|
869
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-P1`,
|
|
870
|
+
aboveCell.bottom,
|
|
871
|
+
pad1
|
|
872
|
+
)
|
|
873
|
+
);
|
|
874
|
+
if (regionsBetweenPads) {
|
|
875
|
+
const ujBounds = underjumper.d.bounds;
|
|
876
|
+
const ujWidth = ujBounds.maxX - ujBounds.minX;
|
|
877
|
+
const portSpacing = ujWidth / 5;
|
|
878
|
+
for (let i = 1; i <= 4; i++) {
|
|
879
|
+
const portX = ujBounds.minX + portSpacing * i;
|
|
880
|
+
const aboveUJPort = {
|
|
881
|
+
portId: `cell_${row - 1}_${col}->cell_${row}_${col}:B-UJ${i}`,
|
|
882
|
+
region1: aboveCell.bottom,
|
|
883
|
+
region2: underjumper,
|
|
884
|
+
d: { x: portX, y: ujBounds.maxY }
|
|
885
|
+
};
|
|
886
|
+
aboveCell.bottom.ports.push(aboveUJPort);
|
|
887
|
+
underjumper.ports.push(aboveUJPort);
|
|
888
|
+
ports.push(aboveUJPort);
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
ports.push(
|
|
892
|
+
createPort(
|
|
893
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-UJ`,
|
|
894
|
+
aboveCell.bottom,
|
|
895
|
+
underjumper
|
|
896
|
+
)
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
ports.push(
|
|
900
|
+
createPort(
|
|
901
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-P8`,
|
|
902
|
+
aboveCell.bottom,
|
|
903
|
+
pad8
|
|
904
|
+
)
|
|
905
|
+
);
|
|
906
|
+
ports.push(
|
|
907
|
+
...createMultiplePorts(
|
|
908
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-R`,
|
|
909
|
+
aboveCell.bottom,
|
|
910
|
+
right,
|
|
911
|
+
isLastCol ? effectiveOuterChannelXPoints : innerColChannelPointCount
|
|
912
|
+
)
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
let graph = { regions, ports };
|
|
918
|
+
const needsRotation = orientation2 === "horizontal";
|
|
919
|
+
const needsCentering = center !== void 0;
|
|
920
|
+
if (needsRotation || needsCentering) {
|
|
921
|
+
const currentBounds = calculateGraphBounds(graph.regions);
|
|
922
|
+
const currentCenter = computeBoundsCenter(currentBounds);
|
|
923
|
+
const matrices = [];
|
|
924
|
+
matrices.push(translate(-currentCenter.x, -currentCenter.y));
|
|
925
|
+
if (needsRotation) {
|
|
926
|
+
matrices.push(rotate(-Math.PI / 2));
|
|
927
|
+
}
|
|
928
|
+
const targetCenter = center ?? currentCenter;
|
|
929
|
+
matrices.push(translate(targetCenter.x, targetCenter.y));
|
|
930
|
+
const matrix = compose(...matrices);
|
|
931
|
+
graph = applyTransformToGraph(graph, matrix);
|
|
932
|
+
}
|
|
933
|
+
return graph;
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
// lib/JumperGraphSolver/jumper-graph-generator/generateSingleJumperRegions.ts
|
|
937
|
+
var dims0603 = {
|
|
938
|
+
padToPad: 1.65,
|
|
939
|
+
padLength: 0.8,
|
|
940
|
+
padWidth: 0.95
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
// lib/JumperGraphSolver/jumper-graph-generator/generateJumperGrid.ts
|
|
944
|
+
var generateJumperGrid = ({
|
|
945
|
+
cols,
|
|
946
|
+
rows,
|
|
947
|
+
marginX,
|
|
948
|
+
marginY,
|
|
949
|
+
innerColChannelPointCount = 1,
|
|
950
|
+
innerRowChannelPointCount = 1,
|
|
951
|
+
outerPaddingX = 0.5,
|
|
952
|
+
outerPaddingY = 0.5,
|
|
953
|
+
outerChannelXPoints,
|
|
954
|
+
outerChannelYPoints
|
|
955
|
+
}) => {
|
|
956
|
+
const effectiveOuterChannelXPoints = outerChannelXPoints ?? Math.max(1, Math.floor(outerPaddingX / 0.4));
|
|
957
|
+
const effectiveOuterChannelYPoints = outerChannelYPoints ?? Math.max(1, Math.floor(outerPaddingY / 0.4));
|
|
958
|
+
const regions = [];
|
|
959
|
+
const ports = [];
|
|
960
|
+
const { padToPad, padLength, padWidth } = dims0603;
|
|
961
|
+
const padHalfLength = padLength / 2;
|
|
962
|
+
const padHalfWidth = padWidth / 2;
|
|
963
|
+
const horizontalSpacing = padToPad + padLength + marginX;
|
|
964
|
+
const verticalSpacing = padWidth + marginY;
|
|
965
|
+
const cells = [];
|
|
966
|
+
const createRegion = (id, bounds, isPad, isThroughJumper) => ({
|
|
967
|
+
regionId: id,
|
|
968
|
+
ports: [],
|
|
969
|
+
d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper }
|
|
970
|
+
});
|
|
971
|
+
const createPort = (id, region1, region2) => {
|
|
972
|
+
const b1 = region1.d.bounds;
|
|
973
|
+
const b2 = region2.d.bounds;
|
|
974
|
+
let x;
|
|
975
|
+
let y;
|
|
976
|
+
if (Math.abs(b1.maxX - b2.minX) < 1e-3) {
|
|
977
|
+
x = b1.maxX;
|
|
978
|
+
y = (Math.max(b1.minY, b2.minY) + Math.min(b1.maxY, b2.maxY)) / 2;
|
|
979
|
+
} else if (Math.abs(b1.minX - b2.maxX) < 1e-3) {
|
|
980
|
+
x = b1.minX;
|
|
981
|
+
y = (Math.max(b1.minY, b2.minY) + Math.min(b1.maxY, b2.maxY)) / 2;
|
|
982
|
+
} else if (Math.abs(b1.maxY - b2.minY) < 1e-3) {
|
|
983
|
+
x = (Math.max(b1.minX, b2.minX) + Math.min(b1.maxX, b2.maxX)) / 2;
|
|
984
|
+
y = b1.maxY;
|
|
985
|
+
} else {
|
|
986
|
+
x = (Math.max(b1.minX, b2.minX) + Math.min(b1.maxX, b2.maxX)) / 2;
|
|
987
|
+
y = b1.minY;
|
|
988
|
+
}
|
|
989
|
+
const port = {
|
|
990
|
+
portId: id,
|
|
991
|
+
region1,
|
|
992
|
+
region2,
|
|
993
|
+
d: { x, y }
|
|
994
|
+
};
|
|
995
|
+
region1.ports.push(port);
|
|
996
|
+
region2.ports.push(port);
|
|
997
|
+
return port;
|
|
998
|
+
};
|
|
999
|
+
const createMultiplePorts = (idPrefix, region1, region2, count) => {
|
|
1000
|
+
if (count <= 0) return [];
|
|
1001
|
+
if (count === 1) {
|
|
1002
|
+
return [createPort(idPrefix, region1, region2)];
|
|
1003
|
+
}
|
|
1004
|
+
const b1 = region1.d.bounds;
|
|
1005
|
+
const b2 = region2.d.bounds;
|
|
1006
|
+
const result = [];
|
|
1007
|
+
let isVerticalBoundary;
|
|
1008
|
+
let boundaryCoord;
|
|
1009
|
+
let sharedMin;
|
|
1010
|
+
let sharedMax;
|
|
1011
|
+
if (Math.abs(b1.maxX - b2.minX) < 1e-3) {
|
|
1012
|
+
isVerticalBoundary = true;
|
|
1013
|
+
boundaryCoord = b1.maxX;
|
|
1014
|
+
sharedMin = Math.max(b1.minY, b2.minY);
|
|
1015
|
+
sharedMax = Math.min(b1.maxY, b2.maxY);
|
|
1016
|
+
} else if (Math.abs(b1.minX - b2.maxX) < 1e-3) {
|
|
1017
|
+
isVerticalBoundary = true;
|
|
1018
|
+
boundaryCoord = b1.minX;
|
|
1019
|
+
sharedMin = Math.max(b1.minY, b2.minY);
|
|
1020
|
+
sharedMax = Math.min(b1.maxY, b2.maxY);
|
|
1021
|
+
} else if (Math.abs(b1.maxY - b2.minY) < 1e-3) {
|
|
1022
|
+
isVerticalBoundary = false;
|
|
1023
|
+
boundaryCoord = b1.maxY;
|
|
1024
|
+
sharedMin = Math.max(b1.minX, b2.minX);
|
|
1025
|
+
sharedMax = Math.min(b1.maxX, b2.maxX);
|
|
1026
|
+
} else {
|
|
1027
|
+
isVerticalBoundary = false;
|
|
1028
|
+
boundaryCoord = b1.minY;
|
|
1029
|
+
sharedMin = Math.max(b1.minX, b2.minX);
|
|
1030
|
+
sharedMax = Math.min(b1.maxX, b2.maxX);
|
|
1031
|
+
}
|
|
1032
|
+
for (let i = 0; i < count; i++) {
|
|
1033
|
+
const t = (i + 0.5) / count;
|
|
1034
|
+
const coord = sharedMin + t * (sharedMax - sharedMin);
|
|
1035
|
+
const x = isVerticalBoundary ? boundaryCoord : coord;
|
|
1036
|
+
const y = isVerticalBoundary ? coord : boundaryCoord;
|
|
1037
|
+
const port = {
|
|
1038
|
+
portId: `${idPrefix}:${i}`,
|
|
1039
|
+
region1,
|
|
1040
|
+
region2,
|
|
1041
|
+
d: { x, y }
|
|
1042
|
+
};
|
|
1043
|
+
region1.ports.push(port);
|
|
1044
|
+
region2.ports.push(port);
|
|
1045
|
+
result.push(port);
|
|
1046
|
+
}
|
|
1047
|
+
return result;
|
|
1048
|
+
};
|
|
1049
|
+
for (let row = 0; row < rows; row++) {
|
|
1050
|
+
cells[row] = [];
|
|
1051
|
+
for (let col = 0; col < cols; col++) {
|
|
1052
|
+
const idPrefix = `cell_${row}_${col}`;
|
|
1053
|
+
const centerX = col * horizontalSpacing;
|
|
1054
|
+
const centerY = -row * verticalSpacing;
|
|
1055
|
+
const leftPadCenterX = centerX - padToPad / 2;
|
|
1056
|
+
const rightPadCenterX = centerX + padToPad / 2;
|
|
1057
|
+
const leftPadBounds = {
|
|
1058
|
+
minX: leftPadCenterX - padHalfLength,
|
|
1059
|
+
maxX: leftPadCenterX + padHalfLength,
|
|
1060
|
+
minY: centerY - padHalfWidth,
|
|
1061
|
+
maxY: centerY + padHalfWidth
|
|
1062
|
+
};
|
|
1063
|
+
const rightPadBounds = {
|
|
1064
|
+
minX: rightPadCenterX - padHalfLength,
|
|
1065
|
+
maxX: rightPadCenterX + padHalfLength,
|
|
1066
|
+
minY: centerY - padHalfWidth,
|
|
1067
|
+
maxY: centerY + padHalfWidth
|
|
1068
|
+
};
|
|
1069
|
+
const underjumperBounds = {
|
|
1070
|
+
minX: leftPadBounds.maxX,
|
|
1071
|
+
maxX: rightPadBounds.minX,
|
|
1072
|
+
minY: centerY - padHalfWidth,
|
|
1073
|
+
maxY: centerY + padHalfWidth
|
|
1074
|
+
};
|
|
1075
|
+
const throughjumperHeight = 0.3;
|
|
1076
|
+
const throughjumperBounds = {
|
|
1077
|
+
minX: leftPadCenterX,
|
|
1078
|
+
maxX: rightPadCenterX,
|
|
1079
|
+
minY: centerY - throughjumperHeight / 2,
|
|
1080
|
+
maxY: centerY + throughjumperHeight / 2
|
|
1081
|
+
};
|
|
1082
|
+
const mainMinX = leftPadBounds.minX;
|
|
1083
|
+
const mainMaxX = rightPadBounds.maxX;
|
|
1084
|
+
const mainMinY = leftPadBounds.minY;
|
|
1085
|
+
const mainMaxY = leftPadBounds.maxY;
|
|
1086
|
+
const leftPad = createRegion(`${idPrefix}:leftPad`, leftPadBounds, true);
|
|
1087
|
+
const rightPad = createRegion(
|
|
1088
|
+
`${idPrefix}:rightPad`,
|
|
1089
|
+
rightPadBounds,
|
|
1090
|
+
true
|
|
1091
|
+
);
|
|
1092
|
+
const underjumper = createRegion(
|
|
1093
|
+
`${idPrefix}:underjumper`,
|
|
1094
|
+
underjumperBounds,
|
|
1095
|
+
false
|
|
1096
|
+
);
|
|
1097
|
+
const throughjumper = createRegion(
|
|
1098
|
+
`${idPrefix}:throughjumper`,
|
|
1099
|
+
throughjumperBounds,
|
|
1100
|
+
false,
|
|
1101
|
+
true
|
|
1102
|
+
);
|
|
1103
|
+
regions.push(leftPad, rightPad, underjumper, throughjumper);
|
|
1104
|
+
const isFirstRow = row === 0;
|
|
1105
|
+
const isFirstCol = col === 0;
|
|
1106
|
+
const isLastRow = row === rows - 1;
|
|
1107
|
+
const isLastCol = col === cols - 1;
|
|
1108
|
+
let frameRightEdge;
|
|
1109
|
+
if (isLastCol) {
|
|
1110
|
+
frameRightEdge = mainMaxX + outerPaddingX;
|
|
1111
|
+
} else {
|
|
1112
|
+
const nextCenterX = (col + 1) * horizontalSpacing;
|
|
1113
|
+
const nextLeftPadCenterX = nextCenterX - padToPad / 2;
|
|
1114
|
+
frameRightEdge = nextLeftPadCenterX - padHalfLength;
|
|
1115
|
+
}
|
|
1116
|
+
let top = null;
|
|
1117
|
+
if (isFirstRow) {
|
|
1118
|
+
top = createRegion(
|
|
1119
|
+
`${idPrefix}:T`,
|
|
1120
|
+
{
|
|
1121
|
+
minX: isFirstCol ? mainMinX - outerPaddingX : mainMinX,
|
|
1122
|
+
maxX: frameRightEdge,
|
|
1123
|
+
minY: mainMaxY,
|
|
1124
|
+
maxY: mainMaxY + outerPaddingY
|
|
1125
|
+
},
|
|
1126
|
+
false
|
|
1127
|
+
);
|
|
1128
|
+
regions.push(top);
|
|
1129
|
+
}
|
|
1130
|
+
let bottom = null;
|
|
1131
|
+
const bottomHeight = isLastRow ? outerPaddingY : marginY;
|
|
1132
|
+
bottom = createRegion(
|
|
1133
|
+
`${idPrefix}:B`,
|
|
1134
|
+
{
|
|
1135
|
+
minX: isFirstCol ? mainMinX - outerPaddingX : mainMinX,
|
|
1136
|
+
maxX: frameRightEdge,
|
|
1137
|
+
minY: mainMinY - bottomHeight,
|
|
1138
|
+
maxY: mainMinY
|
|
1139
|
+
},
|
|
1140
|
+
false
|
|
1141
|
+
);
|
|
1142
|
+
regions.push(bottom);
|
|
1143
|
+
let left = null;
|
|
1144
|
+
if (isFirstCol) {
|
|
1145
|
+
left = createRegion(
|
|
1146
|
+
`${idPrefix}:L`,
|
|
1147
|
+
{
|
|
1148
|
+
minX: mainMinX - outerPaddingX,
|
|
1149
|
+
maxX: mainMinX,
|
|
1150
|
+
minY: mainMinY,
|
|
1151
|
+
maxY: mainMaxY
|
|
1152
|
+
},
|
|
1153
|
+
false
|
|
1154
|
+
);
|
|
1155
|
+
regions.push(left);
|
|
1156
|
+
}
|
|
1157
|
+
const right = createRegion(
|
|
1158
|
+
`${idPrefix}:R`,
|
|
1159
|
+
{
|
|
1160
|
+
minX: mainMaxX,
|
|
1161
|
+
maxX: frameRightEdge,
|
|
1162
|
+
minY: mainMinY,
|
|
1163
|
+
maxY: mainMaxY
|
|
1164
|
+
},
|
|
1165
|
+
false
|
|
1166
|
+
);
|
|
1167
|
+
regions.push(right);
|
|
1168
|
+
cells[row][col] = {
|
|
1169
|
+
leftPad,
|
|
1170
|
+
rightPad,
|
|
1171
|
+
underjumper,
|
|
1172
|
+
throughjumper,
|
|
1173
|
+
top,
|
|
1174
|
+
bottom,
|
|
1175
|
+
left,
|
|
1176
|
+
right
|
|
1177
|
+
};
|
|
1178
|
+
if (top) {
|
|
1179
|
+
if (left) {
|
|
1180
|
+
ports.push(
|
|
1181
|
+
...createMultiplePorts(
|
|
1182
|
+
`${idPrefix}:T-L`,
|
|
1183
|
+
top,
|
|
1184
|
+
left,
|
|
1185
|
+
effectiveOuterChannelXPoints
|
|
1186
|
+
)
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
ports.push(
|
|
1190
|
+
...createMultiplePorts(
|
|
1191
|
+
`${idPrefix}:T-R`,
|
|
1192
|
+
top,
|
|
1193
|
+
right,
|
|
1194
|
+
effectiveOuterChannelXPoints
|
|
1195
|
+
)
|
|
1196
|
+
);
|
|
1197
|
+
ports.push(createPort(`${idPrefix}:T-LP`, top, leftPad));
|
|
1198
|
+
ports.push(createPort(`${idPrefix}:T-RP`, top, rightPad));
|
|
1199
|
+
ports.push(createPort(`${idPrefix}:T-UJ`, top, underjumper));
|
|
1200
|
+
}
|
|
1201
|
+
if (bottom) {
|
|
1202
|
+
if (left) {
|
|
1203
|
+
ports.push(
|
|
1204
|
+
...createMultiplePorts(
|
|
1205
|
+
`${idPrefix}:B-L`,
|
|
1206
|
+
bottom,
|
|
1207
|
+
left,
|
|
1208
|
+
effectiveOuterChannelXPoints
|
|
1209
|
+
)
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
ports.push(
|
|
1213
|
+
...createMultiplePorts(
|
|
1214
|
+
`${idPrefix}:B-R`,
|
|
1215
|
+
bottom,
|
|
1216
|
+
right,
|
|
1217
|
+
effectiveOuterChannelXPoints
|
|
1218
|
+
)
|
|
1219
|
+
);
|
|
1220
|
+
ports.push(createPort(`${idPrefix}:B-LP`, bottom, leftPad));
|
|
1221
|
+
ports.push(createPort(`${idPrefix}:B-RP`, bottom, rightPad));
|
|
1222
|
+
ports.push(createPort(`${idPrefix}:B-UJ`, bottom, underjumper));
|
|
1223
|
+
}
|
|
1224
|
+
if (left) {
|
|
1225
|
+
ports.push(createPort(`${idPrefix}:L-LP`, left, leftPad));
|
|
1226
|
+
}
|
|
1227
|
+
ports.push(createPort(`${idPrefix}:R-RP`, right, rightPad));
|
|
1228
|
+
const leftThroughPort = {
|
|
1229
|
+
portId: `${idPrefix}:TJ-LP`,
|
|
1230
|
+
region1: throughjumper,
|
|
1231
|
+
region2: leftPad,
|
|
1232
|
+
d: { x: leftPadCenterX, y: centerY }
|
|
1233
|
+
};
|
|
1234
|
+
throughjumper.ports.push(leftThroughPort);
|
|
1235
|
+
leftPad.ports.push(leftThroughPort);
|
|
1236
|
+
ports.push(leftThroughPort);
|
|
1237
|
+
const rightThroughPort = {
|
|
1238
|
+
portId: `${idPrefix}:TJ-RP`,
|
|
1239
|
+
region1: throughjumper,
|
|
1240
|
+
region2: rightPad,
|
|
1241
|
+
d: { x: rightPadCenterX, y: centerY }
|
|
1242
|
+
};
|
|
1243
|
+
throughjumper.ports.push(rightThroughPort);
|
|
1244
|
+
rightPad.ports.push(rightThroughPort);
|
|
1245
|
+
ports.push(rightThroughPort);
|
|
1246
|
+
if (col > 0) {
|
|
1247
|
+
const prevCell = cells[row][col - 1];
|
|
1248
|
+
ports.push(
|
|
1249
|
+
createPort(
|
|
1250
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:R-LP`,
|
|
1251
|
+
prevCell.right,
|
|
1252
|
+
leftPad
|
|
1253
|
+
)
|
|
1254
|
+
);
|
|
1255
|
+
if (top && prevCell.top) {
|
|
1256
|
+
ports.push(
|
|
1257
|
+
...createMultiplePorts(
|
|
1258
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:T-T`,
|
|
1259
|
+
prevCell.top,
|
|
1260
|
+
top,
|
|
1261
|
+
innerRowChannelPointCount
|
|
1262
|
+
)
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
if (bottom && prevCell.bottom) {
|
|
1266
|
+
ports.push(
|
|
1267
|
+
...createMultiplePorts(
|
|
1268
|
+
`cell_${row}_${col - 1}->cell_${row}_${col}:B-B`,
|
|
1269
|
+
prevCell.bottom,
|
|
1270
|
+
bottom,
|
|
1271
|
+
innerRowChannelPointCount
|
|
1272
|
+
)
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if (row > 0) {
|
|
1277
|
+
const aboveCell = cells[row - 1][col];
|
|
1278
|
+
if (left) {
|
|
1279
|
+
ports.push(
|
|
1280
|
+
...createMultiplePorts(
|
|
1281
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-L`,
|
|
1282
|
+
aboveCell.bottom,
|
|
1283
|
+
left,
|
|
1284
|
+
effectiveOuterChannelXPoints
|
|
1285
|
+
)
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
ports.push(
|
|
1289
|
+
createPort(
|
|
1290
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-LP`,
|
|
1291
|
+
aboveCell.bottom,
|
|
1292
|
+
leftPad
|
|
1293
|
+
)
|
|
1294
|
+
);
|
|
1295
|
+
ports.push(
|
|
1296
|
+
createPort(
|
|
1297
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-UJ`,
|
|
1298
|
+
aboveCell.bottom,
|
|
1299
|
+
underjumper
|
|
1300
|
+
)
|
|
1301
|
+
);
|
|
1302
|
+
ports.push(
|
|
1303
|
+
createPort(
|
|
1304
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-RP`,
|
|
1305
|
+
aboveCell.bottom,
|
|
1306
|
+
rightPad
|
|
1307
|
+
)
|
|
1308
|
+
);
|
|
1309
|
+
ports.push(
|
|
1310
|
+
...createMultiplePorts(
|
|
1311
|
+
`cell_${row - 1}_${col}->cell_${row}_${col}:B-R`,
|
|
1312
|
+
aboveCell.bottom,
|
|
1313
|
+
right,
|
|
1314
|
+
effectiveOuterChannelXPoints
|
|
1315
|
+
)
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return {
|
|
1321
|
+
regions,
|
|
1322
|
+
ports
|
|
1323
|
+
};
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
// lib/JumperGraphSolver/jumper-graph-generator/createConnectionPort.ts
|
|
1327
|
+
var createConnectionPort = (portId, connectionRegion, boundaryRegion, portPosition) => {
|
|
1328
|
+
const port = {
|
|
1329
|
+
portId,
|
|
1330
|
+
region1: connectionRegion,
|
|
1331
|
+
region2: boundaryRegion,
|
|
1332
|
+
d: { x: portPosition.x, y: portPosition.y }
|
|
1333
|
+
};
|
|
1334
|
+
connectionRegion.ports.push(port);
|
|
1335
|
+
boundaryRegion.ports.push(port);
|
|
1336
|
+
return port;
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1339
|
+
// lib/JumperGraphSolver/jumper-graph-generator/createConnectionRegion.ts
|
|
1340
|
+
var CONNECTION_REGION_SIZE = 0.4;
|
|
1341
|
+
var createConnectionRegion = (regionId, x, y) => {
|
|
1342
|
+
const halfSize = CONNECTION_REGION_SIZE / 2;
|
|
1343
|
+
return {
|
|
1344
|
+
regionId,
|
|
1345
|
+
ports: [],
|
|
1346
|
+
d: {
|
|
1347
|
+
bounds: {
|
|
1348
|
+
minX: x - halfSize,
|
|
1349
|
+
maxX: x + halfSize,
|
|
1350
|
+
minY: y - halfSize,
|
|
1351
|
+
maxY: y + halfSize
|
|
1352
|
+
},
|
|
1353
|
+
center: { x, y },
|
|
1354
|
+
isPad: false,
|
|
1355
|
+
isConnectionRegion: true
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
// lib/JumperGraphSolver/jumper-graph-generator/findBoundaryRegion.ts
|
|
1361
|
+
var findBoundaryRegion = (x, y, regions, graphBounds) => {
|
|
1362
|
+
for (const region of regions) {
|
|
1363
|
+
if (region.d.isPad || region.d.isThroughJumper) continue;
|
|
1364
|
+
const bounds = region.d.bounds;
|
|
1365
|
+
if (Math.abs(x - bounds.minX) < 0.01 && y >= bounds.minY && y <= bounds.maxY) {
|
|
1366
|
+
return { region, portPosition: { x: bounds.minX, y } };
|
|
1367
|
+
}
|
|
1368
|
+
if (Math.abs(x - bounds.maxX) < 0.01 && y >= bounds.minY && y <= bounds.maxY) {
|
|
1369
|
+
return { region, portPosition: { x: bounds.maxX, y } };
|
|
1370
|
+
}
|
|
1371
|
+
if (Math.abs(y - bounds.minY) < 0.01 && x >= bounds.minX && x <= bounds.maxX) {
|
|
1372
|
+
return { region, portPosition: { x, y: bounds.minY } };
|
|
1373
|
+
}
|
|
1374
|
+
if (Math.abs(y - bounds.maxY) < 0.01 && x >= bounds.minX && x <= bounds.maxX) {
|
|
1375
|
+
return { region, portPosition: { x, y: bounds.maxY } };
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
let closestRegion = null;
|
|
1379
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
1380
|
+
let closestPortPosition = { x, y };
|
|
1381
|
+
for (const region of regions) {
|
|
1382
|
+
if (region.d.isPad || region.d.isThroughJumper) continue;
|
|
1383
|
+
const bounds = region.d.bounds;
|
|
1384
|
+
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;
|
|
1385
|
+
if (!isOuterRegion) continue;
|
|
1386
|
+
const clampedX = Math.max(bounds.minX, Math.min(bounds.maxX, x));
|
|
1387
|
+
const clampedY = Math.max(bounds.minY, Math.min(bounds.maxY, y));
|
|
1388
|
+
const dist = Math.sqrt((x - clampedX) ** 2 + (y - clampedY) ** 2);
|
|
1389
|
+
if (dist < closestDistance) {
|
|
1390
|
+
closestDistance = dist;
|
|
1391
|
+
closestRegion = region;
|
|
1392
|
+
if (x < bounds.minX) {
|
|
1393
|
+
closestPortPosition = { x: bounds.minX, y: clampedY };
|
|
1394
|
+
} else if (x > bounds.maxX) {
|
|
1395
|
+
closestPortPosition = { x: bounds.maxX, y: clampedY };
|
|
1396
|
+
} else if (y < bounds.minY) {
|
|
1397
|
+
closestPortPosition = { x: clampedX, y: bounds.minY };
|
|
1398
|
+
} else if (y > bounds.maxY) {
|
|
1399
|
+
closestPortPosition = { x: clampedX, y: bounds.maxY };
|
|
1400
|
+
} else {
|
|
1401
|
+
closestPortPosition = { x: clampedX, y: clampedY };
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (closestRegion) {
|
|
1406
|
+
return { region: closestRegion, portPosition: closestPortPosition };
|
|
1407
|
+
}
|
|
1408
|
+
return null;
|
|
1409
|
+
};
|
|
1410
|
+
|
|
1411
|
+
// lib/JumperGraphSolver/jumper-graph-generator/createGraphWithConnectionsFromBaseGraph.ts
|
|
1412
|
+
var createGraphWithConnectionsFromBaseGraph = (baseGraph, xyConnections) => {
|
|
1413
|
+
const regions = [...baseGraph.regions];
|
|
1414
|
+
const ports = [...baseGraph.ports];
|
|
1415
|
+
const connections = [];
|
|
1416
|
+
const graphBounds = calculateGraphBounds(baseGraph.regions);
|
|
1417
|
+
for (const xyConn of xyConnections) {
|
|
1418
|
+
const { start, end, connectionId } = xyConn;
|
|
1419
|
+
const startRegion = createConnectionRegion(
|
|
1420
|
+
`conn:${connectionId}:start`,
|
|
1421
|
+
start.x,
|
|
1422
|
+
start.y
|
|
1423
|
+
);
|
|
1424
|
+
regions.push(startRegion);
|
|
1425
|
+
const endRegion = createConnectionRegion(
|
|
1426
|
+
`conn:${connectionId}:end`,
|
|
1427
|
+
end.x,
|
|
1428
|
+
end.y
|
|
1429
|
+
);
|
|
1430
|
+
regions.push(endRegion);
|
|
1431
|
+
const startBoundary = findBoundaryRegion(
|
|
1432
|
+
start.x,
|
|
1433
|
+
start.y,
|
|
1434
|
+
baseGraph.regions,
|
|
1435
|
+
graphBounds
|
|
1436
|
+
);
|
|
1437
|
+
if (startBoundary) {
|
|
1438
|
+
const startPort = createConnectionPort(
|
|
1439
|
+
`conn:${connectionId}:start-port`,
|
|
1440
|
+
startRegion,
|
|
1441
|
+
startBoundary.region,
|
|
1442
|
+
startBoundary.portPosition
|
|
1443
|
+
);
|
|
1444
|
+
ports.push(startPort);
|
|
1445
|
+
}
|
|
1446
|
+
const endBoundary = findBoundaryRegion(
|
|
1447
|
+
end.x,
|
|
1448
|
+
end.y,
|
|
1449
|
+
baseGraph.regions,
|
|
1450
|
+
graphBounds
|
|
1451
|
+
);
|
|
1452
|
+
if (endBoundary) {
|
|
1453
|
+
const endPort = createConnectionPort(
|
|
1454
|
+
`conn:${connectionId}:end-port`,
|
|
1455
|
+
endRegion,
|
|
1456
|
+
endBoundary.region,
|
|
1457
|
+
endBoundary.portPosition
|
|
1458
|
+
);
|
|
1459
|
+
ports.push(endPort);
|
|
1460
|
+
}
|
|
1461
|
+
const connection = {
|
|
1462
|
+
connectionId,
|
|
1463
|
+
mutuallyConnectedNetworkId: connectionId,
|
|
1464
|
+
startRegion,
|
|
1465
|
+
endRegion
|
|
1466
|
+
};
|
|
1467
|
+
connections.push(connection);
|
|
1468
|
+
}
|
|
1469
|
+
return {
|
|
1470
|
+
regions,
|
|
1471
|
+
ports,
|
|
1472
|
+
connections
|
|
1473
|
+
};
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
// lib/HyperGraphSolver.ts
|
|
1477
|
+
import { BaseSolver } from "@tscircuit/solver-utils";
|
|
1478
|
+
|
|
1479
|
+
// lib/convertSerializedHyperGraphToHyperGraph.ts
|
|
1480
|
+
var convertSerializedHyperGraphToHyperGraph = (inputGraph) => {
|
|
1481
|
+
if (inputGraph.ports.length > 0 && "region1" in inputGraph.ports[0] && typeof inputGraph.ports[0].region1 === "object") {
|
|
1482
|
+
return inputGraph;
|
|
1483
|
+
}
|
|
1484
|
+
const portMap = /* @__PURE__ */ new Map();
|
|
1485
|
+
const regionMap = /* @__PURE__ */ new Map();
|
|
1486
|
+
for (const region of inputGraph.regions) {
|
|
1487
|
+
const { assignments: _, ...regionWithoutAssignments } = region;
|
|
1488
|
+
regionMap.set(region.regionId, {
|
|
1489
|
+
...regionWithoutAssignments,
|
|
1490
|
+
ports: [],
|
|
1491
|
+
assignments: void 0
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
for (const port of inputGraph.ports) {
|
|
1495
|
+
const region1 = regionMap.get(port.region1Id ?? port.region1?.regionId);
|
|
1496
|
+
const region2 = regionMap.get(port.region2Id ?? port.region2?.regionId);
|
|
1497
|
+
const hydratedPort = {
|
|
1498
|
+
portId: port.portId,
|
|
1499
|
+
region1,
|
|
1500
|
+
region2,
|
|
1501
|
+
d: port.d
|
|
1502
|
+
};
|
|
1503
|
+
portMap.set(port.portId, hydratedPort);
|
|
1504
|
+
region1.ports.push(hydratedPort);
|
|
1505
|
+
region2.ports.push(hydratedPort);
|
|
1506
|
+
}
|
|
1507
|
+
return {
|
|
1508
|
+
ports: Array.from(portMap.values()),
|
|
1509
|
+
regions: Array.from(regionMap.values())
|
|
1510
|
+
};
|
|
1511
|
+
};
|
|
1512
|
+
|
|
1513
|
+
// lib/convertSerializedConnectionsToConnections.ts
|
|
1514
|
+
var convertSerializedConnectionsToConnections = (inputConnections, graph) => {
|
|
1515
|
+
const connections = [];
|
|
1516
|
+
for (const inputConn of inputConnections) {
|
|
1517
|
+
if ("startRegionId" in inputConn) {
|
|
1518
|
+
connections.push({
|
|
1519
|
+
connectionId: inputConn.connectionId,
|
|
1520
|
+
mutuallyConnectedNetworkId: inputConn.connectionId,
|
|
1521
|
+
startRegion: graph.regions.find(
|
|
1522
|
+
(region) => region.regionId === inputConn.startRegionId
|
|
1523
|
+
),
|
|
1524
|
+
endRegion: graph.regions.find(
|
|
1525
|
+
(region) => region.regionId === inputConn.endRegionId
|
|
1526
|
+
)
|
|
1527
|
+
});
|
|
1528
|
+
} else {
|
|
1529
|
+
connections.push(inputConn);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return connections;
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
// lib/PriorityQueue.ts
|
|
1536
|
+
var PriorityQueue = class {
|
|
1537
|
+
// Store the heap as an array. Index 0 is the root (highest priority/smallest 'f').
|
|
1538
|
+
heap = [];
|
|
1539
|
+
maxSize;
|
|
1540
|
+
/**
|
|
1541
|
+
* Creates a new Priority Queue.
|
|
1542
|
+
* @param nodes An optional initial array of nodes to populate the queue.
|
|
1543
|
+
* @param maxSize The maximum number of elements the queue can hold. Defaults to 10,000.
|
|
1544
|
+
*/
|
|
1545
|
+
constructor(nodes = [], maxSize = 1e4) {
|
|
1546
|
+
this.maxSize = maxSize;
|
|
1547
|
+
if (nodes.length > 0) {
|
|
1548
|
+
this.heap = [...nodes].sort((a, b) => a.f - b.f).slice(0, this.maxSize);
|
|
1549
|
+
for (let i = Math.floor(this.heap.length / 2) - 1; i >= 0; i--) {
|
|
1550
|
+
this._siftDown(i);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Returns the number of elements currently in the queue.
|
|
1556
|
+
*/
|
|
1557
|
+
get size() {
|
|
1558
|
+
return this.heap.length;
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Checks if the queue is empty.
|
|
1562
|
+
*/
|
|
1563
|
+
isEmpty() {
|
|
1564
|
+
return this.heap.length === 0;
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Returns the node with the highest priority (smallest 'f') without removing it.
|
|
1568
|
+
* Returns null if the queue is empty.
|
|
1569
|
+
* @returns The highest priority node or null.
|
|
1570
|
+
*/
|
|
1571
|
+
peek() {
|
|
1572
|
+
if (this.isEmpty()) {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
return this.heap[0] ?? null;
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Returns up to N nodes with the highest priority (smallest 'f') without removing them.
|
|
1579
|
+
* Nodes are returned sorted by priority (smallest 'f' first).
|
|
1580
|
+
* @param count Maximum number of nodes to return.
|
|
1581
|
+
* @returns Array of nodes sorted by priority.
|
|
1582
|
+
*/
|
|
1583
|
+
peekMany(count) {
|
|
1584
|
+
return [...this.heap].sort((a, b) => a.f - b.f).slice(0, count);
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Removes and returns the node with the highest priority (smallest 'f').
|
|
1588
|
+
* Returns null if the queue is empty.
|
|
1589
|
+
* Maintains the heap property.
|
|
1590
|
+
* @returns The highest priority node or null.
|
|
1591
|
+
*/
|
|
1592
|
+
dequeue() {
|
|
1593
|
+
if (this.isEmpty()) {
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
const minNode = this.heap[0];
|
|
1597
|
+
const lastNode = this.heap.pop();
|
|
1598
|
+
if (this.heap.length === 0 && lastNode !== void 0) {
|
|
1599
|
+
return minNode;
|
|
1600
|
+
}
|
|
1601
|
+
if (lastNode !== void 0) {
|
|
1602
|
+
this.heap[0] = lastNode;
|
|
1603
|
+
this._siftDown(0);
|
|
1604
|
+
}
|
|
1605
|
+
return minNode;
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Adds a new node to the queue.
|
|
1609
|
+
* Maintains the heap property.
|
|
1610
|
+
* If the queue is full (at maxSize), the node is not added.
|
|
1611
|
+
* @param node The node to add.
|
|
1612
|
+
*/
|
|
1613
|
+
enqueue(node) {
|
|
1614
|
+
if (this.heap.length >= this.maxSize) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
this.heap.push(node);
|
|
1618
|
+
this._siftUp(this.heap.length - 1);
|
|
1619
|
+
}
|
|
1620
|
+
// --- Private Helper Methods ---
|
|
1621
|
+
/**
|
|
1622
|
+
* Moves the node at the given index up the heap to maintain the heap property.
|
|
1623
|
+
* @param index The index of the node to sift up.
|
|
1624
|
+
*/
|
|
1625
|
+
_siftUp(index) {
|
|
1626
|
+
let currentIndex = index;
|
|
1627
|
+
while (currentIndex > 0) {
|
|
1628
|
+
const parentIndex = this._parentIndex(currentIndex);
|
|
1629
|
+
if (this.heap[parentIndex].f <= this.heap[currentIndex].f) {
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
this._swap(currentIndex, parentIndex);
|
|
1633
|
+
currentIndex = parentIndex;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Moves the node at the given index down the heap to maintain the heap property.
|
|
1638
|
+
* @param index The index of the node to sift down.
|
|
1639
|
+
*/
|
|
1640
|
+
_siftDown(index) {
|
|
1641
|
+
let currentIndex = index;
|
|
1642
|
+
const heapSize = this.heap.length;
|
|
1643
|
+
while (true) {
|
|
1644
|
+
const leftChildIndex = this._leftChildIndex(currentIndex);
|
|
1645
|
+
const rightChildIndex = this._rightChildIndex(currentIndex);
|
|
1646
|
+
let smallestIndex = currentIndex;
|
|
1647
|
+
if (leftChildIndex < heapSize && this.heap[leftChildIndex].f < this.heap[smallestIndex].f) {
|
|
1648
|
+
smallestIndex = leftChildIndex;
|
|
1649
|
+
}
|
|
1650
|
+
if (rightChildIndex < heapSize && this.heap[rightChildIndex].f < this.heap[smallestIndex].f) {
|
|
1651
|
+
smallestIndex = rightChildIndex;
|
|
1652
|
+
}
|
|
1653
|
+
if (smallestIndex !== currentIndex) {
|
|
1654
|
+
this._swap(currentIndex, smallestIndex);
|
|
1655
|
+
currentIndex = smallestIndex;
|
|
1656
|
+
} else {
|
|
1657
|
+
break;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Swaps two elements in the heap array.
|
|
1663
|
+
* @param i Index of the first element.
|
|
1664
|
+
* @param j Index of the second element.
|
|
1665
|
+
*/
|
|
1666
|
+
_swap(i, j) {
|
|
1667
|
+
;
|
|
1668
|
+
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
|
|
1669
|
+
}
|
|
1670
|
+
/** Calculates the parent index of a node. */
|
|
1671
|
+
_parentIndex(index) {
|
|
1672
|
+
return Math.floor((index - 1) / 2);
|
|
1673
|
+
}
|
|
1674
|
+
/** Calculates the left child index of a node. */
|
|
1675
|
+
_leftChildIndex(index) {
|
|
1676
|
+
return 2 * index + 1;
|
|
1677
|
+
}
|
|
1678
|
+
/** Calculates the right child index of a node. */
|
|
1679
|
+
_rightChildIndex(index) {
|
|
1680
|
+
return 2 * index + 2;
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
// lib/HyperGraphSolver.ts
|
|
1685
|
+
var HyperGraphSolver = class extends BaseSolver {
|
|
1686
|
+
constructor(input) {
|
|
1687
|
+
super();
|
|
1688
|
+
this.input = input;
|
|
1689
|
+
this.graph = convertSerializedHyperGraphToHyperGraph(input.inputGraph);
|
|
1690
|
+
for (const region of this.graph.regions) {
|
|
1691
|
+
region.assignments = [];
|
|
1692
|
+
}
|
|
1693
|
+
this.connections = convertSerializedConnectionsToConnections(
|
|
1694
|
+
input.inputConnections,
|
|
1695
|
+
this.graph
|
|
1696
|
+
);
|
|
1697
|
+
if (input.greedyMultiplier !== void 0)
|
|
1698
|
+
this.greedyMultiplier = input.greedyMultiplier;
|
|
1699
|
+
if (input.rippingEnabled !== void 0)
|
|
1700
|
+
this.rippingEnabled = input.rippingEnabled;
|
|
1701
|
+
if (input.ripCost !== void 0) this.ripCost = input.ripCost;
|
|
1702
|
+
this.unprocessedConnections = [...this.connections];
|
|
1703
|
+
this.candidateQueue = new PriorityQueue();
|
|
1704
|
+
this.beginNewConnection();
|
|
1705
|
+
}
|
|
1706
|
+
graph;
|
|
1707
|
+
connections;
|
|
1708
|
+
candidateQueue;
|
|
1709
|
+
unprocessedConnections;
|
|
1710
|
+
solvedRoutes = [];
|
|
1711
|
+
currentConnection = null;
|
|
1712
|
+
currentEndRegion = null;
|
|
1713
|
+
greedyMultiplier = 1;
|
|
1714
|
+
rippingEnabled = false;
|
|
1715
|
+
ripCost = 0;
|
|
1716
|
+
lastCandidate = null;
|
|
1717
|
+
visitedPointsForCurrentConnection = /* @__PURE__ */ new Map();
|
|
1718
|
+
computeH(candidate) {
|
|
1719
|
+
return this.estimateCostToEnd(candidate.port);
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* OVERRIDE THIS
|
|
1723
|
+
*
|
|
1724
|
+
* Return the estimated remaining cost to the end of the route. You must
|
|
1725
|
+
* first understand the UNIT of your costs. If it's distance, then this could
|
|
1726
|
+
* be something like distance(port, this.currentEndRegion.d.center)
|
|
1727
|
+
*/
|
|
1728
|
+
estimateCostToEnd(port) {
|
|
1729
|
+
return 0;
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* OPTIONALLY OVERRIDE THIS
|
|
1733
|
+
*
|
|
1734
|
+
* This is a penalty for using a port that is not relative to a connection,
|
|
1735
|
+
* e.g. maybe this port is in a special area of congestion. Use this to
|
|
1736
|
+
* penalize ports that are e.g. likely to block off connections, you may want
|
|
1737
|
+
* to use port.ripCount to help determine this penalty, or you can use port
|
|
1738
|
+
* position, region volume etc.
|
|
1739
|
+
*/
|
|
1740
|
+
getPortUsagePenalty(port) {
|
|
1741
|
+
return 0;
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* OVERRIDE THIS
|
|
1745
|
+
*
|
|
1746
|
+
* Return the cost of using two ports in the region, make sure to consider
|
|
1747
|
+
* existing assignments. You may use this to penalize intersections
|
|
1748
|
+
*/
|
|
1749
|
+
computeIncreasedRegionCostIfPortsAreUsed(region, port1, port2) {
|
|
1750
|
+
return 0;
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* OPTIONALLY OVERRIDE THIS
|
|
1754
|
+
*
|
|
1755
|
+
* Return the assignments that would need to be ripped if the given ports
|
|
1756
|
+
* are used together in the region. This is used to determine if adopting
|
|
1757
|
+
* a route would require ripping other routes due to problematic crossings.
|
|
1758
|
+
*/
|
|
1759
|
+
getRipsRequiredForPortUsage(_region, _port1, _port2) {
|
|
1760
|
+
return [];
|
|
1761
|
+
}
|
|
1762
|
+
computeG(candidate) {
|
|
1763
|
+
return candidate.parent.g + this.computeIncreasedRegionCostIfPortsAreUsed(
|
|
1764
|
+
candidate.lastRegion,
|
|
1765
|
+
candidate.lastPort,
|
|
1766
|
+
candidate.port
|
|
1767
|
+
) + (candidate.ripRequired ? this.ripCost : 0) + this.getPortUsagePenalty(candidate.port);
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Return a subset of the candidates for entering a region. These candidates
|
|
1771
|
+
* are all possible ways to enter the region- you can e.g. return the middle
|
|
1772
|
+
* port to make it so that you're not queueing candidates that are likely
|
|
1773
|
+
* redundant.
|
|
1774
|
+
*/
|
|
1775
|
+
selectCandidatesForEnteringRegion(candidates) {
|
|
1776
|
+
return candidates;
|
|
1777
|
+
}
|
|
1778
|
+
getNextCandidates(currentCandidate) {
|
|
1779
|
+
const currentRegion = currentCandidate.nextRegion;
|
|
1780
|
+
const currentPort = currentCandidate.port;
|
|
1781
|
+
const nextCandidatesByRegion = {};
|
|
1782
|
+
for (const port of currentRegion.ports) {
|
|
1783
|
+
if (port === currentCandidate.port) continue;
|
|
1784
|
+
const ripRequired = port.assignment && port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId;
|
|
1785
|
+
const newCandidate = {
|
|
1786
|
+
port,
|
|
1787
|
+
hops: currentCandidate.hops + 1,
|
|
1788
|
+
parent: currentCandidate,
|
|
1789
|
+
lastRegion: currentRegion,
|
|
1790
|
+
nextRegion: port.region1 === currentRegion ? port.region2 : port.region1,
|
|
1791
|
+
lastPort: currentPort,
|
|
1792
|
+
ripRequired
|
|
1793
|
+
};
|
|
1794
|
+
if (!this.rippingEnabled && newCandidate.ripRequired) {
|
|
1795
|
+
continue;
|
|
1796
|
+
}
|
|
1797
|
+
nextCandidatesByRegion[newCandidate.nextRegion.regionId] ??= [];
|
|
1798
|
+
nextCandidatesByRegion[newCandidate.nextRegion.regionId].push(
|
|
1799
|
+
newCandidate
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
const nextCandidates = [];
|
|
1803
|
+
for (const regionId in nextCandidatesByRegion) {
|
|
1804
|
+
const nextCandidatesInRegion = nextCandidatesByRegion[regionId];
|
|
1805
|
+
nextCandidates.push(
|
|
1806
|
+
...this.selectCandidatesForEnteringRegion(nextCandidatesInRegion)
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
for (const nextCandidate of nextCandidates) {
|
|
1810
|
+
nextCandidate.g = this.computeG(nextCandidate);
|
|
1811
|
+
nextCandidate.h = this.computeH(nextCandidate);
|
|
1812
|
+
nextCandidate.f = nextCandidate.g + nextCandidate.h * this.greedyMultiplier;
|
|
1813
|
+
}
|
|
1814
|
+
return nextCandidates;
|
|
1815
|
+
}
|
|
1816
|
+
processSolvedRoute(finalCandidate) {
|
|
1817
|
+
const solvedRoute = {
|
|
1818
|
+
path: [],
|
|
1819
|
+
connection: this.currentConnection,
|
|
1820
|
+
requiredRip: false
|
|
1821
|
+
};
|
|
1822
|
+
let cursorCandidate = finalCandidate;
|
|
1823
|
+
let anyRipsRequired = false;
|
|
1824
|
+
while (cursorCandidate) {
|
|
1825
|
+
anyRipsRequired ||= !!cursorCandidate.ripRequired;
|
|
1826
|
+
solvedRoute.path.unshift(cursorCandidate);
|
|
1827
|
+
cursorCandidate = cursorCandidate.parent;
|
|
1828
|
+
}
|
|
1829
|
+
const routesToRip = /* @__PURE__ */ new Set();
|
|
1830
|
+
if (anyRipsRequired) {
|
|
1831
|
+
solvedRoute.requiredRip = true;
|
|
1832
|
+
for (const candidate of solvedRoute.path) {
|
|
1833
|
+
if (candidate.port.assignment && candidate.port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId) {
|
|
1834
|
+
routesToRip.add(candidate.port.assignment.solvedRoute);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
for (const candidate of solvedRoute.path) {
|
|
1839
|
+
if (!candidate.lastPort || !candidate.lastRegion) continue;
|
|
1840
|
+
const ripsRequired = this.getRipsRequiredForPortUsage(
|
|
1841
|
+
candidate.lastRegion,
|
|
1842
|
+
candidate.lastPort,
|
|
1843
|
+
candidate.port
|
|
1844
|
+
);
|
|
1845
|
+
for (const assignment of ripsRequired) {
|
|
1846
|
+
routesToRip.add(assignment.solvedRoute);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
if (routesToRip.size > 0) {
|
|
1850
|
+
solvedRoute.requiredRip = true;
|
|
1851
|
+
for (const route of routesToRip) {
|
|
1852
|
+
this.ripSolvedRoute(route);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
for (const candidate of solvedRoute.path) {
|
|
1856
|
+
candidate.port.assignment = {
|
|
1857
|
+
solvedRoute,
|
|
1858
|
+
connection: this.currentConnection
|
|
1859
|
+
};
|
|
1860
|
+
if (!candidate.lastPort) continue;
|
|
1861
|
+
const regionPortAssignment = {
|
|
1862
|
+
regionPort1: candidate.lastPort,
|
|
1863
|
+
regionPort2: candidate.port,
|
|
1864
|
+
region: candidate.lastRegion,
|
|
1865
|
+
connection: this.currentConnection,
|
|
1866
|
+
solvedRoute
|
|
1867
|
+
};
|
|
1868
|
+
candidate.lastRegion.assignments?.push(regionPortAssignment);
|
|
1869
|
+
}
|
|
1870
|
+
this.solvedRoutes.push(solvedRoute);
|
|
1871
|
+
this.routeSolvedHook(solvedRoute);
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* OPTIONALLY OVERRIDE THIS
|
|
1875
|
+
*
|
|
1876
|
+
* You can override this to perform actions after a route is solved, e.g.
|
|
1877
|
+
* you may want to detect if a solvedRoute.requiredRip is true, in which
|
|
1878
|
+
* case you might want to execute a "random rip" to avoid loops or check
|
|
1879
|
+
* if we've exceeded a maximum number of rips.
|
|
1880
|
+
*
|
|
1881
|
+
* You can also use this to shuffle unprocessed routes if a rip occurred, this
|
|
1882
|
+
* can also help avoid loops
|
|
1883
|
+
*/
|
|
1884
|
+
routeSolvedHook(solvedRoute) {
|
|
1885
|
+
}
|
|
1886
|
+
ripSolvedRoute(solvedRoute) {
|
|
1887
|
+
for (const port of solvedRoute.path.map((candidate) => candidate.port)) {
|
|
1888
|
+
port.ripCount = (port.ripCount ?? 0) + 1;
|
|
1889
|
+
port.region1.assignments = port.region1.assignments?.filter(
|
|
1890
|
+
(a) => a.regionPort1 !== port && a.regionPort2 !== port
|
|
1891
|
+
);
|
|
1892
|
+
port.region2.assignments = port.region2.assignments?.filter(
|
|
1893
|
+
(a) => a.regionPort1 !== port && a.regionPort2 !== port
|
|
1894
|
+
);
|
|
1895
|
+
port.assignment = void 0;
|
|
1896
|
+
}
|
|
1897
|
+
this.solvedRoutes = this.solvedRoutes.filter((r) => r !== solvedRoute);
|
|
1898
|
+
this.unprocessedConnections.push(solvedRoute.connection);
|
|
1899
|
+
}
|
|
1900
|
+
beginNewConnection() {
|
|
1901
|
+
this.currentConnection = this.unprocessedConnections.shift();
|
|
1902
|
+
this.currentEndRegion = this.currentConnection.endRegion;
|
|
1903
|
+
this.candidateQueue = new PriorityQueue();
|
|
1904
|
+
this.visitedPointsForCurrentConnection.clear();
|
|
1905
|
+
for (const port of this.currentConnection.startRegion.ports) {
|
|
1906
|
+
this.candidateQueue.enqueue({
|
|
1907
|
+
port,
|
|
1908
|
+
g: 0,
|
|
1909
|
+
h: 0,
|
|
1910
|
+
f: 0,
|
|
1911
|
+
hops: 0,
|
|
1912
|
+
ripRequired: false,
|
|
1913
|
+
nextRegion: port.region1 === this.currentConnection.startRegion ? port.region2 : port.region1
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
_step() {
|
|
1918
|
+
let currentCandidate = this.candidateQueue.dequeue();
|
|
1919
|
+
if (!currentCandidate) {
|
|
1920
|
+
this.failed = true;
|
|
1921
|
+
this.error = "Ran out of candidates";
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
let visitedPointGScore = this.visitedPointsForCurrentConnection.get(currentCandidate.port.portId);
|
|
1925
|
+
while (true) {
|
|
1926
|
+
if (!currentCandidate) break;
|
|
1927
|
+
if (visitedPointGScore === void 0) break;
|
|
1928
|
+
if (currentCandidate.g < visitedPointGScore) break;
|
|
1929
|
+
currentCandidate = this.candidateQueue.dequeue();
|
|
1930
|
+
if (!currentCandidate) break;
|
|
1931
|
+
visitedPointGScore = this.visitedPointsForCurrentConnection.get(
|
|
1932
|
+
currentCandidate.port.portId
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
if (!currentCandidate) {
|
|
1936
|
+
this.failed = true;
|
|
1937
|
+
this.error = "Ran out of candidates";
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
this.lastCandidate = currentCandidate;
|
|
1941
|
+
this.visitedPointsForCurrentConnection.set(
|
|
1942
|
+
currentCandidate.port.portId,
|
|
1943
|
+
currentCandidate.g
|
|
1944
|
+
);
|
|
1945
|
+
if (currentCandidate.nextRegion === this.currentEndRegion) {
|
|
1946
|
+
this.processSolvedRoute(currentCandidate);
|
|
1947
|
+
if (this.unprocessedConnections.length === 0) {
|
|
1948
|
+
this.solved = true;
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
this.beginNewConnection();
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
const nextCandidates = this.getNextCandidates(currentCandidate);
|
|
1955
|
+
for (const nextCandidate of nextCandidates) {
|
|
1956
|
+
this.candidateQueue.enqueue(nextCandidate);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
|
|
1961
|
+
// lib/JumperGraphSolver/visualizeJumperGraph.ts
|
|
1962
|
+
var visualizeJumperGraph = (graph, options) => {
|
|
1963
|
+
const graphics = {
|
|
1964
|
+
arrows: [],
|
|
1965
|
+
circles: [],
|
|
1966
|
+
title: "Jumper Graph",
|
|
1967
|
+
lines: [],
|
|
1968
|
+
points: [],
|
|
1969
|
+
rects: [],
|
|
1970
|
+
texts: [],
|
|
1971
|
+
coordinateSystem: "cartesian"
|
|
1972
|
+
};
|
|
1973
|
+
for (const region of graph.regions) {
|
|
1974
|
+
const { bounds, isPad, isThroughJumper, isConnectionRegion } = region.d;
|
|
1975
|
+
const centerX = (bounds.minX + bounds.maxX) / 2;
|
|
1976
|
+
const centerY = (bounds.minY + bounds.maxY) / 2;
|
|
1977
|
+
const width = bounds.maxX - bounds.minX;
|
|
1978
|
+
const height = bounds.maxY - bounds.minY;
|
|
1979
|
+
let fill;
|
|
1980
|
+
if (isConnectionRegion) {
|
|
1981
|
+
fill = "rgba(255, 100, 255, 0.6)";
|
|
1982
|
+
} else if (isThroughJumper) {
|
|
1983
|
+
fill = "rgba(100, 200, 100, 0.5)";
|
|
1984
|
+
} else if (isPad) {
|
|
1985
|
+
fill = "rgba(255, 200, 100, 0.5)";
|
|
1986
|
+
} else {
|
|
1987
|
+
fill = "rgba(200, 200, 255, 0.1)";
|
|
1988
|
+
}
|
|
1989
|
+
graphics.rects.push({
|
|
1990
|
+
center: { x: centerX, y: centerY },
|
|
1991
|
+
width: width - 0.1,
|
|
1992
|
+
height: height - 0.1,
|
|
1993
|
+
fill
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
if (!options?.hidePortPoints) {
|
|
1997
|
+
for (const port of graph.ports) {
|
|
1998
|
+
const r1Name = port.region1.regionId.split(":").pop() ?? port.region1.regionId;
|
|
1999
|
+
const r2Name = port.region2.regionId.split(":").pop() ?? port.region2.regionId;
|
|
2000
|
+
graphics.circles.push({
|
|
2001
|
+
center: { x: port.d.x, y: port.d.y },
|
|
2002
|
+
radius: 0.05,
|
|
2003
|
+
fill: "rgba(128, 128, 128, 0.5)",
|
|
2004
|
+
label: `${r1Name}-${r2Name}`
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
if (!options?.hideRegionPortLines) {
|
|
2009
|
+
for (const port of graph.ports) {
|
|
2010
|
+
const r1Center = {
|
|
2011
|
+
x: (port.region1.d.bounds.minX + port.region1.d.bounds.maxX) / 2,
|
|
2012
|
+
y: (port.region1.d.bounds.minY + port.region1.d.bounds.maxY) / 2
|
|
2013
|
+
};
|
|
2014
|
+
const r2Center = {
|
|
2015
|
+
x: (port.region2.d.bounds.minX + port.region2.d.bounds.maxX) / 2,
|
|
2016
|
+
y: (port.region2.d.bounds.minY + port.region2.d.bounds.maxY) / 2
|
|
2017
|
+
};
|
|
2018
|
+
graphics.lines.push({
|
|
2019
|
+
points: [r1Center, { x: port.d.x, y: port.d.y }, r2Center],
|
|
2020
|
+
strokeColor: "rgba(100, 100, 100, 0.3)"
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
if (options?.connections && !options?.hideConnectionLines) {
|
|
2025
|
+
for (const connection of options.connections) {
|
|
2026
|
+
const startRegion = connection.startRegion;
|
|
2027
|
+
const endRegion = connection.endRegion;
|
|
2028
|
+
const startCenter = {
|
|
2029
|
+
x: (startRegion.d.bounds.minX + startRegion.d.bounds.maxX) / 2,
|
|
2030
|
+
y: (startRegion.d.bounds.minY + startRegion.d.bounds.maxY) / 2
|
|
2031
|
+
};
|
|
2032
|
+
const endCenter = {
|
|
2033
|
+
x: (endRegion.d.bounds.minX + endRegion.d.bounds.maxX) / 2,
|
|
2034
|
+
y: (endRegion.d.bounds.minY + endRegion.d.bounds.maxY) / 2
|
|
2035
|
+
};
|
|
2036
|
+
const midX = (startCenter.x + endCenter.x) / 2;
|
|
2037
|
+
const midY = (startCenter.y + endCenter.y) / 2;
|
|
2038
|
+
graphics.lines.push({
|
|
2039
|
+
points: [startCenter, endCenter],
|
|
2040
|
+
strokeColor: "rgba(255, 50, 150, 0.8)",
|
|
2041
|
+
strokeDash: "3 3"
|
|
2042
|
+
});
|
|
2043
|
+
graphics.points.push({
|
|
2044
|
+
x: midX,
|
|
2045
|
+
y: midY,
|
|
2046
|
+
color: "rgba(200, 0, 100, 1)",
|
|
2047
|
+
label: connection.connectionId
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return graphics;
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
// lib/JumperGraphSolver/visualizeJumperGraphSolver.ts
|
|
2055
|
+
var getConnectionColor = (connectionId, alpha = 0.8) => {
|
|
2056
|
+
let hash = 0;
|
|
2057
|
+
for (let i = 0; i < connectionId.length; i++) {
|
|
2058
|
+
hash = connectionId.charCodeAt(i) * 17777 + ((hash << 5) - hash);
|
|
2059
|
+
}
|
|
2060
|
+
const hue = Math.abs(hash) % 360;
|
|
2061
|
+
return `hsla(${hue}, 70%, 50%, ${alpha})`;
|
|
2062
|
+
};
|
|
2063
|
+
var visualizeJumperGraphSolver = (solver) => {
|
|
2064
|
+
const jumperGraph = {
|
|
2065
|
+
regions: solver.graph.regions,
|
|
2066
|
+
ports: solver.graph.ports
|
|
2067
|
+
};
|
|
2068
|
+
const graphics = visualizeJumperGraph(jumperGraph, {
|
|
2069
|
+
connections: solver.connections,
|
|
2070
|
+
...solver.iterations > 0 ? {
|
|
2071
|
+
hideRegionPortLines: true,
|
|
2072
|
+
hideConnectionLines: true,
|
|
2073
|
+
hidePortPoints: true
|
|
2074
|
+
} : {}
|
|
2075
|
+
});
|
|
2076
|
+
if (solver.currentConnection && !solver.solved) {
|
|
2077
|
+
const connectionColor = getConnectionColor(
|
|
2078
|
+
solver.currentConnection.connectionId
|
|
2079
|
+
);
|
|
2080
|
+
const startRegion = solver.currentConnection.startRegion;
|
|
2081
|
+
const endRegion = solver.currentConnection.endRegion;
|
|
2082
|
+
const startCenter = {
|
|
2083
|
+
x: (startRegion.d.bounds.minX + startRegion.d.bounds.maxX) / 2,
|
|
2084
|
+
y: (startRegion.d.bounds.minY + startRegion.d.bounds.maxY) / 2
|
|
2085
|
+
};
|
|
2086
|
+
const endCenter = {
|
|
2087
|
+
x: (endRegion.d.bounds.minX + endRegion.d.bounds.maxX) / 2,
|
|
2088
|
+
y: (endRegion.d.bounds.minY + endRegion.d.bounds.maxY) / 2
|
|
2089
|
+
};
|
|
2090
|
+
graphics.lines.push({
|
|
2091
|
+
points: [startCenter, endCenter],
|
|
2092
|
+
strokeColor: connectionColor,
|
|
2093
|
+
strokeDash: "10 5"
|
|
2094
|
+
});
|
|
2095
|
+
graphics.points.push({
|
|
2096
|
+
x: startCenter.x - 0.1,
|
|
2097
|
+
y: startCenter.y + 0.1,
|
|
2098
|
+
color: connectionColor,
|
|
2099
|
+
label: [solver.currentConnection.connectionId, "start"].join("\n")
|
|
2100
|
+
});
|
|
2101
|
+
graphics.points.push({
|
|
2102
|
+
x: endCenter.x - 0.1,
|
|
2103
|
+
y: endCenter.y + 0.1,
|
|
2104
|
+
color: connectionColor,
|
|
2105
|
+
label: [solver.currentConnection.connectionId, "end"].join("\n")
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
for (const solvedRoute of solver.solvedRoutes) {
|
|
2109
|
+
const connectionColor = getConnectionColor(
|
|
2110
|
+
solvedRoute.connection.connectionId
|
|
2111
|
+
);
|
|
2112
|
+
const pathPoints = [];
|
|
2113
|
+
for (const candidate of solvedRoute.path) {
|
|
2114
|
+
const port = candidate.port;
|
|
2115
|
+
pathPoints.push({ x: port.d.x, y: port.d.y });
|
|
2116
|
+
}
|
|
2117
|
+
if (pathPoints.length > 0) {
|
|
2118
|
+
graphics.lines.push({
|
|
2119
|
+
points: pathPoints,
|
|
2120
|
+
strokeColor: connectionColor
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
const candidates = solver.candidateQueue.peekMany(10);
|
|
2125
|
+
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
|
|
2126
|
+
const candidate = candidates[candidateIndex];
|
|
2127
|
+
const port = candidate.port;
|
|
2128
|
+
const isNext = candidateIndex === 0;
|
|
2129
|
+
graphics.points.push({
|
|
2130
|
+
x: port.d.x,
|
|
2131
|
+
y: port.d.y,
|
|
2132
|
+
color: isNext ? "green" : "rgba(128, 128, 128, 0.25)",
|
|
2133
|
+
label: [
|
|
2134
|
+
candidate.port.portId,
|
|
2135
|
+
`g: ${candidate.g.toFixed(2)}`,
|
|
2136
|
+
`h: ${candidate.h.toFixed(2)}`,
|
|
2137
|
+
`f: ${candidate.f.toFixed(2)}`
|
|
2138
|
+
].join("\n")
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
const nextCandidate = candidates[0];
|
|
2142
|
+
if (!solver.solved && nextCandidate && solver.currentConnection) {
|
|
2143
|
+
const connectionColor = getConnectionColor(
|
|
2144
|
+
solver.currentConnection.connectionId
|
|
2145
|
+
);
|
|
2146
|
+
const activePath = [];
|
|
2147
|
+
let cursor = nextCandidate;
|
|
2148
|
+
while (cursor) {
|
|
2149
|
+
const port = cursor.port;
|
|
2150
|
+
activePath.unshift({ x: port.d.x, y: port.d.y });
|
|
2151
|
+
cursor = cursor.parent;
|
|
2152
|
+
}
|
|
2153
|
+
if (activePath.length > 1) {
|
|
2154
|
+
graphics.lines.push({
|
|
2155
|
+
points: activePath,
|
|
2156
|
+
strokeColor: connectionColor
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
return graphics;
|
|
2161
|
+
};
|
|
2162
|
+
|
|
2163
|
+
// node_modules/@tscircuit/math-utils/dist/chunk-EFLPMB4J.js
|
|
2164
|
+
function distance(p1, p2) {
|
|
2165
|
+
const dx = p1.x - p2.x;
|
|
2166
|
+
const dy = p1.y - p2.y;
|
|
2167
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// lib/JumperGraphSolver/perimeterChordUtils.ts
|
|
2171
|
+
function perimeterT(p, xmin, xmax, ymin, ymax) {
|
|
2172
|
+
const W = xmax - xmin;
|
|
2173
|
+
const H = ymax - ymin;
|
|
2174
|
+
const eps = 1e-6;
|
|
2175
|
+
if (Math.abs(p.y - ymax) < eps) {
|
|
2176
|
+
return p.x - xmin;
|
|
2177
|
+
}
|
|
2178
|
+
if (Math.abs(p.x - xmax) < eps) {
|
|
2179
|
+
return W + (ymax - p.y);
|
|
2180
|
+
}
|
|
2181
|
+
if (Math.abs(p.y - ymin) < eps) {
|
|
2182
|
+
return W + H + (xmax - p.x);
|
|
2183
|
+
}
|
|
2184
|
+
if (Math.abs(p.x - xmin) < eps) {
|
|
2185
|
+
return 2 * W + H + (p.y - ymin);
|
|
2186
|
+
}
|
|
2187
|
+
const distTop = Math.abs(p.y - ymax);
|
|
2188
|
+
const distRight = Math.abs(p.x - xmax);
|
|
2189
|
+
const distBottom = Math.abs(p.y - ymin);
|
|
2190
|
+
const distLeft = Math.abs(p.x - xmin);
|
|
2191
|
+
const minDist = Math.min(distTop, distRight, distBottom, distLeft);
|
|
2192
|
+
if (minDist === distTop) {
|
|
2193
|
+
return Math.max(0, Math.min(W, p.x - xmin));
|
|
2194
|
+
}
|
|
2195
|
+
if (minDist === distRight) {
|
|
2196
|
+
return W + Math.max(0, Math.min(H, ymax - p.y));
|
|
2197
|
+
}
|
|
2198
|
+
if (minDist === distBottom) {
|
|
2199
|
+
return W + H + Math.max(0, Math.min(W, xmax - p.x));
|
|
2200
|
+
}
|
|
2201
|
+
return 2 * W + H + Math.max(0, Math.min(H, p.y - ymin));
|
|
2202
|
+
}
|
|
2203
|
+
function areCoincident(t1, t2, eps = 1e-6) {
|
|
2204
|
+
return Math.abs(t1 - t2) < eps;
|
|
2205
|
+
}
|
|
2206
|
+
function chordsCross(chord1, chord2) {
|
|
2207
|
+
const [a, b] = chord1[0] < chord1[1] ? chord1 : [chord1[1], chord1[0]];
|
|
2208
|
+
const [c, d] = chord2[0] < chord2[1] ? chord2 : [chord2[1], chord2[0]];
|
|
2209
|
+
if (areCoincident(a, c) || areCoincident(a, d) || areCoincident(b, c) || areCoincident(b, d)) {
|
|
2210
|
+
return false;
|
|
2211
|
+
}
|
|
2212
|
+
return a < c && c < b && b < d || c < a && a < d && d < b;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
// lib/JumperGraphSolver/computeDifferentNetCrossings.ts
|
|
2216
|
+
function computeDifferentNetCrossings(region, port1, port2) {
|
|
2217
|
+
const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
|
|
2218
|
+
const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
|
|
2219
|
+
const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
|
|
2220
|
+
const newChord = [t1, t2];
|
|
2221
|
+
let crossings = 0;
|
|
2222
|
+
const assignments = region.assignments ?? [];
|
|
2223
|
+
for (const assignment of assignments) {
|
|
2224
|
+
const existingT1 = perimeterT(
|
|
2225
|
+
assignment.regionPort1.d,
|
|
2226
|
+
xmin,
|
|
2227
|
+
xmax,
|
|
2228
|
+
ymin,
|
|
2229
|
+
ymax
|
|
2230
|
+
);
|
|
2231
|
+
const existingT2 = perimeterT(
|
|
2232
|
+
assignment.regionPort2.d,
|
|
2233
|
+
xmin,
|
|
2234
|
+
xmax,
|
|
2235
|
+
ymin,
|
|
2236
|
+
ymax
|
|
2237
|
+
);
|
|
2238
|
+
const existingChord = [existingT1, existingT2];
|
|
2239
|
+
if (chordsCross(newChord, existingChord)) {
|
|
2240
|
+
crossings++;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
return crossings;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// lib/JumperGraphSolver/computeCrossingAssignments.ts
|
|
2247
|
+
function computeCrossingAssignments(region, port1, port2) {
|
|
2248
|
+
const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
|
|
2249
|
+
const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
|
|
2250
|
+
const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
|
|
2251
|
+
const newChord = [t1, t2];
|
|
2252
|
+
const crossingAssignments = [];
|
|
2253
|
+
const assignments = region.assignments ?? [];
|
|
2254
|
+
for (const assignment of assignments) {
|
|
2255
|
+
const existingT1 = perimeterT(
|
|
2256
|
+
assignment.regionPort1.d,
|
|
2257
|
+
xmin,
|
|
2258
|
+
xmax,
|
|
2259
|
+
ymin,
|
|
2260
|
+
ymax
|
|
2261
|
+
);
|
|
2262
|
+
const existingT2 = perimeterT(
|
|
2263
|
+
assignment.regionPort2.d,
|
|
2264
|
+
xmin,
|
|
2265
|
+
xmax,
|
|
2266
|
+
ymin,
|
|
2267
|
+
ymax
|
|
2268
|
+
);
|
|
2269
|
+
const existingChord = [existingT1, existingT2];
|
|
2270
|
+
if (chordsCross(newChord, existingChord)) {
|
|
2271
|
+
crossingAssignments.push(assignment);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
return crossingAssignments;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
// lib/JumperGraphSolver/JumperGraphSolver.ts
|
|
2278
|
+
var JumperGraphSolver = class extends HyperGraphSolver {
|
|
2279
|
+
UNIT_OF_COST = "distance";
|
|
2280
|
+
constructor(input) {
|
|
2281
|
+
super({
|
|
2282
|
+
...input,
|
|
2283
|
+
greedyMultiplier: 1.2,
|
|
2284
|
+
rippingEnabled: true,
|
|
2285
|
+
ripCost: 40
|
|
2286
|
+
});
|
|
2287
|
+
this.MAX_ITERATIONS = 400 + input.inputConnections.length * 200;
|
|
2288
|
+
}
|
|
2289
|
+
estimateCostToEnd(port) {
|
|
2290
|
+
return distance(port.d, this.currentEndRegion.d.center);
|
|
2291
|
+
}
|
|
2292
|
+
getPortUsagePenalty(port) {
|
|
2293
|
+
return port.ripCount ?? 0;
|
|
2294
|
+
}
|
|
2295
|
+
computeIncreasedRegionCostIfPortsAreUsed(region, port1, port2) {
|
|
2296
|
+
return computeDifferentNetCrossings(region, port1, port2) * 6;
|
|
2297
|
+
}
|
|
2298
|
+
getRipsRequiredForPortUsage(region, port1, port2) {
|
|
2299
|
+
const crossingAssignments = computeCrossingAssignments(region, port1, port2);
|
|
2300
|
+
return crossingAssignments.filter(
|
|
2301
|
+
(a) => a.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
routeSolvedHook(solvedRoute) {
|
|
2305
|
+
}
|
|
2306
|
+
visualize() {
|
|
2307
|
+
return visualizeJumperGraphSolver(this);
|
|
2308
|
+
}
|
|
2309
|
+
};
|
|
2310
|
+
export {
|
|
2311
|
+
HyperGraphSolver,
|
|
2312
|
+
JumperGraphSolver,
|
|
2313
|
+
applyTransformToGraph,
|
|
2314
|
+
createGraphWithConnectionsFromBaseGraph,
|
|
2315
|
+
generateJumperGrid,
|
|
2316
|
+
generateJumperX4Grid,
|
|
2317
|
+
rotateGraph90Degrees
|
|
2318
|
+
};
|