@mark1russell7/highways 0.4.0 → 0.4.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/README.md +40 -0
- package/build/index.d.ts +313 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2256 -341
- package/build/index.js.map +1 -1
- package/package.json +2 -1
- package/src/bench/measures/bundles.ts +101 -2
- package/src/bench/measures/index.ts +1 -0
- package/src/bench/measures/order.ts +97 -0
- package/src/bench/room.ts +126 -9
- package/src/bench/screen/measures.ts +12 -0
- package/src/bench/screen/screen.ts +73 -9
- package/src/index.ts +2 -0
- package/src/order/index.ts +65 -0
- package/src/plugs/justify.ts +48 -0
- package/src/plugs/side.ts +138 -0
- package/src/repair/index.ts +377 -0
- package/src/repair/walk.ts +220 -0
- package/src/room/lanes.ts +109 -10
- package/src/routers/corners.ts +73 -6
- package/src/routers/fan.ts +145 -0
- package/src/routers/index.ts +2 -2
- package/src/routers/shared.ts +107 -8
- package/src/routers/snell.ts +140 -0
- package/src/routers/trunks.ts +43 -2
- package/src/strategies/schemas.ts +53 -2
package/build/index.js
CHANGED
|
@@ -427,7 +427,7 @@ const COLLECTIONS = [
|
|
|
427
427
|
"scores",
|
|
428
428
|
"teams"
|
|
429
429
|
];
|
|
430
|
-
const SIDES$
|
|
430
|
+
const SIDES$2 = [
|
|
431
431
|
"north",
|
|
432
432
|
"east",
|
|
433
433
|
"south",
|
|
@@ -496,7 +496,7 @@ function makeFixture(spec) {
|
|
|
496
496
|
clusterOf.push(cluster);
|
|
497
497
|
const id = `n${nodes.length}`;
|
|
498
498
|
if (R() < (spec.fixedPorts ?? 0)) {
|
|
499
|
-
const s = SIDES$
|
|
499
|
+
const s = SIDES$2[Math.floor(R() * 4)];
|
|
500
500
|
const t = .2 + .6 * R();
|
|
501
501
|
const at = s === "north" ? {
|
|
502
502
|
x: w * t,
|
|
@@ -1049,7 +1049,7 @@ function checkProperties(graph, result, limit = 100) {
|
|
|
1049
1049
|
}
|
|
1050
1050
|
for (const id of run.edges) {
|
|
1051
1051
|
const r = routes.get(id);
|
|
1052
|
-
if (!r || !(holds$
|
|
1052
|
+
if (!r || !(holds$2(r.points, run.points) || holds$2(r.points, [...run.points].reverse()))) {
|
|
1053
1053
|
if (add({
|
|
1054
1054
|
rule: "run-shared",
|
|
1055
1055
|
edge: id,
|
|
@@ -1096,7 +1096,7 @@ function checkLevels(graph, levels, limit = 100) {
|
|
|
1096
1096
|
const s = boxOf.get(l.source);
|
|
1097
1097
|
const t = boxOf.get(l.target);
|
|
1098
1098
|
const p = l.points;
|
|
1099
|
-
if (!s || !t || p.length < 2 || !onBoundary(p[0], s) || !onBoundary(p[p.length - 1], t)) {
|
|
1099
|
+
if (!s || !t || p.length < 2 || !onBoundary$1(p[0], s) || !onBoundary$1(p[p.length - 1], t)) {
|
|
1100
1100
|
if (add({
|
|
1101
1101
|
rule: "link-ends",
|
|
1102
1102
|
detail: `level ${level.depth}: the link ${l.source} to ${l.target} does not start and end on the boundaries of its items`
|
|
@@ -1107,12 +1107,12 @@ function checkLevels(graph, levels, limit = 100) {
|
|
|
1107
1107
|
return out;
|
|
1108
1108
|
}
|
|
1109
1109
|
/** This function tells if the point is on the boundary of the box, within `TOLERANCE`. */
|
|
1110
|
-
function onBoundary(p, b) {
|
|
1110
|
+
function onBoundary$1(p, b) {
|
|
1111
1111
|
const inner = inflate(b, -.5);
|
|
1112
1112
|
return !(p.x > inner.x && p.x < inner.x + inner.w && p.y > inner.y && p.y < inner.y + inner.h) && pointBoxDistance(p, b) <= .5;
|
|
1113
1113
|
}
|
|
1114
1114
|
/** This function tells if the polyline `p` holds the points of `part` in a row. */
|
|
1115
|
-
function holds$
|
|
1115
|
+
function holds$2(p, part) {
|
|
1116
1116
|
if (part.length === 0) return true;
|
|
1117
1117
|
for (let i = 0; i + part.length <= p.length; i++) if (part.every((q, j) => dist(q, p[i + j]) <= .5)) return true;
|
|
1118
1118
|
return false;
|
|
@@ -2286,7 +2286,7 @@ function partBefore(P, Q) {
|
|
|
2286
2286
|
}
|
|
2287
2287
|
}
|
|
2288
2288
|
}
|
|
2289
|
-
const byEdge = (P, Q) => P.edge < Q.edge ? -1 : P.edge > Q.edge ? 1 : 0;
|
|
2289
|
+
const byEdge$1 = (P, Q) => P.edge < Q.edge ? -1 : P.edge > Q.edge ? 1 : 0;
|
|
2290
2290
|
/** This function puts the point `a` before the point `b`: the smaller x first, then the smaller y. */
|
|
2291
2291
|
const firstPoint = (a, b) => a.x < b.x - SAME$3 || Math.abs(a.x - b.x) <= SAME$3 && a.y < b.y;
|
|
2292
2292
|
/**
|
|
@@ -2323,14 +2323,14 @@ function orderLanes(run, routes, order = "run") {
|
|
|
2323
2323
|
else lost.push(r.edge);
|
|
2324
2324
|
}
|
|
2325
2325
|
}
|
|
2326
|
-
if (order === "run") placed.sort((P, Q) => partAfter(P, Q)?.side ?? partBefore(P, Q)?.side ?? byEdge(P, Q));
|
|
2326
|
+
if (order === "run") placed.sort((P, Q) => partAfter(P, Q)?.side ?? partBefore(P, Q)?.side ?? byEdge$1(P, Q));
|
|
2327
2327
|
else {
|
|
2328
|
-
placed.sort(byEdge);
|
|
2328
|
+
placed.sort(byEdge$1);
|
|
2329
2329
|
placed.sort((P, Q) => {
|
|
2330
2330
|
const after = partAfter(P, Q);
|
|
2331
2331
|
const before = partBefore(P, Q);
|
|
2332
2332
|
if (after && before && Math.sign(after.side) !== Math.sign(before.side)) return firstPoint(after.at, before.at) ? after.side : before.side;
|
|
2333
|
-
return after?.side ?? before?.side ?? byEdge(P, Q);
|
|
2333
|
+
return after?.side ?? before?.side ?? byEdge$1(P, Q);
|
|
2334
2334
|
});
|
|
2335
2335
|
}
|
|
2336
2336
|
return [...placed.map((x) => x.edge), ...lost.sort()];
|
|
@@ -4598,6 +4598,201 @@ function pickLevel(sizes, scale, options = PICK) {
|
|
|
4598
4598
|
for (let d = 0; d < n - 1; d++) if (sizes[d] * scale >= itemAt) pick = d;
|
|
4599
4599
|
return pick;
|
|
4600
4600
|
}
|
|
4601
|
+
// src/plugs/justify.ts
|
|
4602
|
+
/**
|
|
4603
|
+
* Plug justification (WILD-002, with NAT-031): the plugs of one side keep their order and a minimum gap.
|
|
4604
|
+
* They move the least distance from their ideal places.
|
|
4605
|
+
*/
|
|
4606
|
+
/**
|
|
4607
|
+
* This function places `ideal.length` points on the segment from `lo` to `hi` of one side.
|
|
4608
|
+
* The points keep the order of their ideal places. Two neighbours are at least `gap` apart, or the side divided evenly when it is too short.
|
|
4609
|
+
* The sum of the squares of the moves is the least. The answer is exact: a fit by pool adjacent violators.
|
|
4610
|
+
*
|
|
4611
|
+
* The function `tie` orders two points with the same ideal place. The result is in the order of `ideal`.
|
|
4612
|
+
*/
|
|
4613
|
+
function justifyPlugs(ideal, lo, hi, gap, tie) {
|
|
4614
|
+
const k = ideal.length;
|
|
4615
|
+
if (k === 0) return [];
|
|
4616
|
+
const order = ideal.map((_, i) => i).sort((a, b) => ideal[a] - ideal[b] || (tie ? tie(a) - tie(b) : 0) || a - b);
|
|
4617
|
+
const s = k > 1 ? Math.max(0, Math.min(gap, (hi - lo) / (k - 1))) : 0;
|
|
4618
|
+
const y = isotonic(order.map((j, i) => ideal[j] - i * s));
|
|
4619
|
+
const top = Math.max(lo, hi - (k - 1) * s);
|
|
4620
|
+
const out = new Array(k);
|
|
4621
|
+
order.forEach((j, i) => {
|
|
4622
|
+
out[j] = Math.max(lo, Math.min(top, y[i])) + i * s;
|
|
4623
|
+
});
|
|
4624
|
+
return out;
|
|
4625
|
+
}
|
|
4626
|
+
/** This function gives the non-decreasing sequence nearest to `v` in the sum of squares (pool adjacent violators). */
|
|
4627
|
+
function isotonic(v) {
|
|
4628
|
+
const mean = [];
|
|
4629
|
+
const size = [];
|
|
4630
|
+
for (const x of v) {
|
|
4631
|
+
mean.push(x);
|
|
4632
|
+
size.push(1);
|
|
4633
|
+
while (mean.length > 1 && mean[mean.length - 2] > mean[mean.length - 1]) {
|
|
4634
|
+
const m = mean.pop();
|
|
4635
|
+
const n = size.pop();
|
|
4636
|
+
const i = mean.length - 1;
|
|
4637
|
+
mean[i] = (mean[i] * size[i] + m * n) / (size[i] + n);
|
|
4638
|
+
size[i] += n;
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
const out = [];
|
|
4642
|
+
mean.forEach((m, i) => {
|
|
4643
|
+
for (let j = 0; j < size[i]; j++) out.push(m);
|
|
4644
|
+
});
|
|
4645
|
+
return out;
|
|
4646
|
+
}
|
|
4647
|
+
// src/plugs/side.ts
|
|
4648
|
+
/**
|
|
4649
|
+
* Free plugs on a side (WILD-001): the search of a route starts from the whole free side, not from one point of it.
|
|
4650
|
+
* Thus the first leg goes straight out to the first corner of the way, and the plug is the foot of that leg.
|
|
4651
|
+
*/
|
|
4652
|
+
/** This function gives the free side of an end, or `undefined` when the plug of the end is fixed. */
|
|
4653
|
+
function freeSide(node, end, toward, inset) {
|
|
4654
|
+
if (fixedPortPoint(node, end)) return void 0;
|
|
4655
|
+
const side = (end.port === void 0 ? void 0 : node.ports?.find((p) => p.id === end.port))?.side ?? sideToward(node.box, toward);
|
|
4656
|
+
const b = node.box;
|
|
4657
|
+
const along = side === "east" || side === "west" ? "y" : "x";
|
|
4658
|
+
const start = along === "y" ? b.y : b.x;
|
|
4659
|
+
const len = along === "y" ? b.h : b.w;
|
|
4660
|
+
const m = Math.min(inset, len / 4);
|
|
4661
|
+
const at = side === "east" ? b.x + b.w : side === "west" ? b.x : side === "south" ? b.y + b.h : b.y;
|
|
4662
|
+
const point = pointOnSide(b, side, toward, inset);
|
|
4663
|
+
return {
|
|
4664
|
+
node,
|
|
4665
|
+
side,
|
|
4666
|
+
normal: SIDE_NORMAL[side],
|
|
4667
|
+
along,
|
|
4668
|
+
at,
|
|
4669
|
+
lo: start + m,
|
|
4670
|
+
hi: start + len - m,
|
|
4671
|
+
facing: {
|
|
4672
|
+
point,
|
|
4673
|
+
normal: SIDE_NORMAL[side]
|
|
4674
|
+
}
|
|
4675
|
+
};
|
|
4676
|
+
}
|
|
4677
|
+
/** This function gives the point of the side at `v` on the axis along the side. */
|
|
4678
|
+
function footAt(f, v) {
|
|
4679
|
+
return f.along === "y" ? {
|
|
4680
|
+
x: f.at,
|
|
4681
|
+
y: v
|
|
4682
|
+
} : {
|
|
4683
|
+
x: v,
|
|
4684
|
+
y: f.at
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
/**
|
|
4688
|
+
* This function gives the places that the search tries on one free side. A place is the out point of a plug on the side.
|
|
4689
|
+
* A place is also a corner in front of the side. The plug of a corner is the foot of the leg to it.
|
|
4690
|
+
*/
|
|
4691
|
+
function startsOf(ways, f, others, bend, most) {
|
|
4692
|
+
const margin = ways.margin;
|
|
4693
|
+
const pad = margin / 2;
|
|
4694
|
+
const own = /* @__PURE__ */ new Set([f.node]);
|
|
4695
|
+
const starts = [];
|
|
4696
|
+
const out = (p) => ({
|
|
4697
|
+
x: p.x + f.normal.x * margin,
|
|
4698
|
+
y: p.y + f.normal.y * margin
|
|
4699
|
+
});
|
|
4700
|
+
const plugAt = (v) => ({
|
|
4701
|
+
point: footAt(f, v),
|
|
4702
|
+
normal: f.normal
|
|
4703
|
+
});
|
|
4704
|
+
starts.push({
|
|
4705
|
+
plug: f.facing,
|
|
4706
|
+
point: out(f.facing.point),
|
|
4707
|
+
cost: margin
|
|
4708
|
+
});
|
|
4709
|
+
const mid = (f.lo + f.hi) / 2;
|
|
4710
|
+
for (const v of [
|
|
4711
|
+
f.lo,
|
|
4712
|
+
mid,
|
|
4713
|
+
f.hi,
|
|
4714
|
+
...others
|
|
4715
|
+
]) {
|
|
4716
|
+
if (v < f.lo - 1e-9 || v > f.hi + 1e-9) continue;
|
|
4717
|
+
const p = out(footAt(f, v));
|
|
4718
|
+
if (!ways.obstacles.inside(p, 0, f.node)) starts.push({
|
|
4719
|
+
plug: plugAt(v),
|
|
4720
|
+
point: p,
|
|
4721
|
+
cost: margin
|
|
4722
|
+
});
|
|
4723
|
+
}
|
|
4724
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4725
|
+
for (const v of [
|
|
4726
|
+
f.lo,
|
|
4727
|
+
mid,
|
|
4728
|
+
f.hi,
|
|
4729
|
+
f.facing.point[f.along]
|
|
4730
|
+
]) for (const c of ways.corners.nearestVisible(out(footAt(f, v)), 16)) seen.add(c);
|
|
4731
|
+
for (const c of seen) {
|
|
4732
|
+
const q = ways.corners.points[c];
|
|
4733
|
+
const across = f.along === "y" ? "x" : "y";
|
|
4734
|
+
const depth = (q[across] - f.at) * f.normal[across];
|
|
4735
|
+
if (depth < margin - 1e-9) continue;
|
|
4736
|
+
const v = Math.max(f.lo, Math.min(f.hi, q[f.along]));
|
|
4737
|
+
const foot = footAt(f, v);
|
|
4738
|
+
const angle = Math.atan2(Math.abs(q[f.along] - v), depth);
|
|
4739
|
+
if (angle > Math.PI / 4 + 1e-9) continue;
|
|
4740
|
+
if (!ways.obstacles.clear(foot, q, pad, own)) continue;
|
|
4741
|
+
starts.push({
|
|
4742
|
+
plug: plugAt(v),
|
|
4743
|
+
point: q,
|
|
4744
|
+
cost: dist(foot, q) * (1 + bend * angle)
|
|
4745
|
+
});
|
|
4746
|
+
}
|
|
4747
|
+
const best = /* @__PURE__ */ new Map();
|
|
4748
|
+
for (const s of starts) {
|
|
4749
|
+
const key = Math.round(s.plug.point[f.along] * 2);
|
|
4750
|
+
const old = best.get(key);
|
|
4751
|
+
if (!old || s.cost < old.cost) best.set(key, s);
|
|
4752
|
+
}
|
|
4753
|
+
const facing = f.facing.point[f.along];
|
|
4754
|
+
return [...best.values()].sort((a, b) => Math.abs(a.plug.point[f.along] - facing) - Math.abs(b.plug.point[f.along] - facing)).slice(0, Math.max(1, most));
|
|
4755
|
+
}
|
|
4756
|
+
/**
|
|
4757
|
+
* This function places the two plugs of an edge with a free side. A fixed plug stays at its point.
|
|
4758
|
+
* The search (A*) starts from each place of the free side of the source. It ends at each place of the free side of the target.
|
|
4759
|
+
* The plugs are the places of the shortest way. Without a way, the plugs face the other end, as `placePlug` places them.
|
|
4760
|
+
*/
|
|
4761
|
+
function sidePlugs(ways, s, es, t, et, options = {}) {
|
|
4762
|
+
const inset = options.inset ?? 6;
|
|
4763
|
+
const ps = placePlug(s, es, center(t.box));
|
|
4764
|
+
const pt = placePlug(t, et, center(s.box));
|
|
4765
|
+
if (s === t) return [ps, pt];
|
|
4766
|
+
const fs = freeSide(s, es, center(t.box), inset);
|
|
4767
|
+
const ft = freeSide(t, et, center(s.box), inset);
|
|
4768
|
+
if (!fs && !ft) return [ps, pt];
|
|
4769
|
+
const bend = options.bend ?? 1;
|
|
4770
|
+
const most = options.most ?? 16;
|
|
4771
|
+
const fixed = (p) => [{
|
|
4772
|
+
plug: p,
|
|
4773
|
+
point: ways.out(p),
|
|
4774
|
+
cost: ways.margin
|
|
4775
|
+
}];
|
|
4776
|
+
const facingPair = fs && ft && fs.along === ft.along && fs.normal.x === -ft.normal.x && fs.normal.y === -ft.normal.y;
|
|
4777
|
+
const shared = (a, b, p) => {
|
|
4778
|
+
if (!a) return [];
|
|
4779
|
+
if (facingPair && b) {
|
|
4780
|
+
const lo = Math.max(a.lo, b.lo);
|
|
4781
|
+
const hi = Math.min(a.hi, b.hi);
|
|
4782
|
+
return lo <= hi ? [
|
|
4783
|
+
lo,
|
|
4784
|
+
(lo + hi) / 2,
|
|
4785
|
+
hi
|
|
4786
|
+
] : [];
|
|
4787
|
+
}
|
|
4788
|
+
return [p.point[a.along]];
|
|
4789
|
+
};
|
|
4790
|
+
const from = fs ? startsOf(ways, fs, shared(fs, ft, pt), bend, most) : fixed(ps);
|
|
4791
|
+
const to = ft ? startsOf(ways, ft, shared(ft, fs, ps), bend, most) : fixed(pt);
|
|
4792
|
+
const way = ways.corners.pathAmong(from, to);
|
|
4793
|
+
if (!way) return [ps, pt];
|
|
4794
|
+
return [from[way.from].plug, to[way.to].plug];
|
|
4795
|
+
}
|
|
4601
4796
|
// src/routers/corners.ts
|
|
4602
4797
|
/**
|
|
4603
4798
|
* This function makes a router that goes around the nodes.
|
|
@@ -4611,17 +4806,39 @@ function cornersRouter(options) {
|
|
|
4611
4806
|
const byId = nodeIndex(graph);
|
|
4612
4807
|
const ways = new WayFinder(graph.nodes, options);
|
|
4613
4808
|
const routes = [];
|
|
4809
|
+
if ((options.plugs ?? "facing") === "facing" && !((options.plugGap ?? 0) > 0)) {
|
|
4810
|
+
for (const e of graph.edges) {
|
|
4811
|
+
const s = byId.get(e.source.node);
|
|
4812
|
+
const t = byId.get(e.target.node);
|
|
4813
|
+
if (!s || !t) continue;
|
|
4814
|
+
const ps = placePlug(s, e.source, center(t.box));
|
|
4815
|
+
const pt = placePlug(t, e.target, center(s.box));
|
|
4816
|
+
routes.push({
|
|
4817
|
+
edge: e.id,
|
|
4818
|
+
points: ways.between(ps, pt, /* @__PURE__ */ new Set([s, t]))
|
|
4819
|
+
});
|
|
4820
|
+
}
|
|
4821
|
+
return { routes };
|
|
4822
|
+
}
|
|
4823
|
+
const ends = [];
|
|
4614
4824
|
for (const e of graph.edges) {
|
|
4615
4825
|
const s = byId.get(e.source.node);
|
|
4616
4826
|
const t = byId.get(e.target.node);
|
|
4617
4827
|
if (!s || !t) continue;
|
|
4618
|
-
const ps = placePlug(s, e.source, center(t.box));
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4828
|
+
const [ps, pt] = options.plugs === "side" ? sidePlugs(ways, s, e.source, t, e.target) : [placePlug(s, e.source, center(t.box)), placePlug(t, e.target, center(s.box))];
|
|
4829
|
+
ends.push([
|
|
4830
|
+
e,
|
|
4831
|
+
s,
|
|
4832
|
+
t,
|
|
4833
|
+
ps,
|
|
4834
|
+
pt
|
|
4835
|
+
]);
|
|
4624
4836
|
}
|
|
4837
|
+
if ((options.plugGap ?? 0) > 0) spacePlugs(ends, options.plugGap);
|
|
4838
|
+
for (const [e, s, t, ps, pt] of ends) routes.push({
|
|
4839
|
+
edge: e.id,
|
|
4840
|
+
points: ways.between(ps, pt, /* @__PURE__ */ new Set([s, t]))
|
|
4841
|
+
});
|
|
4625
4842
|
return { routes };
|
|
4626
4843
|
}
|
|
4627
4844
|
};
|
|
@@ -4630,6 +4847,69 @@ function cornersRouter(options) {
|
|
|
4630
4847
|
maxNodes: options.maxNodes
|
|
4631
4848
|
};
|
|
4632
4849
|
}
|
|
4850
|
+
/**
|
|
4851
|
+
* This function moves the free plugs of each side of each node, so that two of them are at least `gap` apart (`justifyPlugs`).
|
|
4852
|
+
* The plugs keep their order on the side. Two plugs at one place take the order of their other ends. A fixed plug does not move.
|
|
4853
|
+
*/
|
|
4854
|
+
function spacePlugs(ends, gap) {
|
|
4855
|
+
const inset = 6;
|
|
4856
|
+
const sides = /* @__PURE__ */ new Map();
|
|
4857
|
+
ends.forEach(([e, s, t, ps, pt], i) => {
|
|
4858
|
+
for (const [end, node, plug, other, k] of [[
|
|
4859
|
+
e.source,
|
|
4860
|
+
s,
|
|
4861
|
+
ps,
|
|
4862
|
+
pt,
|
|
4863
|
+
3
|
|
4864
|
+
], [
|
|
4865
|
+
e.target,
|
|
4866
|
+
t,
|
|
4867
|
+
pt,
|
|
4868
|
+
ps,
|
|
4869
|
+
4
|
|
4870
|
+
]]) {
|
|
4871
|
+
if ((end.port === void 0 ? void 0 : node.ports?.find((p) => p.id === end.port))?.at !== void 0) continue;
|
|
4872
|
+
const key = JSON.stringify([
|
|
4873
|
+
node.id,
|
|
4874
|
+
plug.normal.x,
|
|
4875
|
+
plug.normal.y
|
|
4876
|
+
]);
|
|
4877
|
+
let side = sides.get(key);
|
|
4878
|
+
if (!side) sides.set(key, side = {
|
|
4879
|
+
node,
|
|
4880
|
+
normal: plug.normal,
|
|
4881
|
+
items: []
|
|
4882
|
+
});
|
|
4883
|
+
side.items.push([
|
|
4884
|
+
i,
|
|
4885
|
+
k,
|
|
4886
|
+
other.point
|
|
4887
|
+
]);
|
|
4888
|
+
}
|
|
4889
|
+
});
|
|
4890
|
+
for (const { node, normal, items } of sides.values()) {
|
|
4891
|
+
if (items.length < 2) continue;
|
|
4892
|
+
const along = normal.x !== 0 ? "y" : "x";
|
|
4893
|
+
const b = node.box;
|
|
4894
|
+
const start = along === "y" ? b.y : b.x;
|
|
4895
|
+
const len = along === "y" ? b.h : b.w;
|
|
4896
|
+
const m = Math.min(inset, len / 4);
|
|
4897
|
+
const placed = justifyPlugs(items.map(([i, k]) => ends[i][k].point[along]), start + m, start + len - m, gap, (j) => items[j][2][along]);
|
|
4898
|
+
items.forEach(([i, k], j) => {
|
|
4899
|
+
const old = ends[i][k];
|
|
4900
|
+
ends[i][k] = {
|
|
4901
|
+
point: along === "y" ? {
|
|
4902
|
+
x: old.point.x,
|
|
4903
|
+
y: placed[j]
|
|
4904
|
+
} : {
|
|
4905
|
+
x: placed[j],
|
|
4906
|
+
y: old.point.y
|
|
4907
|
+
},
|
|
4908
|
+
normal: old.normal
|
|
4909
|
+
};
|
|
4910
|
+
});
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4633
4913
|
// src/routers/plugs.ts
|
|
4634
4914
|
/**
|
|
4635
4915
|
* Plugs and the inflection rule, with no obstacles. Each edge leaves its source plug at the tangent of the side.
|
|
@@ -4819,7 +5099,7 @@ const LEVEL_LINK_DEFAULTS = Object.freeze({
|
|
|
4819
5099
|
plugInset: 6,
|
|
4820
5100
|
throughCost: 8
|
|
4821
5101
|
});
|
|
4822
|
-
const EPS$
|
|
5102
|
+
const EPS$3 = 1e-6;
|
|
4823
5103
|
/**
|
|
4824
5104
|
* This function removes the repeated points, the points in the middle of a straight run,
|
|
4825
5105
|
* and the points where the way turns back on itself.
|
|
@@ -4834,15 +5114,15 @@ function tidy(p) {
|
|
|
4834
5114
|
function tidyPoints(p) {
|
|
4835
5115
|
const out = [];
|
|
4836
5116
|
for (const q of p) {
|
|
4837
|
-
if (out.length > 0 && dist(q, out[out.length - 1]) <= EPS$
|
|
5117
|
+
if (out.length > 0 && dist(q, out[out.length - 1]) <= EPS$3) continue;
|
|
4838
5118
|
while (out.length >= 2) {
|
|
4839
5119
|
const a = out[out.length - 2];
|
|
4840
5120
|
const b = out[out.length - 1];
|
|
4841
5121
|
const cross = (b.x - a.x) * (q.y - b.y) - (b.y - a.y) * (q.x - b.x);
|
|
4842
|
-
if (Math.abs(cross) > EPS$
|
|
5122
|
+
if (Math.abs(cross) > EPS$3 * (1 + dist(a, b) * dist(b, q))) break;
|
|
4843
5123
|
out.pop();
|
|
4844
5124
|
}
|
|
4845
|
-
if (out.length > 0 && dist(q, out[out.length - 1]) <= EPS$
|
|
5125
|
+
if (out.length > 0 && dist(q, out[out.length - 1]) <= EPS$3) continue;
|
|
4846
5126
|
out.push(q);
|
|
4847
5127
|
}
|
|
4848
5128
|
return out;
|
|
@@ -5097,7 +5377,7 @@ function facingPlugs(a, b, inset) {
|
|
|
5097
5377
|
}
|
|
5098
5378
|
return [plug(a, sa, center(b)), plug(b, sb, center(a))];
|
|
5099
5379
|
}
|
|
5100
|
-
const SIDES = [
|
|
5380
|
+
const SIDES$1 = [
|
|
5101
5381
|
"north",
|
|
5102
5382
|
"east",
|
|
5103
5383
|
"south",
|
|
@@ -5116,7 +5396,7 @@ function sidePlug(ways, node, toward) {
|
|
|
5116
5396
|
y: toward.y - c.y
|
|
5117
5397
|
};
|
|
5118
5398
|
const facing = (s) => SIDE_NORMAL[s].x * d.x + SIDE_NORMAL[s].y * d.y;
|
|
5119
|
-
const order = [...SIDES].sort((u, v) => facing(v) - facing(u));
|
|
5399
|
+
const order = [...SIDES$1].sort((u, v) => facing(v) - facing(u));
|
|
5120
5400
|
const plug = (s) => {
|
|
5121
5401
|
const n = SIDE_NORMAL[s];
|
|
5122
5402
|
return {
|
|
@@ -5139,7 +5419,7 @@ function freePlugs(ways, node, inset) {
|
|
|
5139
5419
|
if (known) return known;
|
|
5140
5420
|
const b = node.box;
|
|
5141
5421
|
const out = [];
|
|
5142
|
-
for (const side of SIDES) {
|
|
5422
|
+
for (const side of SIDES$1) {
|
|
5143
5423
|
const n = SIDE_NORMAL[side];
|
|
5144
5424
|
const across = n.x === 0;
|
|
5145
5425
|
const lo = across ? b.x : b.y;
|
|
@@ -5215,7 +5495,7 @@ function freeSpans(lo, hi, covered) {
|
|
|
5215
5495
|
if (x >= hi) break;
|
|
5216
5496
|
}
|
|
5217
5497
|
if (x < hi) out.push([x, hi]);
|
|
5218
|
-
return out.filter(([a, b]) => b - a > EPS$
|
|
5498
|
+
return out.filter(([a, b]) => b - a > EPS$3);
|
|
5219
5499
|
}
|
|
5220
5500
|
/**
|
|
5221
5501
|
* This function gives the shortest way between the free plugs of the two nodes, or `undefined` when the second corner graph has none.
|
|
@@ -5347,7 +5627,7 @@ function squarePull(p, obstacles, pad, first, last, lookahead = 16) {
|
|
|
5347
5627
|
out.push(...step.legs);
|
|
5348
5628
|
const end = out[out.length - 1];
|
|
5349
5629
|
const before = out[out.length - 2];
|
|
5350
|
-
axis = Math.abs(end.y - before.y) < EPS$
|
|
5630
|
+
axis = Math.abs(end.y - before.y) < EPS$3 ? "x" : Math.abs(end.x - before.x) < EPS$3 ? "y" : void 0;
|
|
5351
5631
|
i = step.j;
|
|
5352
5632
|
}
|
|
5353
5633
|
return out;
|
|
@@ -5357,7 +5637,7 @@ function squarePull(p, obstacles, pad, first, last, lookahead = 16) {
|
|
|
5357
5637
|
* Of two clear corners, it picks the corner that continues the axis `from` and arrives along the axis `to`.
|
|
5358
5638
|
*/
|
|
5359
5639
|
function square(a, b, obstacles, pad, from, to) {
|
|
5360
|
-
if (Math.abs(a.x - b.x) < EPS$
|
|
5640
|
+
if (Math.abs(a.x - b.x) < EPS$3 || Math.abs(a.y - b.y) < EPS$3) return obstacles.clear(a, b, pad) ? [b] : void 0;
|
|
5361
5641
|
const options = [{
|
|
5362
5642
|
c: {
|
|
5363
5643
|
x: b.x,
|
|
@@ -5388,7 +5668,7 @@ const SECTIONS_DEFAULTS = Object.freeze({
|
|
|
5388
5668
|
stretchPad: 4,
|
|
5389
5669
|
hubSlack: 80
|
|
5390
5670
|
});
|
|
5391
|
-
const dedupe$
|
|
5671
|
+
const dedupe$3 = (p) => p.filter((q, i) => i === 0 || dist(q, p[i - 1]) > 1e-9);
|
|
5392
5672
|
/**
|
|
5393
5673
|
* This function makes a router with a tree of sections. Each section has a hub: the free corner nearest to the center of its nodes.
|
|
5394
5674
|
* An edge goes from its node up the hubs of its sections to the lowest common section, across, and down to the other node.
|
|
@@ -5466,7 +5746,7 @@ function sectionsRouter(options) {
|
|
|
5466
5746
|
if (!p) {
|
|
5467
5747
|
const [from, to] = forward ? [a, b] : [b, a];
|
|
5468
5748
|
const way = corners.path(from, to);
|
|
5469
|
-
p = dedupe$
|
|
5749
|
+
p = dedupe$3(way ? corners.pull(way) : [from, to]);
|
|
5470
5750
|
pieces.set(key, p);
|
|
5471
5751
|
}
|
|
5472
5752
|
if (!used.includes(key)) used.push(key);
|
|
@@ -5623,6 +5903,136 @@ function makeLevels(graph, root, routeOf, options) {
|
|
|
5623
5903
|
}
|
|
5624
5904
|
return levels;
|
|
5625
5905
|
}
|
|
5906
|
+
// src/routers/snell.ts
|
|
5907
|
+
/**
|
|
5908
|
+
* This function gives the Snell merge point of a route (NAT-035).
|
|
5909
|
+
* The route comes from `from`, reaches a run at `at` and goes on along the run to `next`.
|
|
5910
|
+
* A unit of length on the run costs `d` against 1 off it. The cheapest route meets the run at the angle `acos(d)`.
|
|
5911
|
+
* The function gives the distance from `at` toward `next` of that merge point. A value of 0 or less tells that the turn at `at` is already at most `acos(d)`.
|
|
5912
|
+
*/
|
|
5913
|
+
function snellOffset(from, at, next, d) {
|
|
5914
|
+
const len = dist(at, next);
|
|
5915
|
+
if (len <= 0) return 0;
|
|
5916
|
+
const ux = (next.x - at.x) / len;
|
|
5917
|
+
const uy = (next.y - at.y) / len;
|
|
5918
|
+
const vx = from.x - at.x;
|
|
5919
|
+
const vy = from.y - at.y;
|
|
5920
|
+
const along = vx * ux + vy * uy;
|
|
5921
|
+
const h = Math.abs(vx * uy - vy * ux);
|
|
5922
|
+
const c = Math.min(.999, Math.max(0, d));
|
|
5923
|
+
return along + h * c / Math.sqrt(1 - c * c);
|
|
5924
|
+
}
|
|
5925
|
+
/** The turn, in radians from 0 to π, at `b` on the way from `a` through `b` to `c`. */
|
|
5926
|
+
function turnAt(a, b, c) {
|
|
5927
|
+
const ux = b.x - a.x;
|
|
5928
|
+
const uy = b.y - a.y;
|
|
5929
|
+
const vx = c.x - b.x;
|
|
5930
|
+
const vy = c.y - b.y;
|
|
5931
|
+
return Math.abs(Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy));
|
|
5932
|
+
}
|
|
5933
|
+
/**
|
|
5934
|
+
* This function moves the merge points of the ways of the router `shared` to the Snell angle (NAT-035). It changes the ways in place.
|
|
5935
|
+
*
|
|
5936
|
+
* A way joins a run at a corner `J` when it comes alone from the corner before.
|
|
5937
|
+
* The link from `J` to the next corner has other edges of its key.
|
|
5938
|
+
* When the way turns at `J` by more than `acos(d)`, it goes directly to a new point `J*` on that link.
|
|
5939
|
+
* At `J*`, the way meets the link at the angle `acos(d)`.
|
|
5940
|
+
* Each way on the link gets `J*` as a new corner, so the run keeps the same points in each route. Then the same rule moves the exits, on the reversed ways.
|
|
5941
|
+
*
|
|
5942
|
+
* `firstId` is the first free id of a corner. `clear` tells if a line keeps out of the boxes. `delta` gives the cost share `d` of a link with `f` other edges.
|
|
5943
|
+
*/
|
|
5944
|
+
function snellMerges(ways, firstId, clear, delta) {
|
|
5945
|
+
let next = firstId;
|
|
5946
|
+
const keys = /* @__PURE__ */ new Map();
|
|
5947
|
+
for (const w of ways) {
|
|
5948
|
+
if (!w.found) continue;
|
|
5949
|
+
const list = keys.get(w.key);
|
|
5950
|
+
if (list) list.push(w);
|
|
5951
|
+
else keys.set(w.key, [w]);
|
|
5952
|
+
}
|
|
5953
|
+
for (const group of keys.values()) {
|
|
5954
|
+
if (group.length < 2) continue;
|
|
5955
|
+
const linkOf = (a, b) => a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
5956
|
+
const on = /* @__PURE__ */ new Map();
|
|
5957
|
+
const add = (k, w) => {
|
|
5958
|
+
const set = on.get(k);
|
|
5959
|
+
if (set) set.add(w);
|
|
5960
|
+
else on.set(k, /* @__PURE__ */ new Set([w]));
|
|
5961
|
+
};
|
|
5962
|
+
const drop = (k, w) => {
|
|
5963
|
+
const set = on.get(k);
|
|
5964
|
+
if (!set) return;
|
|
5965
|
+
set.delete(w);
|
|
5966
|
+
if (set.size === 0) on.delete(k);
|
|
5967
|
+
};
|
|
5968
|
+
for (const w of group) for (let i = 1; i < w.ids.length; i++) add(linkOf(w.ids[i - 1], w.ids[i]), w);
|
|
5969
|
+
for (const back of [false, true]) for (const w of group) {
|
|
5970
|
+
if (back) {
|
|
5971
|
+
w.seq.reverse();
|
|
5972
|
+
w.ids.reverse();
|
|
5973
|
+
}
|
|
5974
|
+
for (let i = 1; i < w.ids.length - 1; i++) {
|
|
5975
|
+
const before = on.get(linkOf(w.ids[i - 1], w.ids[i]));
|
|
5976
|
+
const after = on.get(linkOf(w.ids[i], w.ids[i + 1]));
|
|
5977
|
+
if (!before || !after || before.size !== 1 || after.size < 2) continue;
|
|
5978
|
+
const P = w.seq[i - 1];
|
|
5979
|
+
const J = w.seq[i];
|
|
5980
|
+
const N = w.seq[i + 1];
|
|
5981
|
+
const d = delta(after.size - 1);
|
|
5982
|
+
if (turnAt(P, J, N) <= Math.acos(d) + .001) continue;
|
|
5983
|
+
const len = dist(J, N);
|
|
5984
|
+
let t = Math.min(len, snellOffset(P, J, N, d));
|
|
5985
|
+
const pointAt = (s) => ({
|
|
5986
|
+
x: J.x + (N.x - J.x) * s / len,
|
|
5987
|
+
y: J.y + (N.y - J.y) * s / len
|
|
5988
|
+
});
|
|
5989
|
+
let tries = 0;
|
|
5990
|
+
while (t >= 1 && !clear(P, pointAt(t)) && tries < 3) {
|
|
5991
|
+
t /= 2;
|
|
5992
|
+
tries++;
|
|
5993
|
+
}
|
|
5994
|
+
if (t < 1 || !clear(P, pointAt(t))) continue;
|
|
5995
|
+
const a = w.ids[i - 1];
|
|
5996
|
+
const j = w.ids[i];
|
|
5997
|
+
const n = w.ids[i + 1];
|
|
5998
|
+
if (t >= len - 1e-6) {
|
|
5999
|
+
drop(linkOf(a, j), w);
|
|
6000
|
+
drop(linkOf(j, n), w);
|
|
6001
|
+
w.seq.splice(i, 1);
|
|
6002
|
+
w.ids.splice(i, 1);
|
|
6003
|
+
add(linkOf(a, n), w);
|
|
6004
|
+
continue;
|
|
6005
|
+
}
|
|
6006
|
+
const star = pointAt(t);
|
|
6007
|
+
const id = next++;
|
|
6008
|
+
const users = [...after];
|
|
6009
|
+
on.delete(linkOf(j, n));
|
|
6010
|
+
for (const o of users) {
|
|
6011
|
+
for (let k = 0; k < o.ids.length - 1; k++) {
|
|
6012
|
+
const x = o.ids[k];
|
|
6013
|
+
const y = o.ids[k + 1];
|
|
6014
|
+
if (x === j && y === n || x === n && y === j) {
|
|
6015
|
+
o.seq.splice(k + 1, 0, star);
|
|
6016
|
+
o.ids.splice(k + 1, 0, id);
|
|
6017
|
+
break;
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
add(linkOf(j, id), o);
|
|
6021
|
+
add(linkOf(id, n), o);
|
|
6022
|
+
}
|
|
6023
|
+
drop(linkOf(a, j), w);
|
|
6024
|
+
drop(linkOf(j, id), w);
|
|
6025
|
+
w.seq.splice(i, 1);
|
|
6026
|
+
w.ids.splice(i, 1);
|
|
6027
|
+
add(linkOf(a, id), w);
|
|
6028
|
+
}
|
|
6029
|
+
if (back) {
|
|
6030
|
+
w.seq.reverse();
|
|
6031
|
+
w.ids.reverse();
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
}
|
|
5626
6036
|
// src/routers/shared.ts
|
|
5627
6037
|
const byText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
5628
6038
|
/** The angle, from -π to π, of the turn at `base` from the direction `from` → `base` to the point `to`. */
|
|
@@ -5641,6 +6051,10 @@ function turn(from, base, to) {
|
|
|
5641
6051
|
*/
|
|
5642
6052
|
function sharedRouter(options) {
|
|
5643
6053
|
const discount = Math.min(1, Math.max(.05, options.discount ?? .5));
|
|
6054
|
+
const flow = options.cost === "flow";
|
|
6055
|
+
const alpha = Math.min(1, Math.max(.1, options.alpha ?? .6));
|
|
6056
|
+
const share = (f) => f <= 0 ? 1 : flow ? (f + 1) ** alpha - f ** alpha : discount;
|
|
6057
|
+
const passes = Math.max(1, Math.floor(options.passes ?? 1));
|
|
5644
6058
|
const keyOf = (e) => {
|
|
5645
6059
|
if (!options.rule) return "*";
|
|
5646
6060
|
return options.rule(e) ?? `\u0000${e.id}`;
|
|
@@ -5680,13 +6094,16 @@ function sharedRouter(options) {
|
|
|
5680
6094
|
});
|
|
5681
6095
|
}
|
|
5682
6096
|
const discounted = /* @__PURE__ */ new Map();
|
|
5683
|
-
const order =
|
|
5684
|
-
|
|
6097
|
+
const order = orderWays(all, options.order ?? "span", graph);
|
|
6098
|
+
const top = /* @__PURE__ */ new Map();
|
|
6099
|
+
const route = (w) => {
|
|
5685
6100
|
let used = discounted.get(w.key);
|
|
5686
6101
|
if (!used) discounted.set(w.key, used = /* @__PURE__ */ new Map());
|
|
5687
6102
|
const mine = used;
|
|
5688
|
-
const
|
|
5689
|
-
|
|
6103
|
+
const floor = flow ? share(top.get(w.key) ?? 0) : discount;
|
|
6104
|
+
const cost = flow ? (i, j, d) => d * share(mine.get(link(i, j))?.length ?? 0) : (i, j, d) => mine.has(link(i, j)) ? d * discount : d;
|
|
6105
|
+
const way = corners.path(ways.out(w.ps), ways.out(w.pt), cost, floor);
|
|
6106
|
+
if (!way) return;
|
|
5690
6107
|
w.found = true;
|
|
5691
6108
|
w.seq = way;
|
|
5692
6109
|
w.ids = way.map((p, i) => i === 0 || i === way.length - 1 ? endId(p) : index.get(p));
|
|
@@ -5697,7 +6114,32 @@ function sharedRouter(options) {
|
|
|
5697
6114
|
const list = mine.get(link(a, b));
|
|
5698
6115
|
if (list) list.push(w.edge.id);
|
|
5699
6116
|
else mine.set(link(a, b), [w.edge.id]);
|
|
6117
|
+
if (flow) top.set(w.key, Math.max(top.get(w.key) ?? 0, mine.get(link(a, b)).length));
|
|
6118
|
+
}
|
|
6119
|
+
};
|
|
6120
|
+
for (const w of order) route(w);
|
|
6121
|
+
for (let pass = 1; pass < passes; pass++) for (const w of order) {
|
|
6122
|
+
if (!w.found) continue;
|
|
6123
|
+
const mine = discounted.get(w.key);
|
|
6124
|
+
for (let i = 1; i < w.ids.length; i++) {
|
|
6125
|
+
const a = w.ids[i - 1];
|
|
6126
|
+
const b = w.ids[i];
|
|
6127
|
+
if (a >= corners.points.length || b >= corners.points.length) continue;
|
|
6128
|
+
const k = link(a, b);
|
|
6129
|
+
const list = mine.get(k);
|
|
6130
|
+
if (!list) continue;
|
|
6131
|
+
const at = list.indexOf(w.edge.id);
|
|
6132
|
+
if (at >= 0) list.splice(at, 1);
|
|
6133
|
+
if (list.length === 0) mine.delete(k);
|
|
5700
6134
|
}
|
|
6135
|
+
w.found = false;
|
|
6136
|
+
w.seq = [];
|
|
6137
|
+
w.ids = [];
|
|
6138
|
+
route(w);
|
|
6139
|
+
}
|
|
6140
|
+
if (options.merge === "snell") {
|
|
6141
|
+
const pad = ways.margin / 2;
|
|
6142
|
+
snellMerges(order, corners.points.length + ends.size, (a, b) => ways.obstacles.clear(a, b, pad), (f) => Math.min(.95, share(f)));
|
|
5701
6143
|
}
|
|
5702
6144
|
const users = /* @__PURE__ */ new Map();
|
|
5703
6145
|
for (const w of order) {
|
|
@@ -5785,6 +6227,33 @@ function sharedRouter(options) {
|
|
|
5785
6227
|
};
|
|
5786
6228
|
}
|
|
5787
6229
|
/**
|
|
6230
|
+
* This function gives the order of the ways for the router `shared`. The ways of one key stay together, in the order of the keys.
|
|
6231
|
+
* In a key, the order is `order`, and the edge id breaks each tie. The order `load` counts the edges of the key between the same two sections.
|
|
6232
|
+
*/
|
|
6233
|
+
function orderWays(all, order, graph) {
|
|
6234
|
+
const span = (w) => dist(w.ps.point, w.pt.point);
|
|
6235
|
+
if (order === "input") {
|
|
6236
|
+
const at = new Map(all.map((w, i) => [w, i]));
|
|
6237
|
+
return [...all].sort((p, q) => byText(p.key, q.key) || at.get(p) - at.get(q));
|
|
6238
|
+
}
|
|
6239
|
+
if (order === "shortest") return [...all].sort((p, q) => byText(p.key, q.key) || span(p) - span(q) || byText(p.edge.id, q.edge.id));
|
|
6240
|
+
if (order === "span") return [...all].sort((p, q) => byText(p.key, q.key) || span(q) - span(p) || byText(p.edge.id, q.edge.id));
|
|
6241
|
+
const leafOf = /* @__PURE__ */ new Map();
|
|
6242
|
+
const walk = (s) => {
|
|
6243
|
+
if (s.children.length === 0) for (const i of s.nodes) leafOf.set(graph.nodes[i].id, s.id);
|
|
6244
|
+
for (const c of s.children) walk(c);
|
|
6245
|
+
};
|
|
6246
|
+
walk(buildSections(graph.nodes));
|
|
6247
|
+
const pair = (w) => {
|
|
6248
|
+
const a = leafOf.get(w.edge.source.node) ?? "";
|
|
6249
|
+
const b = leafOf.get(w.edge.target.node) ?? "";
|
|
6250
|
+
return `${w.key}\u0000${a < b ? a : b}\u0000${a < b ? b : a}`;
|
|
6251
|
+
};
|
|
6252
|
+
const load = /* @__PURE__ */ new Map();
|
|
6253
|
+
for (const w of all) load.set(pair(w), (load.get(pair(w)) ?? 0) + 1);
|
|
6254
|
+
return [...all].sort((p, q) => byText(p.key, q.key) || load.get(pair(q)) - load.get(pair(p)) || span(q) - span(p) || byText(p.edge.id, q.edge.id));
|
|
6255
|
+
}
|
|
6256
|
+
/**
|
|
5788
6257
|
* This function gives the lane order of a run with the option `laneOrder: "run"` (metro-line order). Two edges of the run go together past its last corner
|
|
5789
6258
|
* until they part. The edge that turns more to the left there gets the lane more to the left.
|
|
5790
6259
|
* Two edges can part only before the first corner. Then the edge that comes from more to the left gets the lane more to the left.
|
|
@@ -5874,6 +6343,162 @@ const straightRouter = {
|
|
|
5874
6343
|
return { routes };
|
|
5875
6344
|
}
|
|
5876
6345
|
};
|
|
6346
|
+
// src/routers/fan.ts
|
|
6347
|
+
/**
|
|
6348
|
+
* This function gives the Gilbert split point of a trunk with `targets.length` edges (EDA-030). The point makes the flow cost least:
|
|
6349
|
+
* `len(start, s) * k^alpha + Σ_j len(s, t_j)`, with `k` the number of targets. Weighted Weiszfeld steps find it.
|
|
6350
|
+
* The distance from `start` stays from `lo` to `hi` times the distance to the center of the targets.
|
|
6351
|
+
* It is also at most half of the distance to the nearest target.
|
|
6352
|
+
* The point moves back toward `start` out of the boxes, in steps of `step`.
|
|
6353
|
+
*/
|
|
6354
|
+
function gilbertPoint(start, targets, alpha, free, step, lo = .2, hi = .8) {
|
|
6355
|
+
if (targets.length === 0) return start;
|
|
6356
|
+
const k = targets.length;
|
|
6357
|
+
const c = {
|
|
6358
|
+
x: targets.reduce((s, p) => s + p.x, 0) / k,
|
|
6359
|
+
y: targets.reduce((s, p) => s + p.y, 0) / k
|
|
6360
|
+
};
|
|
6361
|
+
const L = dist(start, c);
|
|
6362
|
+
if (L === 0) return start;
|
|
6363
|
+
const w0 = k ** Math.min(1, Math.max(0, alpha));
|
|
6364
|
+
let s = {
|
|
6365
|
+
x: start.x + (c.x - start.x) * .5,
|
|
6366
|
+
y: start.y + (c.y - start.y) * .5
|
|
6367
|
+
};
|
|
6368
|
+
for (let it = 0; it < 32; it++) {
|
|
6369
|
+
let sx = 0;
|
|
6370
|
+
let sy = 0;
|
|
6371
|
+
let sw = 0;
|
|
6372
|
+
const pull = (p, w) => {
|
|
6373
|
+
const d = Math.max(1e-6, dist(s, p));
|
|
6374
|
+
sx += w * p.x / d;
|
|
6375
|
+
sy += w * p.y / d;
|
|
6376
|
+
sw += w / d;
|
|
6377
|
+
};
|
|
6378
|
+
pull(start, w0);
|
|
6379
|
+
for (const t of targets) pull(t, 1);
|
|
6380
|
+
const n = {
|
|
6381
|
+
x: sx / sw,
|
|
6382
|
+
y: sy / sw
|
|
6383
|
+
};
|
|
6384
|
+
const moved = dist(n, s);
|
|
6385
|
+
s = n;
|
|
6386
|
+
if (moved < .001) break;
|
|
6387
|
+
}
|
|
6388
|
+
const r = dist(start, s);
|
|
6389
|
+
const most = Math.min(hi * L, .5 * Math.min(...targets.map((p) => dist(start, p))));
|
|
6390
|
+
const want = Math.min(most, Math.max(lo * L, r));
|
|
6391
|
+
const dir = r > 1e-9 ? {
|
|
6392
|
+
x: (s.x - start.x) / r,
|
|
6393
|
+
y: (s.y - start.y) / r
|
|
6394
|
+
} : {
|
|
6395
|
+
x: (c.x - start.x) / L,
|
|
6396
|
+
y: (c.y - start.y) / L
|
|
6397
|
+
};
|
|
6398
|
+
const at = (t) => ({
|
|
6399
|
+
x: start.x + dir.x * t,
|
|
6400
|
+
y: start.y + dir.y * t
|
|
6401
|
+
});
|
|
6402
|
+
let d = want;
|
|
6403
|
+
while (d > 0 && !free(at(d))) d -= step;
|
|
6404
|
+
return d > 0 ? at(d) : start;
|
|
6405
|
+
}
|
|
6406
|
+
/**
|
|
6407
|
+
* This function grows a balancing-factor tree from `root` to the targets (NAT-017, after Cuntz and others, 2010).
|
|
6408
|
+
* At each step, the target `t` joins the place `v` of the tree with the least `|v - goal(t)| + bf * PL(v)`.
|
|
6409
|
+
* `PL(v)` is the length of the tree from the root to `v`.
|
|
6410
|
+
* A place is a point of the tree, or the foot of `goal(t)` on a link of the tree. `bf` 0 gives the least ink, and a large `bf` gives direct routes.
|
|
6411
|
+
* `branch(v, t)` gives the way from `v` to the plug of the target `t`: its first point is `v`, and its last point is the plug.
|
|
6412
|
+
*/
|
|
6413
|
+
function balancedFan(root, goals, bf, branch) {
|
|
6414
|
+
const points = [{
|
|
6415
|
+
point: root,
|
|
6416
|
+
parent: -1,
|
|
6417
|
+
pl: 0,
|
|
6418
|
+
open: true
|
|
6419
|
+
}];
|
|
6420
|
+
const leaf = goals.map(() => -1);
|
|
6421
|
+
const left = new Set(goals.map((_, i) => i));
|
|
6422
|
+
while (left.size > 0) {
|
|
6423
|
+
let best = Infinity;
|
|
6424
|
+
let pick = -1;
|
|
6425
|
+
let at = -1;
|
|
6426
|
+
let foot;
|
|
6427
|
+
for (const t of left) {
|
|
6428
|
+
const g = goals[t];
|
|
6429
|
+
for (let v = 0; v < points.length; v++) {
|
|
6430
|
+
const p = points[v];
|
|
6431
|
+
if (!p.open) continue;
|
|
6432
|
+
const c = dist(p.point, g) + bf * p.pl;
|
|
6433
|
+
if (c < best - 1e-9) {
|
|
6434
|
+
best = c;
|
|
6435
|
+
pick = t;
|
|
6436
|
+
at = v;
|
|
6437
|
+
foot = void 0;
|
|
6438
|
+
}
|
|
6439
|
+
if (p.parent < 0) continue;
|
|
6440
|
+
const a = points[p.parent].point;
|
|
6441
|
+
const b = p.point;
|
|
6442
|
+
const L = dist(a, b);
|
|
6443
|
+
if (L <= 1e-9) continue;
|
|
6444
|
+
const u = ((g.x - a.x) * (b.x - a.x) + (g.y - a.y) * (b.y - a.y)) / (L * L);
|
|
6445
|
+
if (u <= 1e-6 || u >= 1 - 1e-6) continue;
|
|
6446
|
+
const f = {
|
|
6447
|
+
x: a.x + u * (b.x - a.x),
|
|
6448
|
+
y: a.y + u * (b.y - a.y)
|
|
6449
|
+
};
|
|
6450
|
+
const pl = points[p.parent].pl + u * L;
|
|
6451
|
+
const cf = dist(f, g) + bf * pl;
|
|
6452
|
+
if (cf < best - 1e-9) {
|
|
6453
|
+
best = cf;
|
|
6454
|
+
pick = t;
|
|
6455
|
+
at = v;
|
|
6456
|
+
foot = {
|
|
6457
|
+
point: f,
|
|
6458
|
+
pl
|
|
6459
|
+
};
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
}
|
|
6463
|
+
left.delete(pick);
|
|
6464
|
+
let from = at;
|
|
6465
|
+
if (foot) {
|
|
6466
|
+
points.push({
|
|
6467
|
+
point: foot.point,
|
|
6468
|
+
parent: points[at].parent,
|
|
6469
|
+
pl: foot.pl,
|
|
6470
|
+
open: true
|
|
6471
|
+
});
|
|
6472
|
+
from = points.length - 1;
|
|
6473
|
+
points[at].parent = from;
|
|
6474
|
+
}
|
|
6475
|
+
const way = branch(points[from].point, pick);
|
|
6476
|
+
let prev = from;
|
|
6477
|
+
for (let i = 1; i < way.length; i++) {
|
|
6478
|
+
const q = way[i];
|
|
6479
|
+
const base = points[prev];
|
|
6480
|
+
if (dist(q, base.point) <= 1e-9) continue;
|
|
6481
|
+
points.push({
|
|
6482
|
+
point: q,
|
|
6483
|
+
parent: prev,
|
|
6484
|
+
pl: base.pl + dist(base.point, q),
|
|
6485
|
+
open: i < way.length - 1
|
|
6486
|
+
});
|
|
6487
|
+
prev = points.length - 1;
|
|
6488
|
+
}
|
|
6489
|
+
leaf[pick] = prev;
|
|
6490
|
+
}
|
|
6491
|
+
return {
|
|
6492
|
+
points,
|
|
6493
|
+
leaf
|
|
6494
|
+
};
|
|
6495
|
+
}
|
|
6496
|
+
/** This function gives the points of the tree from the root to the point `i`. */
|
|
6497
|
+
function fanPath(fan, i) {
|
|
6498
|
+
const out = [];
|
|
6499
|
+
for (let j = i; j >= 0; j = fan.points[j].parent) out.push(fan.points[j].point);
|
|
6500
|
+
return out.reverse();
|
|
6501
|
+
}
|
|
5877
6502
|
// src/routers/trunks.ts
|
|
5878
6503
|
/** The rule that groups the edges by the collection of their target, in `data.collection`. */
|
|
5879
6504
|
const byCollection = (e) => {
|
|
@@ -5932,7 +6557,7 @@ function splitPoint(start, targets, split, free, step) {
|
|
|
5932
6557
|
while (d > 0 && !free(at(d))) d -= step;
|
|
5933
6558
|
return d > 0 ? at(d) : start;
|
|
5934
6559
|
}
|
|
5935
|
-
const dedupe$
|
|
6560
|
+
const dedupe$2 = (p) => p.filter((q, i) => i === 0 || dist(q, p[i - 1]) > 1e-9);
|
|
5936
6561
|
/**
|
|
5937
6562
|
* This function gives the place where a branch leaves its trunk. The branch starts at the last point of the trunk and ends at the plug of the target.
|
|
5938
6563
|
*
|
|
@@ -6038,10 +6663,42 @@ function trunksRouter(options) {
|
|
|
6038
6663
|
y: centers.reduce((a, p) => a + p.y, 0) / centers.length
|
|
6039
6664
|
};
|
|
6040
6665
|
const plug = placePlug(s, first.source, c);
|
|
6041
|
-
const
|
|
6666
|
+
const start = ways.out(plug);
|
|
6667
|
+
const key = id.split("\0")[2];
|
|
6668
|
+
if (options.fan === "tree") {
|
|
6669
|
+
const pts = group.map((e, i) => placePlug(targets[i], e.target, start));
|
|
6670
|
+
const bf = Math.min(1, Math.max(0, options.bf ?? .4));
|
|
6671
|
+
const fan = balancedFan(start, pts.map((pt) => ways.out(pt)), bf, (from, t) => ways.toPlug(from, pts[t]) ?? [from, pts[t].point]);
|
|
6672
|
+
const own = group.map((e, i) => ({
|
|
6673
|
+
edge: e.id,
|
|
6674
|
+
points: dedupe$2([plug.point, ...fanPath(fan, fan.leaf[i])])
|
|
6675
|
+
}));
|
|
6676
|
+
for (const r of own) points.set(r.edge, r.points);
|
|
6677
|
+
const order = options.laneOrder ?? "bundle";
|
|
6678
|
+
const common = own[0].points.slice(0, commonStart(own, 0));
|
|
6679
|
+
bundles.push({
|
|
6680
|
+
id: `b${bundles.length}`,
|
|
6681
|
+
key,
|
|
6682
|
+
node: s.id,
|
|
6683
|
+
edges: orderLanes(common, own, order),
|
|
6684
|
+
trunk: common
|
|
6685
|
+
});
|
|
6686
|
+
for (const way of sharedWays(own, common.length)) {
|
|
6687
|
+
const users = own.filter((r) => way.edges.includes(r.edge));
|
|
6688
|
+
runs.push({
|
|
6689
|
+
id: `r${runs.length}`,
|
|
6690
|
+
key,
|
|
6691
|
+
points: way.points,
|
|
6692
|
+
edges: orderLanes(way.points, users, order)
|
|
6693
|
+
});
|
|
6694
|
+
}
|
|
6695
|
+
continue;
|
|
6696
|
+
}
|
|
6697
|
+
const free = (p) => !ways.obstacles.inside(p, ways.margin);
|
|
6698
|
+
const split = options.splitAt === "gilbert" ? gilbertPoint(start, centers, options.alpha ?? .5, free, ways.margin) : splitPoint(start, centers, options.split ?? .5, free, ways.margin);
|
|
6042
6699
|
const way = ways.fromPlug(plug, split);
|
|
6043
6700
|
if (!way) continue;
|
|
6044
|
-
const trunk = dedupe$
|
|
6701
|
+
const trunk = dedupe$2(way);
|
|
6045
6702
|
const end = trunk[trunk.length - 1];
|
|
6046
6703
|
const prev = trunk.length >= 2 ? trunk[trunk.length - 2] : plug.point;
|
|
6047
6704
|
const len = dist(prev, end);
|
|
@@ -6054,11 +6711,11 @@ function trunksRouter(options) {
|
|
|
6054
6711
|
const exits = [];
|
|
6055
6712
|
group.forEach((e, i) => {
|
|
6056
6713
|
const pt = placePlug(targets[i], e.target, end);
|
|
6057
|
-
const branch = dedupe$
|
|
6714
|
+
const branch = dedupe$2(ways.toPlug(end, pt) ?? [end, pt.point]);
|
|
6058
6715
|
if (!part) {
|
|
6059
6716
|
own.push({
|
|
6060
6717
|
edge: e.id,
|
|
6061
|
-
points: dedupe$
|
|
6718
|
+
points: dedupe$2([...trunk, ...branch.slice(1)])
|
|
6062
6719
|
});
|
|
6063
6720
|
branches.push({
|
|
6064
6721
|
edge: e.id,
|
|
@@ -6072,7 +6729,6 @@ function trunksRouter(options) {
|
|
|
6072
6729
|
exit
|
|
6073
6730
|
});
|
|
6074
6731
|
});
|
|
6075
|
-
const key = id.split("\0")[2];
|
|
6076
6732
|
if (!part) {
|
|
6077
6733
|
for (const r of own) points.set(r.edge, r.points);
|
|
6078
6734
|
const edges = options.laneOrder === "run" || trunk.length < 2 ? laneOrder(dir, end, branches) : orderLanes(trunk, own, "bundle");
|
|
@@ -6090,7 +6746,7 @@ function trunksRouter(options) {
|
|
|
6090
6746
|
for (const { exit } of exits) if (!stops.has(exit.at)) stops.set(exit.at, exit.point);
|
|
6091
6747
|
const line = [...stops].sort((x, y) => x[0] - y[0]);
|
|
6092
6748
|
for (const { edge, exit } of exits) {
|
|
6093
|
-
const p = dedupe$
|
|
6749
|
+
const p = dedupe$2([...line.filter(([at]) => at <= exit.at).map(([, q]) => q), ...exit.rest]);
|
|
6094
6750
|
points.set(edge, p);
|
|
6095
6751
|
own.push({
|
|
6096
6752
|
edge,
|
|
@@ -6564,7 +7220,7 @@ function routeLanes(items, links, gaps, gutter, snap, weight, router) {
|
|
|
6564
7220
|
w: weight(links[i])
|
|
6565
7221
|
});
|
|
6566
7222
|
});
|
|
6567
|
-
return density(spans);
|
|
7223
|
+
return density$1(spans);
|
|
6568
7224
|
});
|
|
6569
7225
|
}
|
|
6570
7226
|
/** This function gives the points of the way of each link on the probe board, in the order of the links. */
|
|
@@ -6635,7 +7291,7 @@ function merge(spans) {
|
|
|
6635
7291
|
return out;
|
|
6636
7292
|
}
|
|
6637
7293
|
/** This function gives the largest sum of the weights of the spans that overlap at one place. Spans that only touch do not overlap. */
|
|
6638
|
-
function density(spans) {
|
|
7294
|
+
function density$1(spans) {
|
|
6639
7295
|
const events = [];
|
|
6640
7296
|
for (const s of spans) events.push([s.from, s.w], [s.to, -s.w]);
|
|
6641
7297
|
events.sort((u, v) => u[0] - v[0] || u[1] - v[1]);
|
|
@@ -6690,7 +7346,13 @@ const LANE_ORDERS = [
|
|
|
6690
7346
|
"ends",
|
|
6691
7347
|
"crossings"
|
|
6692
7348
|
];
|
|
6693
|
-
|
|
7349
|
+
/** The lane rules of `gapLanes`, in the order of the benchmark. */
|
|
7350
|
+
const LANE_TRACKS = [
|
|
7351
|
+
"stack",
|
|
7352
|
+
"left-edge",
|
|
7353
|
+
"free"
|
|
7354
|
+
];
|
|
7355
|
+
const EPS$2 = 1e-6;
|
|
6694
7356
|
const ROUNDS = 8;
|
|
6695
7357
|
/**
|
|
6696
7358
|
* This function gives each link that runs along a gap its own lane in the gap. The lanes of a gap are `spacing` apart, around its middle line.
|
|
@@ -6698,14 +7360,24 @@ const ROUNDS = 8;
|
|
|
6698
7360
|
* When a move puts a link through more boxes than before, the link keeps its points.
|
|
6699
7361
|
*/
|
|
6700
7362
|
function gapLanes(items, links, options = {}) {
|
|
7363
|
+
return laneGaps(items, links, options).links;
|
|
7364
|
+
}
|
|
7365
|
+
/** This function gives the lanes of each gap that holds a piece of a link, with the options of `gapLanes`. A gap without a piece is not in the list. */
|
|
7366
|
+
function gapLaneCounts(items, links, options = {}) {
|
|
7367
|
+
return laneGaps(items, links, options).counts;
|
|
7368
|
+
}
|
|
7369
|
+
/** This function does the work of `gapLanes` and `gapLaneCounts`. It gives the moved links and the lanes of each gap. */
|
|
7370
|
+
function laneGaps(items, links, options) {
|
|
6701
7371
|
const spacing = Math.max(0, options.spacing ?? 4);
|
|
6702
7372
|
const order = options.order ?? "crossings";
|
|
7373
|
+
const tracks = options.tracks ?? "stack";
|
|
6703
7374
|
const moved = links.map((l) => l.points.map((p) => ({
|
|
6704
7375
|
x: p.x,
|
|
6705
7376
|
y: p.y
|
|
6706
7377
|
})));
|
|
7378
|
+
const counts = [];
|
|
6707
7379
|
for (const g of boardGaps(items, options.snap)) {
|
|
6708
|
-
if (g.width <= EPS$
|
|
7380
|
+
if (g.width <= EPS$2) continue;
|
|
6709
7381
|
const across = g.axis;
|
|
6710
7382
|
const along = g.axis === "x" ? "y" : "x";
|
|
6711
7383
|
const lo = g.at - g.width;
|
|
@@ -6716,8 +7388,8 @@ function gapLanes(items, links, options = {}) {
|
|
|
6716
7388
|
for (let i = 2; i <= p.length - 2; i++) {
|
|
6717
7389
|
const a = p[i - 1];
|
|
6718
7390
|
const b = p[i];
|
|
6719
|
-
if (Math.abs(a[across] - b[across]) > EPS$
|
|
6720
|
-
if (Math.abs(a[along] - b[along]) <= EPS$
|
|
7391
|
+
if (Math.abs(a[across] - b[across]) > EPS$2 || a[across] <= lo + EPS$2 || a[across] >= hi - EPS$2) continue;
|
|
7392
|
+
if (Math.abs(a[along] - b[along]) <= EPS$2) continue;
|
|
6721
7393
|
const side = (q, r) => Math.sign(r[across] - q[across]);
|
|
6722
7394
|
pieces.push({
|
|
6723
7395
|
link: li,
|
|
@@ -6730,13 +7402,15 @@ function gapLanes(items, links, options = {}) {
|
|
|
6730
7402
|
});
|
|
6731
7403
|
if (pieces.length === 0) continue;
|
|
6732
7404
|
const list = orderPieces(pieces, order);
|
|
6733
|
-
const lane =
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
7405
|
+
const lane = tracks === "stack" ? stackLanes(list) : leftEdgeLanes(list, tracks === "left-edge");
|
|
7406
|
+
const n = lane.reduce((m, k) => Math.max(m, k + 1), 0);
|
|
7407
|
+
counts.push({
|
|
7408
|
+
axis: g.axis,
|
|
7409
|
+
at: g.at,
|
|
7410
|
+
width: g.width,
|
|
7411
|
+
lanes: n,
|
|
7412
|
+
pieces: list.length,
|
|
7413
|
+
density: density(list)
|
|
6740
7414
|
});
|
|
6741
7415
|
const step = Math.min(spacing, g.width / (n + 1));
|
|
6742
7416
|
const mid = (lo + hi) / 2;
|
|
@@ -6748,20 +7422,81 @@ function gapLanes(items, links, options = {}) {
|
|
|
6748
7422
|
}
|
|
6749
7423
|
const boxes = new Grid(Math.max(20, meanSide(items)));
|
|
6750
7424
|
for (const i of items) boxes.add(i.box, i.box);
|
|
6751
|
-
return
|
|
6752
|
-
|
|
6753
|
-
|
|
7425
|
+
return {
|
|
7426
|
+
links: links.map((l, i) => hits(boxes, moved[i]) > hits(boxes, l.points) ? l : {
|
|
7427
|
+
...l,
|
|
7428
|
+
points: moved[i]
|
|
7429
|
+
}),
|
|
7430
|
+
counts
|
|
7431
|
+
};
|
|
7432
|
+
}
|
|
7433
|
+
/** This function gives the lanes of the rule `stack`. A piece goes one lane past each piece before it in the order that it overlaps. */
|
|
7434
|
+
function stackLanes(list) {
|
|
7435
|
+
const lane = [];
|
|
7436
|
+
list.forEach((P, i) => {
|
|
7437
|
+
let k = 0;
|
|
7438
|
+
for (let j = 0; j < i; j++) if (overlaps(list[j], P)) k = Math.max(k, lane[j] + 1);
|
|
7439
|
+
lane.push(k);
|
|
6754
7440
|
});
|
|
7441
|
+
return lane;
|
|
7442
|
+
}
|
|
7443
|
+
/**
|
|
7444
|
+
* This function gives the lanes of the left-edge rule (Hashimoto and Stevens, 1971) for the pieces in lane order.
|
|
7445
|
+
* With `constrained`, a piece stays below a later piece that it overlaps when the swap of the two adds crossings (`cost`).
|
|
7446
|
+
* These constraints follow the lane order, so they have no cycle. Each pass fills one lane, from the near side.
|
|
7447
|
+
* A pass takes the free pieces whose constraints are met, in the order of their start. It puts each piece that does not overlap the last piece of the lane.
|
|
7448
|
+
*/
|
|
7449
|
+
function leftEdgeLanes(list, constrained) {
|
|
7450
|
+
const n = list.length;
|
|
7451
|
+
const below = list.map(() => []);
|
|
7452
|
+
const wait = new Array(n).fill(0);
|
|
7453
|
+
if (constrained) for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) {
|
|
7454
|
+
const P = list[i];
|
|
7455
|
+
const Q = list[j];
|
|
7456
|
+
if (overlaps(P, Q) && cost$1(Q, P) > cost$1(P, Q)) {
|
|
7457
|
+
below[i].push(j);
|
|
7458
|
+
wait[j]++;
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
const byStart = list.map((_, i) => i).sort((a, b) => list[a].lo - list[b].lo || a - b);
|
|
7462
|
+
const lane = new Array(n).fill(-1);
|
|
7463
|
+
let placed = 0;
|
|
7464
|
+
for (let track = 0; placed < n; track++) {
|
|
7465
|
+
const now = [];
|
|
7466
|
+
let end = -Infinity;
|
|
7467
|
+
for (const i of byStart) {
|
|
7468
|
+
if (lane[i] >= 0 || wait[i] > 0 || list[i].lo < end - EPS$2) continue;
|
|
7469
|
+
now.push(i);
|
|
7470
|
+
end = list[i].hi;
|
|
7471
|
+
}
|
|
7472
|
+
for (const i of now) lane[i] = track;
|
|
7473
|
+
for (const i of now) for (const j of below[i]) wait[j]--;
|
|
7474
|
+
placed += now.length;
|
|
7475
|
+
}
|
|
7476
|
+
return lane;
|
|
7477
|
+
}
|
|
7478
|
+
/** This function gives the largest number of pieces that overlap at one place. Pieces that only touch do not overlap. */
|
|
7479
|
+
function density(list) {
|
|
7480
|
+
const events = [];
|
|
7481
|
+
for (const P of list) events.push([P.lo, 1], [P.hi, -1]);
|
|
7482
|
+
events.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
7483
|
+
let open = 0;
|
|
7484
|
+
let most = 0;
|
|
7485
|
+
for (const [, d] of events) {
|
|
7486
|
+
open += d;
|
|
7487
|
+
most = Math.max(most, open);
|
|
7488
|
+
}
|
|
7489
|
+
return most;
|
|
6755
7490
|
}
|
|
6756
|
-
const overlaps = (P, Q) => Math.min(P.hi, Q.hi) - Math.max(P.lo, Q.lo) > EPS$
|
|
7491
|
+
const overlaps = (P, Q) => Math.min(P.hi, Q.hi) - Math.max(P.lo, Q.lo) > EPS$2;
|
|
6757
7492
|
/**
|
|
6758
7493
|
* This function counts the crossings when the piece `P` is on the near side of the piece `Q`.
|
|
6759
7494
|
* A turn of `P` to the far side crosses `Q` when it is inside the span of `Q`. A turn of `Q` to the near side crosses `P` in the same way.
|
|
6760
7495
|
*/
|
|
6761
7496
|
function cost$1(P, Q) {
|
|
6762
7497
|
let c = 0;
|
|
6763
|
-
for (const [y, s] of P.turns) if (s > 0 && y > Q.lo + EPS$
|
|
6764
|
-
for (const [y, s] of Q.turns) if (s < 0 && y > P.lo + EPS$
|
|
7498
|
+
for (const [y, s] of P.turns) if (s > 0 && y > Q.lo + EPS$2 && y < Q.hi - EPS$2) c++;
|
|
7499
|
+
for (const [y, s] of Q.turns) if (s < 0 && y > P.lo + EPS$2 && y < P.hi - EPS$2) c++;
|
|
6765
7500
|
return c;
|
|
6766
7501
|
}
|
|
6767
7502
|
/** This function gives the pieces of one gap in lane order, from the near side to the far side. */
|
|
@@ -7857,7 +8592,23 @@ const RUN_LANE_ORDER = {
|
|
|
7857
8592
|
default: "run"
|
|
7858
8593
|
};
|
|
7859
8594
|
/** The declarations of the options of the routers `corners` and `corners-smooth`. */
|
|
7860
|
-
const CORNERS_SCHEMA =
|
|
8595
|
+
const CORNERS_SCHEMA = {
|
|
8596
|
+
...WAY_SCHEMA,
|
|
8597
|
+
plugs: {
|
|
8598
|
+
type: "enum",
|
|
8599
|
+
values: ["facing", "side"],
|
|
8600
|
+
default: "facing",
|
|
8601
|
+
describe: "With `facing`, a free plug faces the other end. With `side`, the search starts from the whole free side, and the plug goes where the way leaves (WILD-001)."
|
|
8602
|
+
},
|
|
8603
|
+
plugGap: {
|
|
8604
|
+
type: "number",
|
|
8605
|
+
min: 0,
|
|
8606
|
+
max: 24,
|
|
8607
|
+
step: 1,
|
|
8608
|
+
default: 0,
|
|
8609
|
+
describe: "With a gap above 0, the free plugs of a side keep their order and at least this gap (WILD-002). 0 keeps each plug where it is."
|
|
8610
|
+
}
|
|
8611
|
+
};
|
|
7861
8612
|
/** The declarations of the options of the router `trunks`. */
|
|
7862
8613
|
const TRUNKS_SCHEMA = {
|
|
7863
8614
|
...WAY_SCHEMA,
|
|
@@ -7887,6 +8638,34 @@ const TRUNKS_SCHEMA = {
|
|
|
7887
8638
|
values: ["part", "split"],
|
|
7888
8639
|
default: "part",
|
|
7889
8640
|
describe: "With `part`, a lane leaves the trunk where its way parts from the other ways. With `split`, each lane leaves at the split point."
|
|
8641
|
+
},
|
|
8642
|
+
splitAt: {
|
|
8643
|
+
type: "enum",
|
|
8644
|
+
values: ["share", "gilbert"],
|
|
8645
|
+
default: "share",
|
|
8646
|
+
describe: "With `share`, the split point is at `split` of the way. With `gilbert`, it is where the flow cost is least (EDA-030)."
|
|
8647
|
+
},
|
|
8648
|
+
alpha: {
|
|
8649
|
+
type: "number",
|
|
8650
|
+
min: 0,
|
|
8651
|
+
max: 1,
|
|
8652
|
+
step: .05,
|
|
8653
|
+
default: .5,
|
|
8654
|
+
describe: "The exponent of the flow cost of the split `gilbert`. A smaller value moves the split toward the targets."
|
|
8655
|
+
},
|
|
8656
|
+
fan: {
|
|
8657
|
+
type: "enum",
|
|
8658
|
+
values: ["split", "tree"],
|
|
8659
|
+
default: "split",
|
|
8660
|
+
describe: "With `split`, the edges of a group share a trunk to a split point. With `tree`, they grow a balancing-factor tree (NAT-017)."
|
|
8661
|
+
},
|
|
8662
|
+
bf: {
|
|
8663
|
+
type: "number",
|
|
8664
|
+
min: 0,
|
|
8665
|
+
max: 1,
|
|
8666
|
+
step: .05,
|
|
8667
|
+
default: .4,
|
|
8668
|
+
describe: "The balancing factor of the fan `tree`. 0 gives the least ink, and a larger factor gives more direct routes."
|
|
7890
8669
|
}
|
|
7891
8670
|
};
|
|
7892
8671
|
/** The declarations of the options of the routers `shared` and `shared-all`. */
|
|
@@ -7904,7 +8683,46 @@ const SHARED_SCHEMA = {
|
|
|
7904
8683
|
default: .5,
|
|
7905
8684
|
describe: "The cost of a link that an edge with the same key uses already, as a share of its length."
|
|
7906
8685
|
},
|
|
7907
|
-
laneOrder: LANE_ORDER
|
|
8686
|
+
laneOrder: LANE_ORDER,
|
|
8687
|
+
cost: {
|
|
8688
|
+
type: "enum",
|
|
8689
|
+
values: ["fixed", "flow"],
|
|
8690
|
+
default: "fixed",
|
|
8691
|
+
describe: "With `fixed`, a shared link costs `discount` of its length. With `flow`, a link with `f` edges costs `len * f^alpha` (EDA-029)."
|
|
8692
|
+
},
|
|
8693
|
+
alpha: {
|
|
8694
|
+
type: "number",
|
|
8695
|
+
min: .1,
|
|
8696
|
+
max: 1,
|
|
8697
|
+
step: .05,
|
|
8698
|
+
default: .6,
|
|
8699
|
+
describe: "The exponent of the cost `flow`. A smaller value gives more sharing."
|
|
8700
|
+
},
|
|
8701
|
+
passes: {
|
|
8702
|
+
type: "integer",
|
|
8703
|
+
min: 1,
|
|
8704
|
+
max: 5,
|
|
8705
|
+
step: 1,
|
|
8706
|
+
default: 1,
|
|
8707
|
+
describe: "In each pass after the first, each edge finds its way again with the ways of the other edges fixed (HWY-039)."
|
|
8708
|
+
},
|
|
8709
|
+
order: {
|
|
8710
|
+
type: "enum",
|
|
8711
|
+
values: [
|
|
8712
|
+
"span",
|
|
8713
|
+
"shortest",
|
|
8714
|
+
"load",
|
|
8715
|
+
"input"
|
|
8716
|
+
],
|
|
8717
|
+
default: "span",
|
|
8718
|
+
describe: "The order of the edges of a key: the longest first, the shortest first, the most edges between two sections first, or the input order (NAT-024)."
|
|
8719
|
+
},
|
|
8720
|
+
merge: {
|
|
8721
|
+
type: "enum",
|
|
8722
|
+
values: ["corner", "snell"],
|
|
8723
|
+
default: "corner",
|
|
8724
|
+
describe: "With `corner`, a route joins a run at a corner of the graph. With `snell`, it joins at the angle `acos(d)` (NAT-035)."
|
|
8725
|
+
}
|
|
7908
8726
|
};
|
|
7909
8727
|
/** The declarations of the options of the highway strategy `corridors` (HWY-031). */
|
|
7910
8728
|
const CORRIDOR_SCHEMA = {
|
|
@@ -8671,11 +9489,11 @@ const BUILT_IN_STRATEGIES = Object.freeze([
|
|
|
8671
9489
|
/** The registry of the built-in strategies. The benchmark and the site take their lists from it. */
|
|
8672
9490
|
const STRATEGIES = createRegistry(BUILT_IN_STRATEGIES);
|
|
8673
9491
|
// src/bench/measures/curves.ts
|
|
8674
|
-
const EPS = 1e-9;
|
|
9492
|
+
const EPS$1 = 1e-9;
|
|
8675
9493
|
const DEG = 180 / Math.PI;
|
|
8676
9494
|
const unit = (x, y) => {
|
|
8677
9495
|
const l = Math.hypot(x, y);
|
|
8678
|
-
return l > EPS ? {
|
|
9496
|
+
return l > EPS$1 ? {
|
|
8679
9497
|
x: x / l,
|
|
8680
9498
|
y: y / l
|
|
8681
9499
|
} : void 0;
|
|
@@ -8694,7 +9512,7 @@ function frame(from, s, t) {
|
|
|
8694
9512
|
x: -Math.sin(a) * sign,
|
|
8695
9513
|
y: Math.cos(a) * sign
|
|
8696
9514
|
},
|
|
8697
|
-
curvature: s.radius > EPS ? sign / s.radius : 0
|
|
9515
|
+
curvature: s.radius > EPS$1 ? sign / s.radius : 0
|
|
8698
9516
|
};
|
|
8699
9517
|
}
|
|
8700
9518
|
const [p0, p1, p2, p3] = [
|
|
@@ -8718,7 +9536,7 @@ function frame(from, s, t) {
|
|
|
8718
9536
|
y: 6 * (p1.y - 2 * p2.y + p3.y)
|
|
8719
9537
|
};
|
|
8720
9538
|
const speed = Math.hypot(d.x, d.y);
|
|
8721
|
-
if (speed > EPS) return {
|
|
9539
|
+
if (speed > EPS$1) return {
|
|
8722
9540
|
tangent: {
|
|
8723
9541
|
x: d.x / speed,
|
|
8724
9542
|
y: d.y / speed
|
|
@@ -8787,13 +9605,13 @@ function g2Defect(segments, u) {
|
|
|
8787
9605
|
*/
|
|
8788
9606
|
function bendingEnergy(points, tolerance = .25) {
|
|
8789
9607
|
const q = [];
|
|
8790
|
-
for (const v of points) if (q.length === 0 || dist(q[q.length - 1], v) > EPS) q.push(v);
|
|
9608
|
+
for (const v of points) if (q.length === 0 || dist(q[q.length - 1], v) > EPS$1) q.push(v);
|
|
8791
9609
|
if (q.length < 3) return 0;
|
|
8792
9610
|
const phi = turns(q);
|
|
8793
9611
|
let sum = 0;
|
|
8794
9612
|
for (let i = 1; i + 1 < q.length; i++) {
|
|
8795
9613
|
const t = Math.abs(phi[i - 1]);
|
|
8796
|
-
if (t < EPS) continue;
|
|
9614
|
+
if (t < EPS$1) continue;
|
|
8797
9615
|
const l = Math.min((dist(q[i - 1], q[i]) + dist(q[i], q[i + 1])) / 2, 8 * tolerance / t);
|
|
8798
9616
|
sum += t * t / l;
|
|
8799
9617
|
}
|
|
@@ -9420,15 +10238,7 @@ function measureBoard(graph, spacing, order) {
|
|
|
9420
10238
|
order
|
|
9421
10239
|
}) : level;
|
|
9422
10240
|
const boxes = new Map(graph.nodes.map((n) => [n.id, n.box]));
|
|
9423
|
-
const
|
|
9424
|
-
for (const e of graph.edges) {
|
|
9425
|
-
const key = `${e.source.node}\u0000${e.target.node}`;
|
|
9426
|
-
if (!edgeOf.has(key)) edgeOf.set(key, e.id);
|
|
9427
|
-
}
|
|
9428
|
-
const q = measureQuality(graph, { routes: drawn.map((l) => ({
|
|
9429
|
-
edge: edgeOf.get(`${l.source}\u0000${l.target}`),
|
|
9430
|
-
points: l.points
|
|
9431
|
-
})) });
|
|
10241
|
+
const q = farQuality(graph, drawn);
|
|
9432
10242
|
const segments = segmentsOf(sectionsRouter({ name: "sections" }).route(graph));
|
|
9433
10243
|
const lanes = laneRoutes(segments, new Set(segments.segments.filter((s) => s.count > 1).map((s) => runKey(s.edges))), spacing);
|
|
9434
10244
|
return {
|
|
@@ -9439,22 +10249,46 @@ function measureBoard(graph, spacing, order) {
|
|
|
9439
10249
|
laneThrough: measureRouteFaults(graph, { routes: lanes }).through
|
|
9440
10250
|
};
|
|
9441
10251
|
}
|
|
9442
|
-
/**
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
10252
|
+
/** This function gives the links of the far view of a board: `levelLinks` with each node as one item. */
|
|
10253
|
+
function farLinks(graph) {
|
|
10254
|
+
return levelLinks(graph.nodes.map((n) => ({
|
|
10255
|
+
id: n.id,
|
|
10256
|
+
box: n.box,
|
|
10257
|
+
nodes: [n.id]
|
|
10258
|
+
})), roomLinks(graph).map((l) => ({
|
|
10259
|
+
source: l.source,
|
|
10260
|
+
target: l.target,
|
|
10261
|
+
count: l.count ?? 1
|
|
10262
|
+
})));
|
|
10263
|
+
}
|
|
10264
|
+
/** This function measures the links of the far view with `measureQuality`. Each link stands for the first edge of its pair, because `measureQuality` reads routes of edges. */
|
|
10265
|
+
function farQuality(graph, drawn) {
|
|
10266
|
+
const edgeOf = /* @__PURE__ */ new Map();
|
|
10267
|
+
for (const e of graph.edges) {
|
|
10268
|
+
const key = `${e.source.node}\u0000${e.target.node}`;
|
|
10269
|
+
if (!edgeOf.has(key)) edgeOf.set(key, e.id);
|
|
10270
|
+
}
|
|
10271
|
+
return measureQuality(graph, { routes: drawn.map((l) => ({
|
|
10272
|
+
edge: edgeOf.get(`${l.source}\u0000${l.target}`),
|
|
10273
|
+
points: l.points
|
|
10274
|
+
})) });
|
|
10275
|
+
}
|
|
10276
|
+
/**
|
|
10277
|
+
* This function measures a packed board, the board with each gap open to the gutter only, and the board that each room strategy opens.
|
|
10278
|
+
* The options are the same for each strategy, except `method`. The default strategies are the methods `routes` and `lines` of `roomDemand`.
|
|
10279
|
+
*/
|
|
10280
|
+
function benchRoom(fixture, graph, options = {}, methods = [roomStrategies.routes, roomStrategies.lines]) {
|
|
10281
|
+
const spacing = options.spacing ?? 4;
|
|
10282
|
+
const area0 = areaOf$1(graph.nodes);
|
|
10283
|
+
const links = roomLinks(graph);
|
|
10284
|
+
const row = (board, nodes, ms) => ({
|
|
10285
|
+
fixture,
|
|
10286
|
+
board,
|
|
10287
|
+
area: area0 > 0 ? areaOf$1(nodes) / area0 : 1,
|
|
10288
|
+
ms,
|
|
10289
|
+
...measureBoard({
|
|
10290
|
+
nodes,
|
|
10291
|
+
edges: graph.edges
|
|
9458
10292
|
}, spacing)
|
|
9459
10293
|
});
|
|
9460
10294
|
const rows = [row("packed", graph.nodes, 0)];
|
|
@@ -9779,6 +10613,94 @@ function formatBendRoom(rows) {
|
|
|
9779
10613
|
...rows.map((r) => `| ${r.router} | ${r.fixture} | ${r.board} | ${r.area.toFixed(3)} | ${r.bundles} | ${(r.worstSpacing * 100).toFixed(1)}% | ${r.minRadius === Infinity ? "-" : r.minRadius.toFixed(2)} | ${r.underMin} | ${r.through} |`)
|
|
9780
10614
|
].join("\n");
|
|
9781
10615
|
}
|
|
10616
|
+
/**
|
|
10617
|
+
* This function gives R12 (lane separation) of the links. Two legs of two links are near when they are parallel and nearer than `near`.
|
|
10618
|
+
* R12 is the length of the near legs, as a share of the ink.
|
|
10619
|
+
* It reads only the legs on an axis. The links of the far view have no other legs.
|
|
10620
|
+
*/
|
|
10621
|
+
function laneSeparation(links, near) {
|
|
10622
|
+
let ink = 0;
|
|
10623
|
+
let close = 0;
|
|
10624
|
+
for (const across of ["x", "y"]) {
|
|
10625
|
+
const along = across === "x" ? "y" : "x";
|
|
10626
|
+
const legs = [];
|
|
10627
|
+
links.forEach((l, li) => {
|
|
10628
|
+
for (let i = 1; i < l.points.length; i++) {
|
|
10629
|
+
const a = l.points[i - 1];
|
|
10630
|
+
const b = l.points[i];
|
|
10631
|
+
if (Math.abs(a[across] - b[across]) > 1e-6 || Math.abs(a[along] - b[along]) <= 1e-6) continue;
|
|
10632
|
+
legs.push({
|
|
10633
|
+
link: li,
|
|
10634
|
+
at: a[across],
|
|
10635
|
+
lo: Math.min(a[along], b[along]),
|
|
10636
|
+
hi: Math.max(a[along], b[along])
|
|
10637
|
+
});
|
|
10638
|
+
}
|
|
10639
|
+
});
|
|
10640
|
+
legs.sort((p, q) => p.at - q.at);
|
|
10641
|
+
for (let i = 0; i < legs.length; i++) {
|
|
10642
|
+
const P = legs[i];
|
|
10643
|
+
ink += P.hi - P.lo;
|
|
10644
|
+
for (let j = i + 1; j < legs.length && legs[j].at - P.at < near; j++) {
|
|
10645
|
+
const Q = legs[j];
|
|
10646
|
+
if (Q.link !== P.link) close += Math.max(0, Math.min(P.hi, Q.hi) - Math.max(P.lo, Q.lo));
|
|
10647
|
+
}
|
|
10648
|
+
}
|
|
10649
|
+
}
|
|
10650
|
+
return ink > 0 ? close / ink : 0;
|
|
10651
|
+
}
|
|
10652
|
+
/**
|
|
10653
|
+
* This function measures each lane rule of `gapLanes` (EDA-004) on the board as it is, and on the board that `makeRoom` opens.
|
|
10654
|
+
* The lane order is `crossings` for each rule. The lanes are `spacing` apart where the gap has room.
|
|
10655
|
+
*/
|
|
10656
|
+
function benchTracks(fixture, graph, spacing = 4, rules = LANE_TRACKS) {
|
|
10657
|
+
const open = makeRoom(graph.nodes, roomLinks(graph), {
|
|
10658
|
+
gutter: 8,
|
|
10659
|
+
spacing,
|
|
10660
|
+
method: "routes"
|
|
10661
|
+
}).items;
|
|
10662
|
+
const rows = [];
|
|
10663
|
+
for (const [board, nodes] of [["given", graph.nodes], ["open", open]]) {
|
|
10664
|
+
const g = {
|
|
10665
|
+
nodes,
|
|
10666
|
+
edges: graph.edges
|
|
10667
|
+
};
|
|
10668
|
+
const level = farLinks(g);
|
|
10669
|
+
const boxes = new Map(nodes.map((n) => [n.id, n.box]));
|
|
10670
|
+
for (const tracks of rules) {
|
|
10671
|
+
const options = {
|
|
10672
|
+
spacing,
|
|
10673
|
+
order: "crossings",
|
|
10674
|
+
tracks
|
|
10675
|
+
};
|
|
10676
|
+
const counts = gapLaneCounts(nodes, level, options);
|
|
10677
|
+
const drawn = gapLanes(nodes, level, options);
|
|
10678
|
+
const q = farQuality(g, drawn);
|
|
10679
|
+
rows.push({
|
|
10680
|
+
fixture,
|
|
10681
|
+
board,
|
|
10682
|
+
tracks,
|
|
10683
|
+
gaps: counts.length,
|
|
10684
|
+
lanes: counts.reduce((s, c) => s + c.lanes, 0),
|
|
10685
|
+
density: counts.reduce((s, c) => s + c.density, 0),
|
|
10686
|
+
squeezed: counts.filter((c) => c.width / (c.lanes + 1) < spacing - 1e-9).length,
|
|
10687
|
+
crossings: q.crossings,
|
|
10688
|
+
ink: q.ink,
|
|
10689
|
+
separation: laneSeparation(drawn, spacing),
|
|
10690
|
+
through: measureLinks(boxes, drawn).through
|
|
10691
|
+
});
|
|
10692
|
+
}
|
|
10693
|
+
}
|
|
10694
|
+
return rows;
|
|
10695
|
+
}
|
|
10696
|
+
/** This function writes the rows of `benchTracks` as a Markdown table. */
|
|
10697
|
+
function formatTracks(rows) {
|
|
10698
|
+
return [
|
|
10699
|
+
"| fixture | board | tracks | gaps | lanes | density | squeezed | crossings | ink | R12 | through |",
|
|
10700
|
+
"|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|",
|
|
10701
|
+
...rows.map((r) => `| ${r.fixture} | ${r.board} | ${r.tracks} | ${r.gaps} | ${r.lanes} | ${r.density} | ${r.squeezed} | ${r.crossings} | ${Math.round(r.ink)} | ${r.separation.toFixed(4)} | ${r.through} |`)
|
|
10702
|
+
].join("\n");
|
|
10703
|
+
}
|
|
9782
10704
|
// src/bench/sections.ts
|
|
9783
10705
|
const now$1 = () => globalThis.performance?.now() ?? Date.now();
|
|
9784
10706
|
/** This function gives the number of items of the first level with more than one item, or 0 for a result without levels. */
|
|
@@ -10268,6 +11190,104 @@ function ambiguity(graph, routes, options = {}) {
|
|
|
10268
11190
|
estimated: !exact
|
|
10269
11191
|
};
|
|
10270
11192
|
}
|
|
11193
|
+
/** The drawn segments of each route, as keys, in the order of its points. A segment shorter than the grain of `segmentKey` has no key. */
|
|
11194
|
+
function routeKeys(routes) {
|
|
11195
|
+
const keys = /* @__PURE__ */ new Map();
|
|
11196
|
+
const users = /* @__PURE__ */ new Map();
|
|
11197
|
+
for (const r of routes) {
|
|
11198
|
+
const own = [];
|
|
11199
|
+
for (let i = 1; i < r.points.length; i++) {
|
|
11200
|
+
const k = segmentKey(r.points[i - 1], r.points[i]);
|
|
11201
|
+
if (k === void 0) continue;
|
|
11202
|
+
own.push(k);
|
|
11203
|
+
let set = users.get(k);
|
|
11204
|
+
if (!set) users.set(k, set = /* @__PURE__ */ new Set());
|
|
11205
|
+
set.add(r.edge);
|
|
11206
|
+
}
|
|
11207
|
+
keys.set(r.edge, own);
|
|
11208
|
+
}
|
|
11209
|
+
return {
|
|
11210
|
+
keys,
|
|
11211
|
+
users
|
|
11212
|
+
};
|
|
11213
|
+
}
|
|
11214
|
+
/**
|
|
11215
|
+
* This function gives the bundled share of a result (B2 of MEAS0): `count(e : e is in a run or a bundle) / m`.
|
|
11216
|
+
* An edge is also in a bundle when its route shares a drawn segment with the route of another edge. Thus a router without `runs` and `bundles` gets a value too.
|
|
11217
|
+
* The scale is 0 to 1. A higher value is better only in context. The cost is linear.
|
|
11218
|
+
*/
|
|
11219
|
+
function bundledShare(result) {
|
|
11220
|
+
const routes = result.routes;
|
|
11221
|
+
if (routes.length === 0) return 0;
|
|
11222
|
+
const shared = /* @__PURE__ */ new Set();
|
|
11223
|
+
for (const run of result.runs ?? []) for (const e of run.edges) shared.add(e);
|
|
11224
|
+
for (const b of result.bundles ?? []) if (b.edges.length > 1) for (const e of b.edges) shared.add(e);
|
|
11225
|
+
const { keys, users } = routeKeys(routes);
|
|
11226
|
+
for (const [edge, own] of keys) if (own.some((k) => (users.get(k)?.size ?? 0) > 1)) shared.add(edge);
|
|
11227
|
+
let count = 0;
|
|
11228
|
+
for (const r of routes) if (shared.has(r.edge)) count++;
|
|
11229
|
+
return count / routes.length;
|
|
11230
|
+
}
|
|
11231
|
+
/**
|
|
11232
|
+
* This function gives the bundle stretch (B3 of MEAS0): `st(e) = L(r_e) / L(r*_e)` for each edge, with `r*_e` the base route of the edge.
|
|
11233
|
+
* The base routes are the routes of `corners` on the same input. An edge without a base route, or with a base route of 1 unit or less, does not count.
|
|
11234
|
+
* The cost is an extra run of the base router, and then linear.
|
|
11235
|
+
*/
|
|
11236
|
+
function bundleStretch(routes, base) {
|
|
11237
|
+
const baseLength = /* @__PURE__ */ new Map();
|
|
11238
|
+
for (const r of base) baseLength.set(r.edge, polylineLength(r.points));
|
|
11239
|
+
const st = [];
|
|
11240
|
+
for (const r of routes) {
|
|
11241
|
+
const b = baseLength.get(r.edge);
|
|
11242
|
+
if (b === void 0 || b <= 1 || r.points.length < 2) continue;
|
|
11243
|
+
st.push(polylineLength(r.points) / b);
|
|
11244
|
+
}
|
|
11245
|
+
if (st.length === 0) return {
|
|
11246
|
+
median: 1,
|
|
11247
|
+
q95: 1,
|
|
11248
|
+
edges: 0
|
|
11249
|
+
};
|
|
11250
|
+
return {
|
|
11251
|
+
median: quantile(st, .5),
|
|
11252
|
+
q95: quantile(st, .95),
|
|
11253
|
+
edges: st.length
|
|
11254
|
+
};
|
|
11255
|
+
}
|
|
11256
|
+
/**
|
|
11257
|
+
* This function gives the merge angle of the routes (B6 of MEAS0): the largest turn of a lane where it joins or leaves a run, in degrees.
|
|
11258
|
+
* A run is a drawn segment that the routes of two edges or more share. A route joins or leaves a run at a vertex where the edges on its segment change.
|
|
11259
|
+
* One of the two segments at that vertex has two edges or more. The turn is the angle between the two segments.
|
|
11260
|
+
* Routes without a shared segment give 0. The scale is 0 to 180 degrees, and lower is better. The cost is linear.
|
|
11261
|
+
*/
|
|
11262
|
+
function mergeAngle(routes) {
|
|
11263
|
+
const { users } = routeKeys(routes);
|
|
11264
|
+
const sig = /* @__PURE__ */ new Map();
|
|
11265
|
+
for (const [k, set] of users) sig.set(k, [...set].sort().join("\0"));
|
|
11266
|
+
let max = 0;
|
|
11267
|
+
for (const r of routes) {
|
|
11268
|
+
let prev;
|
|
11269
|
+
for (let i = 1; i < r.points.length; i++) {
|
|
11270
|
+
const a = r.points[i - 1];
|
|
11271
|
+
const b = r.points[i];
|
|
11272
|
+
const k = segmentKey(a, b);
|
|
11273
|
+
if (k === void 0) continue;
|
|
11274
|
+
if (prev && sig.get(prev.k) !== sig.get(k) && ((users.get(prev.k)?.size ?? 0) > 1 || (users.get(k)?.size ?? 0) > 1)) {
|
|
11275
|
+
const ux = prev.b.x - prev.a.x;
|
|
11276
|
+
const uy = prev.b.y - prev.a.y;
|
|
11277
|
+
const vx = b.x - a.x;
|
|
11278
|
+
const vy = b.y - a.y;
|
|
11279
|
+
const turn = Math.abs(Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy));
|
|
11280
|
+
max = Math.max(max, turn * 180 / Math.PI);
|
|
11281
|
+
}
|
|
11282
|
+
prev = {
|
|
11283
|
+
k,
|
|
11284
|
+
a,
|
|
11285
|
+
b
|
|
11286
|
+
};
|
|
11287
|
+
}
|
|
11288
|
+
}
|
|
11289
|
+
return max;
|
|
11290
|
+
}
|
|
10271
11291
|
// src/bench/measures/levels.ts
|
|
10272
11292
|
/** This function gives the levels in the order of their depth, from the far view to the near view. */
|
|
10273
11293
|
function byDepth(levels) {
|
|
@@ -10631,27 +11651,337 @@ function paretoFront(items, values, objectives) {
|
|
|
10631
11651
|
const v = items.map(values);
|
|
10632
11652
|
return items.filter((_, i) => !v.some((w, j) => j !== i && dominates(w, v[i], objectives)));
|
|
10633
11653
|
}
|
|
10634
|
-
// src/
|
|
10635
|
-
const
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
11654
|
+
// src/bench/screen/hash.ts
|
|
11655
|
+
const K = [
|
|
11656
|
+
1116352408,
|
|
11657
|
+
1899447441,
|
|
11658
|
+
3049323471,
|
|
11659
|
+
3921009573,
|
|
11660
|
+
961987163,
|
|
11661
|
+
1508970993,
|
|
11662
|
+
2453635748,
|
|
11663
|
+
2870763221,
|
|
11664
|
+
3624381080,
|
|
11665
|
+
310598401,
|
|
11666
|
+
607225278,
|
|
11667
|
+
1426881987,
|
|
11668
|
+
1925078388,
|
|
11669
|
+
2162078206,
|
|
11670
|
+
2614888103,
|
|
11671
|
+
3248222580,
|
|
11672
|
+
3835390401,
|
|
11673
|
+
4022224774,
|
|
11674
|
+
264347078,
|
|
11675
|
+
604807628,
|
|
11676
|
+
770255983,
|
|
11677
|
+
1249150122,
|
|
11678
|
+
1555081692,
|
|
11679
|
+
1996064986,
|
|
11680
|
+
2554220882,
|
|
11681
|
+
2821834349,
|
|
11682
|
+
2952996808,
|
|
11683
|
+
3210313671,
|
|
11684
|
+
3336571891,
|
|
11685
|
+
3584528711,
|
|
11686
|
+
113926993,
|
|
11687
|
+
338241895,
|
|
11688
|
+
666307205,
|
|
11689
|
+
773529912,
|
|
11690
|
+
1294757372,
|
|
11691
|
+
1396182291,
|
|
11692
|
+
1695183700,
|
|
11693
|
+
1986661051,
|
|
11694
|
+
2177026350,
|
|
11695
|
+
2456956037,
|
|
11696
|
+
2730485921,
|
|
11697
|
+
2820302411,
|
|
11698
|
+
3259730800,
|
|
11699
|
+
3345764771,
|
|
11700
|
+
3516065817,
|
|
11701
|
+
3600352804,
|
|
11702
|
+
4094571909,
|
|
11703
|
+
275423344,
|
|
11704
|
+
430227734,
|
|
11705
|
+
506948616,
|
|
11706
|
+
659060556,
|
|
11707
|
+
883997877,
|
|
11708
|
+
958139571,
|
|
11709
|
+
1322822218,
|
|
11710
|
+
1537002063,
|
|
11711
|
+
1747873779,
|
|
11712
|
+
1955562222,
|
|
11713
|
+
2024104815,
|
|
11714
|
+
2227730452,
|
|
11715
|
+
2361852424,
|
|
11716
|
+
2428436474,
|
|
11717
|
+
2756734187,
|
|
11718
|
+
3204031479,
|
|
11719
|
+
3329325298
|
|
11720
|
+
];
|
|
11721
|
+
/** This function gives the bytes of a string in UTF-8. */
|
|
11722
|
+
function utf8(text) {
|
|
11723
|
+
const out = [];
|
|
11724
|
+
for (const ch of text) {
|
|
11725
|
+
const c = ch.codePointAt(0);
|
|
11726
|
+
if (c < 128) out.push(c);
|
|
11727
|
+
else if (c < 2048) out.push(192 | c >> 6, 128 | c & 63);
|
|
11728
|
+
else if (c < 65536) out.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
|
|
11729
|
+
else out.push(240 | c >> 18, 128 | c >> 12 & 63, 128 | c >> 6 & 63, 128 | c & 63);
|
|
10643
11730
|
}
|
|
10644
|
-
return
|
|
11731
|
+
return Uint8Array.from(out);
|
|
11732
|
+
}
|
|
11733
|
+
/** This function gives the SHA-256 digest of a string, as 64 lowercase hex characters. It encodes the string in UTF-8. */
|
|
11734
|
+
function sha256Hex(text) {
|
|
11735
|
+
const bytes = utf8(text);
|
|
11736
|
+
const bits = bytes.length * 8;
|
|
11737
|
+
const total = Math.ceil((bytes.length + 9) / 64) * 64;
|
|
11738
|
+
const data = new Uint8Array(total);
|
|
11739
|
+
data.set(bytes);
|
|
11740
|
+
data[bytes.length] = 128;
|
|
11741
|
+
const view = new DataView(data.buffer);
|
|
11742
|
+
view.setUint32(total - 8, Math.floor(bits / 4294967296));
|
|
11743
|
+
view.setUint32(total - 4, bits >>> 0);
|
|
11744
|
+
const h = [
|
|
11745
|
+
1779033703,
|
|
11746
|
+
3144134277,
|
|
11747
|
+
1013904242,
|
|
11748
|
+
2773480762,
|
|
11749
|
+
1359893119,
|
|
11750
|
+
2600822924,
|
|
11751
|
+
528734635,
|
|
11752
|
+
1541459225
|
|
11753
|
+
];
|
|
11754
|
+
const w = /* @__PURE__ */ new Uint32Array(64);
|
|
11755
|
+
const rotr = (x, n) => x >>> n | x << 32 - n;
|
|
11756
|
+
for (let off = 0; off < total; off += 64) {
|
|
11757
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + 4 * i);
|
|
11758
|
+
for (let i = 16; i < 64; i++) {
|
|
11759
|
+
const a = w[i - 15];
|
|
11760
|
+
const b = w[i - 2];
|
|
11761
|
+
const s0 = rotr(a, 7) ^ rotr(a, 18) ^ a >>> 3;
|
|
11762
|
+
const s1 = rotr(b, 17) ^ rotr(b, 19) ^ b >>> 10;
|
|
11763
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
11764
|
+
}
|
|
11765
|
+
let [a, b, c, d, e, f, g, k] = h;
|
|
11766
|
+
for (let i = 0; i < 64; i++) {
|
|
11767
|
+
const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
11768
|
+
const ch = e & f ^ ~e & g;
|
|
11769
|
+
const t1 = k + s1 + ch + K[i] + w[i] >>> 0;
|
|
11770
|
+
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
11771
|
+
k = g;
|
|
11772
|
+
g = f;
|
|
11773
|
+
f = e;
|
|
11774
|
+
e = d + t1 >>> 0;
|
|
11775
|
+
d = c;
|
|
11776
|
+
c = b;
|
|
11777
|
+
b = a;
|
|
11778
|
+
a = t1 + t2 >>> 0;
|
|
11779
|
+
}
|
|
11780
|
+
h[0] = h[0] + a >>> 0;
|
|
11781
|
+
h[1] = h[1] + b >>> 0;
|
|
11782
|
+
h[2] = h[2] + c >>> 0;
|
|
11783
|
+
h[3] = h[3] + d >>> 0;
|
|
11784
|
+
h[4] = h[4] + e >>> 0;
|
|
11785
|
+
h[5] = h[5] + f >>> 0;
|
|
11786
|
+
h[6] = h[6] + g >>> 0;
|
|
11787
|
+
h[7] = h[7] + k >>> 0;
|
|
11788
|
+
}
|
|
11789
|
+
return h.map((x) => x.toString(16).padStart(8, "0")).join("");
|
|
10645
11790
|
}
|
|
10646
|
-
/** This function tells if a polyline holds the points of `part` in a row, in either direction. */
|
|
10647
|
-
const holds = (p, part) => holdsInOrder(p, part) || holdsInOrder(p, [...part].reverse());
|
|
10648
11791
|
/**
|
|
10649
|
-
* This function
|
|
10650
|
-
*
|
|
11792
|
+
* This function writes a value as canonical JSON: the keys of each object in sorted order, and no whitespace.
|
|
11793
|
+
* It drops a key whose value is `undefined`, as `JSON.stringify` does.
|
|
10651
11794
|
*/
|
|
10652
|
-
function
|
|
10653
|
-
if (
|
|
10654
|
-
|
|
11795
|
+
function canonicalJson(value) {
|
|
11796
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
11797
|
+
if (Array.isArray(value)) return `[${value.map((v) => v === void 0 ? "null" : canonicalJson(v)).join(",")}]`;
|
|
11798
|
+
const o = value;
|
|
11799
|
+
return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
|
|
11800
|
+
}
|
|
11801
|
+
/**
|
|
11802
|
+
* This function gives the hash of a JSON document: the first 16 hex characters of the SHA-256 of its canonical JSON.
|
|
11803
|
+
* The document does not include its own field `hash`. The function removes that field before it calculates the hash.
|
|
11804
|
+
*/
|
|
11805
|
+
function documentHash(value) {
|
|
11806
|
+
const { hash: _hash, ...rest } = value;
|
|
11807
|
+
return sha256Hex(canonicalJson(rest)).slice(0, 16);
|
|
11808
|
+
}
|
|
11809
|
+
/** This function gives the 32-bit FNV-1a hash of the UTF-8 bytes of a text, as an unsigned integer. */
|
|
11810
|
+
function fnv1a32(text) {
|
|
11811
|
+
let h = 2166136261;
|
|
11812
|
+
for (const byte of utf8(text)) {
|
|
11813
|
+
h ^= byte;
|
|
11814
|
+
h = Math.imul(h, 16777619);
|
|
11815
|
+
}
|
|
11816
|
+
return h >>> 0;
|
|
11817
|
+
}
|
|
11818
|
+
/** This function gives the seed of one step of a generator: `fnv1a32("<seed>:<step>")`. */
|
|
11819
|
+
function stepSeed(seed, step) {
|
|
11820
|
+
return fnv1a32(`${seed}:${step}`);
|
|
11821
|
+
}
|
|
11822
|
+
/** This function gives the generator `mulberry32` for a seed. Each call gives the next number from 0 to below 1. */
|
|
11823
|
+
function mulberry32(seed) {
|
|
11824
|
+
let a = seed >>> 0;
|
|
11825
|
+
return () => {
|
|
11826
|
+
a = a + 1831565813 >>> 0;
|
|
11827
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
11828
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
11829
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
11830
|
+
};
|
|
11831
|
+
}
|
|
11832
|
+
/** This function rounds a number to 3 decimals, as the files of a corpus keep it. The value `-0` becomes `0`. */
|
|
11833
|
+
function round3(value) {
|
|
11834
|
+
const r = Math.round(value * 1e3) / 1e3;
|
|
11835
|
+
return r === 0 ? 0 : r;
|
|
11836
|
+
}
|
|
11837
|
+
// src/order/index.ts
|
|
11838
|
+
/** This function compares two edges by the hash of their ids, and then by their ids. Thus the order of two edges does not depend on the input. */
|
|
11839
|
+
function byHash(a, b) {
|
|
11840
|
+
if (a.h !== b.h) return a.h - b.h;
|
|
11841
|
+
return a.e.id < b.e.id ? -1 : a.e.id > b.e.id ? 1 : 0;
|
|
11842
|
+
}
|
|
11843
|
+
/**
|
|
11844
|
+
* This function gives the graph with its edges in the stable order of WILD-053: by the FNV-1a hash of the id, then by the id.
|
|
11845
|
+
* The nodes and the edges do not change. Two orders of the same edges give the same graph.
|
|
11846
|
+
* The hash spreads the edges, so the stable order does not favor the edges of one node or one collection.
|
|
11847
|
+
*/
|
|
11848
|
+
function stableOrder(graph) {
|
|
11849
|
+
const edges = graph.edges.map((e) => ({
|
|
11850
|
+
h: fnv1a32(e.id),
|
|
11851
|
+
e
|
|
11852
|
+
})).sort(byHash).map((x) => x.e);
|
|
11853
|
+
return {
|
|
11854
|
+
...graph,
|
|
11855
|
+
edges
|
|
11856
|
+
};
|
|
11857
|
+
}
|
|
11858
|
+
/** This function gives the graph with its edges in a random order of the seed. The same graph and seed always give the same order. */
|
|
11859
|
+
function shuffleEdges(graph, seed) {
|
|
11860
|
+
const edges = [...graph.edges];
|
|
11861
|
+
const r = rng(seed);
|
|
11862
|
+
for (let i = edges.length - 1; i > 0; i--) {
|
|
11863
|
+
const j = Math.floor(r() * (i + 1));
|
|
11864
|
+
[edges[i], edges[j]] = [edges[j], edges[i]];
|
|
11865
|
+
}
|
|
11866
|
+
return {
|
|
11867
|
+
...graph,
|
|
11868
|
+
edges
|
|
11869
|
+
};
|
|
11870
|
+
}
|
|
11871
|
+
/**
|
|
11872
|
+
* This function routes the graph in the stable order of `stableOrder`, with the function `route`.
|
|
11873
|
+
* The routes come back in the order of the input edges, so the caller sees no change of order.
|
|
11874
|
+
* The bundles, the runs and the levels come from the stable order. Thus each order of the input gives the same data.
|
|
11875
|
+
*/
|
|
11876
|
+
function routeInStableOrder(graph, route) {
|
|
11877
|
+
const result = route(stableOrder(graph));
|
|
11878
|
+
const index = new Map(graph.edges.map((e, i) => [e.id, i]));
|
|
11879
|
+
const at = (edge) => index.get(edge) ?? Number.MAX_SAFE_INTEGER;
|
|
11880
|
+
const routes = [...result.routes].sort((a, b) => at(a.edge) - at(b.edge));
|
|
11881
|
+
return {
|
|
11882
|
+
...result,
|
|
11883
|
+
routes
|
|
11884
|
+
};
|
|
11885
|
+
}
|
|
11886
|
+
/** This function gives a router that routes each graph in the stable order of WILD-053. It keeps the name and the `maxNodes` of the router. */
|
|
11887
|
+
function withStableOrder(router) {
|
|
11888
|
+
return {
|
|
11889
|
+
...router,
|
|
11890
|
+
route: (graph) => routeInStableOrder(graph, (g) => router.route(g))
|
|
11891
|
+
};
|
|
11892
|
+
}
|
|
11893
|
+
/**
|
|
11894
|
+
* This function gives a router strategy that runs `strategy` in the stable order of WILD-053. The output does not depend on the order of the input edges.
|
|
11895
|
+
* The new strategy has the name `<name>+stable`, and the options and the schema of `strategy`. The strategy `strategy` does not change.
|
|
11896
|
+
*/
|
|
11897
|
+
function stableOrderStrategy(strategy) {
|
|
11898
|
+
return defineStrategy({
|
|
11899
|
+
step: "router",
|
|
11900
|
+
name: `${strategy.name}+stable`,
|
|
11901
|
+
describe: `${strategy.describe} The edges go in a stable order: by a hash of their ids.`,
|
|
11902
|
+
defaults: { ...strategy.defaults },
|
|
11903
|
+
...strategy.schema === void 0 ? {} : { schema: strategy.schema },
|
|
11904
|
+
...strategy.maxNodes === void 0 ? {} : { maxNodes: strategy.maxNodes },
|
|
11905
|
+
run: (graph, options) => routeInStableOrder(graph, (g) => strategy.run(g, options))
|
|
11906
|
+
});
|
|
11907
|
+
}
|
|
11908
|
+
// src/bench/measures/order.ts
|
|
11909
|
+
/**
|
|
11910
|
+
* This function gives `IQR / median` of the values. Values with a median of 0 give `IQR / mean`, and values that are all 0 give 0.
|
|
11911
|
+
* An IQR below `1e-9` of the median is rounding noise of the sums, so it gives 0.
|
|
11912
|
+
*/
|
|
11913
|
+
function spread(values) {
|
|
11914
|
+
const iqr = quantile(values, .75) - quantile(values, .25);
|
|
11915
|
+
const mid = Math.abs(quantile(values, .5));
|
|
11916
|
+
if (iqr <= 1e-9 * Math.max(1, mid)) return 0;
|
|
11917
|
+
if (mid > 0) return iqr / mid;
|
|
11918
|
+
const mean = values.reduce((s, v) => s + Math.abs(v), 0) / values.length;
|
|
11919
|
+
return mean > 0 ? iqr / mean : 0;
|
|
11920
|
+
}
|
|
11921
|
+
/** This function gives the routes by the id of the edge, in the order of the ids, so two orders of the input compare. */
|
|
11922
|
+
function byEdge(routes) {
|
|
11923
|
+
return [...routes].sort((a, b) => a.edge < b.edge ? -1 : a.edge > b.edge ? 1 : 0);
|
|
11924
|
+
}
|
|
11925
|
+
/**
|
|
11926
|
+
* This function measures the order fragility of a router on one graph (W3, from WILD-056).
|
|
11927
|
+
* It routes the graph with `orders` random orders of the edges.
|
|
11928
|
+
* It gives the spread `IQR / median` of the crossings, the detour, the ink saved and the ink.
|
|
11929
|
+
* It also gives the share of orders that change a route, and the price of anarchy of the crossings.
|
|
11930
|
+
* The cost is `orders` extra runs of the router.
|
|
11931
|
+
*/
|
|
11932
|
+
function orderFragility(graph, route, options = {}) {
|
|
11933
|
+
const orders = Math.max(1, Math.floor(options.orders ?? 20));
|
|
11934
|
+
const seed = options.seed ?? 1;
|
|
11935
|
+
const baseRoutes = byEdge((options.base ?? route(graph)).routes);
|
|
11936
|
+
const crossings = [];
|
|
11937
|
+
const detour = [];
|
|
11938
|
+
const saved = [];
|
|
11939
|
+
const ink = [];
|
|
11940
|
+
let changed = 0;
|
|
11941
|
+
for (let k = 0; k < orders; k++) {
|
|
11942
|
+
const routes = byEdge(route(shuffleEdges(graph, seed + k)).routes);
|
|
11943
|
+
const segs = drawnSegments(routes);
|
|
11944
|
+
crossings.push(crossingAngles(segs).crossings);
|
|
11945
|
+
detour.push(detourSpread(routes).median);
|
|
11946
|
+
saved.push(inkSaved(routes));
|
|
11947
|
+
let length = 0;
|
|
11948
|
+
for (const [a, b] of segs) length += Math.hypot(b.x - a.x, b.y - a.y);
|
|
11949
|
+
ink.push(length);
|
|
11950
|
+
if (!sameData(routes, baseRoutes)) changed++;
|
|
11951
|
+
}
|
|
11952
|
+
const own = crossingAngles(drawnSegments(baseRoutes)).crossings;
|
|
11953
|
+
const best = Math.min(...crossings);
|
|
11954
|
+
return {
|
|
11955
|
+
orders,
|
|
11956
|
+
crossings: spread(crossings),
|
|
11957
|
+
detour: spread(detour),
|
|
11958
|
+
inkSaved: spread(saved),
|
|
11959
|
+
ink: spread(ink),
|
|
11960
|
+
changed: changed / orders,
|
|
11961
|
+
poa: best > 0 ? own / best : own === 0 ? 1 : NaN
|
|
11962
|
+
};
|
|
11963
|
+
}
|
|
11964
|
+
// src/highways/follow.ts
|
|
11965
|
+
const SAME = 1e-6;
|
|
11966
|
+
const dedupe$1 = (p) => p.filter((q, i) => i === 0 || dist(q, p[i - 1]) > SAME);
|
|
11967
|
+
/** This function tells if a polyline holds the points of `part` in a row, in this order. */
|
|
11968
|
+
function holdsInOrder(p, part) {
|
|
11969
|
+
for (let i = 0; i + part.length <= p.length; i++) {
|
|
11970
|
+
let ok = true;
|
|
11971
|
+
for (let j = 0; ok && j < part.length; j++) ok = dist(p[i + j], part[j]) <= SAME;
|
|
11972
|
+
if (ok) return true;
|
|
11973
|
+
}
|
|
11974
|
+
return false;
|
|
11975
|
+
}
|
|
11976
|
+
/** This function tells if a polyline holds the points of `part` in a row, in either direction. */
|
|
11977
|
+
const holds$1 = (p, part) => holdsInOrder(p, part) || holdsInOrder(p, [...part].reverse());
|
|
11978
|
+
/**
|
|
11979
|
+
* This function tells if a point of a spine can be the plug of an end. The point is on the box of the node.
|
|
11980
|
+
* When the end has a fixed plug, the point is at that plug.
|
|
11981
|
+
*/
|
|
11982
|
+
function plugAt(node, end, p) {
|
|
11983
|
+
if (pointBoxDistance(p, node.box) > SAME) return false;
|
|
11984
|
+
const fixed = fixedPortPoint(node, end);
|
|
10655
11985
|
return fixed === void 0 || dist(fixed, p) <= SAME;
|
|
10656
11986
|
}
|
|
10657
11987
|
/** This function gives the points of the stops from the place `on` to the place `off`. When `on` is larger than `off`, the points go backward. */
|
|
@@ -10745,7 +12075,7 @@ function follow(graph, plan, options = {}) {
|
|
|
10745
12075
|
}
|
|
10746
12076
|
routes.push({
|
|
10747
12077
|
edge: e.id,
|
|
10748
|
-
points: dedupe(out)
|
|
12078
|
+
points: dedupe$1(out)
|
|
10749
12079
|
});
|
|
10750
12080
|
}
|
|
10751
12081
|
const routeOf = new Map(routes.map((r) => [r.edge, r]));
|
|
@@ -10758,10 +12088,10 @@ function follow(graph, plan, options = {}) {
|
|
|
10758
12088
|
const users = usersOf(k);
|
|
10759
12089
|
let end = k + 1;
|
|
10760
12090
|
while (end < stops.length - 1 && usersOf(end).join("\0") === users.join("\0")) end++;
|
|
10761
|
-
const points = dedupe(stops.slice(k, end + 1).map(([, p]) => p));
|
|
12091
|
+
const points = dedupe$1(stops.slice(k, end + 1).map(([, p]) => p));
|
|
10762
12092
|
k = end;
|
|
10763
12093
|
if (users.length < 2 || points.length < 2) continue;
|
|
10764
|
-
const mine = users.map((id) => routeOf.get(id)).filter((r) => holds(r.points, points));
|
|
12094
|
+
const mine = users.map((id) => routeOf.get(id)).filter((r) => holds$1(r.points, points));
|
|
10765
12095
|
if (mine.length < 2) continue;
|
|
10766
12096
|
runs.push({
|
|
10767
12097
|
id: `r${runs.length}`,
|
|
@@ -12013,230 +13343,47 @@ function compareStep(step, graph, options = {}) {
|
|
|
12013
13343
|
excluded: rows.filter((r) => !fair.includes(r)).map((r) => r.strategy)
|
|
12014
13344
|
};
|
|
12015
13345
|
}
|
|
12016
|
-
// src/bench/screen/
|
|
12017
|
-
const
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
604807628,
|
|
12038
|
-
770255983,
|
|
12039
|
-
1249150122,
|
|
12040
|
-
1555081692,
|
|
12041
|
-
1996064986,
|
|
12042
|
-
2554220882,
|
|
12043
|
-
2821834349,
|
|
12044
|
-
2952996808,
|
|
12045
|
-
3210313671,
|
|
12046
|
-
3336571891,
|
|
12047
|
-
3584528711,
|
|
12048
|
-
113926993,
|
|
12049
|
-
338241895,
|
|
12050
|
-
666307205,
|
|
12051
|
-
773529912,
|
|
12052
|
-
1294757372,
|
|
12053
|
-
1396182291,
|
|
12054
|
-
1695183700,
|
|
12055
|
-
1986661051,
|
|
12056
|
-
2177026350,
|
|
12057
|
-
2456956037,
|
|
12058
|
-
2730485921,
|
|
12059
|
-
2820302411,
|
|
12060
|
-
3259730800,
|
|
12061
|
-
3345764771,
|
|
12062
|
-
3516065817,
|
|
12063
|
-
3600352804,
|
|
12064
|
-
4094571909,
|
|
12065
|
-
275423344,
|
|
12066
|
-
430227734,
|
|
12067
|
-
506948616,
|
|
12068
|
-
659060556,
|
|
12069
|
-
883997877,
|
|
12070
|
-
958139571,
|
|
12071
|
-
1322822218,
|
|
12072
|
-
1537002063,
|
|
12073
|
-
1747873779,
|
|
12074
|
-
1955562222,
|
|
12075
|
-
2024104815,
|
|
12076
|
-
2227730452,
|
|
12077
|
-
2361852424,
|
|
12078
|
-
2428436474,
|
|
12079
|
-
2756734187,
|
|
12080
|
-
3204031479,
|
|
12081
|
-
3329325298
|
|
12082
|
-
];
|
|
12083
|
-
/** This function gives the bytes of a string in UTF-8. */
|
|
12084
|
-
function utf8(text) {
|
|
12085
|
-
const out = [];
|
|
12086
|
-
for (const ch of text) {
|
|
12087
|
-
const c = ch.codePointAt(0);
|
|
12088
|
-
if (c < 128) out.push(c);
|
|
12089
|
-
else if (c < 2048) out.push(192 | c >> 6, 128 | c & 63);
|
|
12090
|
-
else if (c < 65536) out.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
|
|
12091
|
-
else out.push(240 | c >> 18, 128 | c >> 12 & 63, 128 | c >> 6 & 63, 128 | c & 63);
|
|
12092
|
-
}
|
|
12093
|
-
return Uint8Array.from(out);
|
|
13346
|
+
// src/bench/screen/forms.ts
|
|
13347
|
+
const TO_FILE = {
|
|
13348
|
+
north: "top",
|
|
13349
|
+
east: "right",
|
|
13350
|
+
south: "bottom",
|
|
13351
|
+
west: "left"
|
|
13352
|
+
};
|
|
13353
|
+
const TO_MODEL = {
|
|
13354
|
+
top: "north",
|
|
13355
|
+
right: "east",
|
|
13356
|
+
bottom: "south",
|
|
13357
|
+
left: "west"
|
|
13358
|
+
};
|
|
13359
|
+
/** This function gives the side of a box that holds a point, relative to the top-left corner of the box. */
|
|
13360
|
+
function sideAt(p, w, h) {
|
|
13361
|
+
return [
|
|
13362
|
+
["top", Math.abs(p.y)],
|
|
13363
|
+
["right", Math.abs(w - p.x)],
|
|
13364
|
+
["bottom", Math.abs(h - p.y)],
|
|
13365
|
+
["left", Math.abs(p.x)]
|
|
13366
|
+
].reduce((a, b) => b[1] < a[1] ? b : a)[0];
|
|
12094
13367
|
}
|
|
12095
|
-
/** This function gives
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
const
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
1541459225
|
|
12115
|
-
];
|
|
12116
|
-
const w = /* @__PURE__ */ new Uint32Array(64);
|
|
12117
|
-
const rotr = (x, n) => x >>> n | x << 32 - n;
|
|
12118
|
-
for (let off = 0; off < total; off += 64) {
|
|
12119
|
-
for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + 4 * i);
|
|
12120
|
-
for (let i = 16; i < 64; i++) {
|
|
12121
|
-
const a = w[i - 15];
|
|
12122
|
-
const b = w[i - 2];
|
|
12123
|
-
const s0 = rotr(a, 7) ^ rotr(a, 18) ^ a >>> 3;
|
|
12124
|
-
const s1 = rotr(b, 17) ^ rotr(b, 19) ^ b >>> 10;
|
|
12125
|
-
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
12126
|
-
}
|
|
12127
|
-
let [a, b, c, d, e, f, g, k] = h;
|
|
12128
|
-
for (let i = 0; i < 64; i++) {
|
|
12129
|
-
const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
12130
|
-
const ch = e & f ^ ~e & g;
|
|
12131
|
-
const t1 = k + s1 + ch + K[i] + w[i] >>> 0;
|
|
12132
|
-
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
12133
|
-
k = g;
|
|
12134
|
-
g = f;
|
|
12135
|
-
f = e;
|
|
12136
|
-
e = d + t1 >>> 0;
|
|
12137
|
-
d = c;
|
|
12138
|
-
c = b;
|
|
12139
|
-
b = a;
|
|
12140
|
-
a = t1 + t2 >>> 0;
|
|
12141
|
-
}
|
|
12142
|
-
h[0] = h[0] + a >>> 0;
|
|
12143
|
-
h[1] = h[1] + b >>> 0;
|
|
12144
|
-
h[2] = h[2] + c >>> 0;
|
|
12145
|
-
h[3] = h[3] + d >>> 0;
|
|
12146
|
-
h[4] = h[4] + e >>> 0;
|
|
12147
|
-
h[5] = h[5] + f >>> 0;
|
|
12148
|
-
h[6] = h[6] + g >>> 0;
|
|
12149
|
-
h[7] = h[7] + k >>> 0;
|
|
12150
|
-
}
|
|
12151
|
-
return h.map((x) => x.toString(16).padStart(8, "0")).join("");
|
|
12152
|
-
}
|
|
12153
|
-
/**
|
|
12154
|
-
* This function writes a value as canonical JSON: the keys of each object in sorted order, and no whitespace.
|
|
12155
|
-
* It drops a key whose value is `undefined`, as `JSON.stringify` does.
|
|
12156
|
-
*/
|
|
12157
|
-
function canonicalJson(value) {
|
|
12158
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
12159
|
-
if (Array.isArray(value)) return `[${value.map((v) => v === void 0 ? "null" : canonicalJson(v)).join(",")}]`;
|
|
12160
|
-
const o = value;
|
|
12161
|
-
return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
|
|
12162
|
-
}
|
|
12163
|
-
/**
|
|
12164
|
-
* This function gives the hash of a JSON document: the first 16 hex characters of the SHA-256 of its canonical JSON.
|
|
12165
|
-
* The document does not include its own field `hash`. The function removes that field before it calculates the hash.
|
|
12166
|
-
*/
|
|
12167
|
-
function documentHash(value) {
|
|
12168
|
-
const { hash: _hash, ...rest } = value;
|
|
12169
|
-
return sha256Hex(canonicalJson(rest)).slice(0, 16);
|
|
12170
|
-
}
|
|
12171
|
-
/** This function gives the 32-bit FNV-1a hash of the UTF-8 bytes of a text, as an unsigned integer. */
|
|
12172
|
-
function fnv1a32(text) {
|
|
12173
|
-
let h = 2166136261;
|
|
12174
|
-
for (const byte of utf8(text)) {
|
|
12175
|
-
h ^= byte;
|
|
12176
|
-
h = Math.imul(h, 16777619);
|
|
12177
|
-
}
|
|
12178
|
-
return h >>> 0;
|
|
12179
|
-
}
|
|
12180
|
-
/** This function gives the seed of one step of a generator: `fnv1a32("<seed>:<step>")`. */
|
|
12181
|
-
function stepSeed(seed, step) {
|
|
12182
|
-
return fnv1a32(`${seed}:${step}`);
|
|
12183
|
-
}
|
|
12184
|
-
/** This function gives the generator `mulberry32` for a seed. Each call gives the next number from 0 to below 1. */
|
|
12185
|
-
function mulberry32(seed) {
|
|
12186
|
-
let a = seed >>> 0;
|
|
12187
|
-
return () => {
|
|
12188
|
-
a = a + 1831565813 >>> 0;
|
|
12189
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
12190
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
12191
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
12192
|
-
};
|
|
12193
|
-
}
|
|
12194
|
-
/** This function rounds a number to 3 decimals, as the files of a corpus keep it. The value `-0` becomes `0`. */
|
|
12195
|
-
function round3(value) {
|
|
12196
|
-
const r = Math.round(value * 1e3) / 1e3;
|
|
12197
|
-
return r === 0 ? 0 : r;
|
|
12198
|
-
}
|
|
12199
|
-
// src/bench/screen/forms.ts
|
|
12200
|
-
const TO_FILE = {
|
|
12201
|
-
north: "top",
|
|
12202
|
-
east: "right",
|
|
12203
|
-
south: "bottom",
|
|
12204
|
-
west: "left"
|
|
12205
|
-
};
|
|
12206
|
-
const TO_MODEL = {
|
|
12207
|
-
top: "north",
|
|
12208
|
-
right: "east",
|
|
12209
|
-
bottom: "south",
|
|
12210
|
-
left: "west"
|
|
12211
|
-
};
|
|
12212
|
-
/** This function gives the side of a box that holds a point, relative to the top-left corner of the box. */
|
|
12213
|
-
function sideAt(p, w, h) {
|
|
12214
|
-
return [
|
|
12215
|
-
["top", Math.abs(p.y)],
|
|
12216
|
-
["right", Math.abs(w - p.x)],
|
|
12217
|
-
["bottom", Math.abs(h - p.y)],
|
|
12218
|
-
["left", Math.abs(p.x)]
|
|
12219
|
-
].reduce((a, b) => b[1] < a[1] ? b : a)[0];
|
|
12220
|
-
}
|
|
12221
|
-
/** This function gives 0 for -0, because JSON writes -0 as 0. Then a file and the graph that it comes from hold the same numbers. */
|
|
12222
|
-
const num = round3;
|
|
12223
|
-
function portToFile(p, n) {
|
|
12224
|
-
const side = p.side ? TO_FILE[p.side] : p.at ? sideAt(p.at, n.box.w, n.box.h) : void 0;
|
|
12225
|
-
return {
|
|
12226
|
-
id: p.id,
|
|
12227
|
-
...side ? { side } : {},
|
|
12228
|
-
...p.at ? {
|
|
12229
|
-
x: num(p.at.x),
|
|
12230
|
-
y: num(p.at.y)
|
|
12231
|
-
} : {}
|
|
12232
|
-
};
|
|
12233
|
-
}
|
|
12234
|
-
function portToModel(p) {
|
|
12235
|
-
if (p.x !== void 0 && p.y !== void 0) return {
|
|
12236
|
-
id: p.id,
|
|
12237
|
-
at: {
|
|
12238
|
-
x: p.x,
|
|
12239
|
-
y: p.y
|
|
13368
|
+
/** This function gives 0 for -0, because JSON writes -0 as 0. Then a file and the graph that it comes from hold the same numbers. */
|
|
13369
|
+
const num = round3;
|
|
13370
|
+
function portToFile(p, n) {
|
|
13371
|
+
const side = p.side ? TO_FILE[p.side] : p.at ? sideAt(p.at, n.box.w, n.box.h) : void 0;
|
|
13372
|
+
return {
|
|
13373
|
+
id: p.id,
|
|
13374
|
+
...side ? { side } : {},
|
|
13375
|
+
...p.at ? {
|
|
13376
|
+
x: num(p.at.x),
|
|
13377
|
+
y: num(p.at.y)
|
|
13378
|
+
} : {}
|
|
13379
|
+
};
|
|
13380
|
+
}
|
|
13381
|
+
function portToModel(p) {
|
|
13382
|
+
if (p.x !== void 0 && p.y !== void 0) return {
|
|
13383
|
+
id: p.id,
|
|
13384
|
+
at: {
|
|
13385
|
+
x: p.x,
|
|
13386
|
+
y: p.y
|
|
12240
13387
|
}
|
|
12241
13388
|
};
|
|
12242
13389
|
return {
|
|
@@ -12592,6 +13739,86 @@ const MEASURES = Object.freeze([
|
|
|
12592
13739
|
"lower",
|
|
12593
13740
|
false
|
|
12594
13741
|
],
|
|
13742
|
+
[
|
|
13743
|
+
"B2.share",
|
|
13744
|
+
"B2",
|
|
13745
|
+
"route.bundle.share",
|
|
13746
|
+
"0 to 1",
|
|
13747
|
+
"higher",
|
|
13748
|
+
false
|
|
13749
|
+
],
|
|
13750
|
+
[
|
|
13751
|
+
"B3.median",
|
|
13752
|
+
"B3",
|
|
13753
|
+
"route.bundle.stretch.median",
|
|
13754
|
+
"ratio",
|
|
13755
|
+
"lower",
|
|
13756
|
+
false
|
|
13757
|
+
],
|
|
13758
|
+
[
|
|
13759
|
+
"B3.q95",
|
|
13760
|
+
"B3",
|
|
13761
|
+
"route.bundle.stretch.q95",
|
|
13762
|
+
"ratio",
|
|
13763
|
+
"lower",
|
|
13764
|
+
false
|
|
13765
|
+
],
|
|
13766
|
+
[
|
|
13767
|
+
"B6.max",
|
|
13768
|
+
"B6",
|
|
13769
|
+
"route.bundle.merge-angle.max",
|
|
13770
|
+
"degrees",
|
|
13771
|
+
"lower",
|
|
13772
|
+
false
|
|
13773
|
+
],
|
|
13774
|
+
[
|
|
13775
|
+
"W3.crossings",
|
|
13776
|
+
"W3",
|
|
13777
|
+
"route.order.crossings",
|
|
13778
|
+
"IQR over the median",
|
|
13779
|
+
"lower",
|
|
13780
|
+
true
|
|
13781
|
+
],
|
|
13782
|
+
[
|
|
13783
|
+
"W3.detour",
|
|
13784
|
+
"W3",
|
|
13785
|
+
"route.order.detour",
|
|
13786
|
+
"IQR over the median",
|
|
13787
|
+
"lower",
|
|
13788
|
+
true
|
|
13789
|
+
],
|
|
13790
|
+
[
|
|
13791
|
+
"W3.inkSaved",
|
|
13792
|
+
"W3",
|
|
13793
|
+
"route.order.ink-saved",
|
|
13794
|
+
"IQR over the median",
|
|
13795
|
+
"lower",
|
|
13796
|
+
true
|
|
13797
|
+
],
|
|
13798
|
+
[
|
|
13799
|
+
"W3.ink",
|
|
13800
|
+
"W3",
|
|
13801
|
+
"route.order.ink",
|
|
13802
|
+
"IQR over the median",
|
|
13803
|
+
"lower",
|
|
13804
|
+
true
|
|
13805
|
+
],
|
|
13806
|
+
[
|
|
13807
|
+
"W3.changed",
|
|
13808
|
+
"W3",
|
|
13809
|
+
"route.order.changed",
|
|
13810
|
+
"0 to 1",
|
|
13811
|
+
"lower",
|
|
13812
|
+
true
|
|
13813
|
+
],
|
|
13814
|
+
[
|
|
13815
|
+
"W3.poa",
|
|
13816
|
+
"W3",
|
|
13817
|
+
"route.order.poa",
|
|
13818
|
+
"ratio",
|
|
13819
|
+
"lower",
|
|
13820
|
+
true
|
|
13821
|
+
],
|
|
12595
13822
|
[
|
|
12596
13823
|
"Z10.mean",
|
|
12597
13824
|
"Z10",
|
|
@@ -13035,6 +14262,639 @@ function corpusManifest(corpus, inputs) {
|
|
|
13035
14262
|
}))
|
|
13036
14263
|
};
|
|
13037
14264
|
}
|
|
14265
|
+
// src/repair/walk.ts
|
|
14266
|
+
/** The sides of a box, as the indexes of two corners: north, east, south and west. */
|
|
14267
|
+
const SIDES = [
|
|
14268
|
+
[0, 1],
|
|
14269
|
+
[1, 2],
|
|
14270
|
+
[3, 2],
|
|
14271
|
+
[0, 3]
|
|
14272
|
+
];
|
|
14273
|
+
const SIDE_INDEX = {
|
|
14274
|
+
north: 0,
|
|
14275
|
+
east: 1,
|
|
14276
|
+
south: 2,
|
|
14277
|
+
west: 3
|
|
14278
|
+
};
|
|
14279
|
+
/** This function gives the four corners of a box: top left, top right, bottom right and bottom left. */
|
|
14280
|
+
function corners(b) {
|
|
14281
|
+
return [
|
|
14282
|
+
{
|
|
14283
|
+
x: b.x,
|
|
14284
|
+
y: b.y
|
|
14285
|
+
},
|
|
14286
|
+
{
|
|
14287
|
+
x: b.x + b.w,
|
|
14288
|
+
y: b.y
|
|
14289
|
+
},
|
|
14290
|
+
{
|
|
14291
|
+
x: b.x + b.w,
|
|
14292
|
+
y: b.y + b.h
|
|
14293
|
+
},
|
|
14294
|
+
{
|
|
14295
|
+
x: b.x,
|
|
14296
|
+
y: b.y + b.h
|
|
14297
|
+
}
|
|
14298
|
+
];
|
|
14299
|
+
}
|
|
14300
|
+
const EPS = 1e-6;
|
|
14301
|
+
const keyOf = (p) => `${Math.round(p.x * 1e3)},${Math.round(p.y * 1e3)}`;
|
|
14302
|
+
/** This function tells if the point `c` is on the axis-parallel segment `pq`. */
|
|
14303
|
+
function onSide(c, p, q) {
|
|
14304
|
+
if (Math.abs(p.y - q.y) <= EPS) return Math.abs(c.y - p.y) <= EPS && c.x >= Math.min(p.x, q.x) - EPS && c.x <= Math.max(p.x, q.x) + EPS;
|
|
14305
|
+
return Math.abs(c.x - p.x) <= EPS && c.y >= Math.min(p.y, q.y) - EPS && c.y <= Math.max(p.y, q.y) + EPS;
|
|
14306
|
+
}
|
|
14307
|
+
/** This function drops each point that is on the straight line between the point before it and the point after it. */
|
|
14308
|
+
function straighten(p) {
|
|
14309
|
+
const out = [];
|
|
14310
|
+
for (const v of p) {
|
|
14311
|
+
if (out.length > 0 && dist(out[out.length - 1], v) <= EPS) continue;
|
|
14312
|
+
while (out.length >= 2) {
|
|
14313
|
+
const a = out[out.length - 2];
|
|
14314
|
+
const b = out[out.length - 1];
|
|
14315
|
+
const cross = (b.x - a.x) * (v.y - b.y) - (b.y - a.y) * (v.x - b.x);
|
|
14316
|
+
const dot = (b.x - a.x) * (v.x - b.x) + (b.y - a.y) * (v.y - b.y);
|
|
14317
|
+
if (Math.abs(cross) > EPS || dot < 0) break;
|
|
14318
|
+
out.pop();
|
|
14319
|
+
}
|
|
14320
|
+
out.push(v);
|
|
14321
|
+
}
|
|
14322
|
+
return out;
|
|
14323
|
+
}
|
|
14324
|
+
/**
|
|
14325
|
+
* The boundary walk of the repair step (RULE-046): a graph of the sides of the boxes.
|
|
14326
|
+
* The vertices are the corners of the boxes. The edges are the parts of each side between two corners on it.
|
|
14327
|
+
* A walk on this graph goes along the boundaries of the boxes, and not through a box. Thus it joins two boxes also when the boxes touch.
|
|
14328
|
+
* The graph drops each part of a side that goes through another box, so overlapping boxes do not open a way.
|
|
14329
|
+
*/
|
|
14330
|
+
var BoundaryWalk = class {
|
|
14331
|
+
#points = [];
|
|
14332
|
+
#index = /* @__PURE__ */ new Map();
|
|
14333
|
+
#next = [];
|
|
14334
|
+
/** The vertices on each side of each node, in the order along the side. */
|
|
14335
|
+
#sides = /* @__PURE__ */ new Map();
|
|
14336
|
+
/** This constructor makes the graph of the sides of the nodes. `grid` holds the nodes by their boxes. */
|
|
14337
|
+
constructor(nodes, grid) {
|
|
14338
|
+
for (const n of nodes) for (const c of corners(n.box)) this.#vertex(c);
|
|
14339
|
+
for (const n of nodes) {
|
|
14340
|
+
const cs = corners(n.box);
|
|
14341
|
+
const lists = [];
|
|
14342
|
+
for (const [i, j] of SIDES) {
|
|
14343
|
+
const p = cs[i];
|
|
14344
|
+
const q = cs[j];
|
|
14345
|
+
const on = /* @__PURE__ */ new Set([this.#vertex(p), this.#vertex(q)]);
|
|
14346
|
+
const area = {
|
|
14347
|
+
x: Math.min(p.x, q.x) - 1,
|
|
14348
|
+
y: Math.min(p.y, q.y) - 1,
|
|
14349
|
+
w: Math.abs(q.x - p.x) + 2,
|
|
14350
|
+
h: Math.abs(q.y - p.y) + 2
|
|
14351
|
+
};
|
|
14352
|
+
grid.query(area, (m) => {
|
|
14353
|
+
for (const c of corners(m.box)) if (onSide(c, p, q)) on.add(this.#vertex(c));
|
|
14354
|
+
});
|
|
14355
|
+
const list = [...on].sort((u, v) => dist(p, this.#points[u]) - dist(p, this.#points[v]) || u - v);
|
|
14356
|
+
lists.push(list);
|
|
14357
|
+
for (let k = 1; k < list.length; k++) {
|
|
14358
|
+
const u = list[k - 1];
|
|
14359
|
+
const v = list[k];
|
|
14360
|
+
const a = this.#points[u];
|
|
14361
|
+
const b = this.#points[v];
|
|
14362
|
+
if (dist(a, b) <= EPS || this.#blocked(grid, a, b)) continue;
|
|
14363
|
+
this.#link(u, v);
|
|
14364
|
+
}
|
|
14365
|
+
}
|
|
14366
|
+
this.#sides.set(n, lists);
|
|
14367
|
+
}
|
|
14368
|
+
}
|
|
14369
|
+
/** This method gives the index of the vertex at the point, and adds the vertex if it is new. */
|
|
14370
|
+
#vertex(p) {
|
|
14371
|
+
const k = keyOf(p);
|
|
14372
|
+
let i = this.#index.get(k);
|
|
14373
|
+
if (i === void 0) {
|
|
14374
|
+
i = this.#points.length;
|
|
14375
|
+
this.#points.push(p);
|
|
14376
|
+
this.#next.push([]);
|
|
14377
|
+
this.#index.set(k, i);
|
|
14378
|
+
}
|
|
14379
|
+
return i;
|
|
14380
|
+
}
|
|
14381
|
+
#link(u, v) {
|
|
14382
|
+
const d = dist(this.#points[u], this.#points[v]);
|
|
14383
|
+
if (this.#next[u].some((e) => e.to === v)) return;
|
|
14384
|
+
this.#next[u].push({
|
|
14385
|
+
to: v,
|
|
14386
|
+
d
|
|
14387
|
+
});
|
|
14388
|
+
this.#next[v].push({
|
|
14389
|
+
to: u,
|
|
14390
|
+
d
|
|
14391
|
+
});
|
|
14392
|
+
}
|
|
14393
|
+
/** This method tells if the segment goes through a box, as the rule `no-pass-through` tests it. */
|
|
14394
|
+
#blocked(grid, a, b) {
|
|
14395
|
+
return grid.someSegment(a, b, 0, (n) => segmentHitsBox(a, b, inflate(n.box, -.5)));
|
|
14396
|
+
}
|
|
14397
|
+
/** This method finds the side of `node` that is nearest to `p`. It gives the two vertices of that side that are next to `p`. */
|
|
14398
|
+
#around(node, p) {
|
|
14399
|
+
const list = this.#sides.get(node)?.[SIDE_INDEX[nearestSide(node.box, p)]];
|
|
14400
|
+
if (!list || list.length === 0) return [];
|
|
14401
|
+
const from = this.#points[list[0]];
|
|
14402
|
+
const at = dist(from, p);
|
|
14403
|
+
let k = 0;
|
|
14404
|
+
while (k < list.length && dist(from, this.#points[list[k]]) < at) k++;
|
|
14405
|
+
return [list[k - 1], list[k]].filter((v) => v !== void 0);
|
|
14406
|
+
}
|
|
14407
|
+
/**
|
|
14408
|
+
* This method gives a way along the boundaries of the boxes. The way goes from the point `a` on the box of `s` to the point `b` on the box of `t`.
|
|
14409
|
+
* A point that is not on its box goes first to the nearest point of the boundary.
|
|
14410
|
+
* The method gives `undefined` when no way joins the two boxes.
|
|
14411
|
+
*/
|
|
14412
|
+
path(a, s, b, t) {
|
|
14413
|
+
const a1 = nearestOnBoundary(s.box, a);
|
|
14414
|
+
const b1 = nearestOnBoundary(t.box, b);
|
|
14415
|
+
const n = this.#points.length;
|
|
14416
|
+
const src = n;
|
|
14417
|
+
const dst = n + 1;
|
|
14418
|
+
const extra = /* @__PURE__ */ new Map();
|
|
14419
|
+
const add = (u, v, d) => {
|
|
14420
|
+
if (!extra.has(u)) extra.set(u, []);
|
|
14421
|
+
extra.get(u).push({
|
|
14422
|
+
to: v,
|
|
14423
|
+
d
|
|
14424
|
+
});
|
|
14425
|
+
};
|
|
14426
|
+
for (const v of this.#around(s, a1)) add(src, v, dist(a1, this.#points[v]));
|
|
14427
|
+
for (const v of this.#around(t, b1)) add(v, dst, dist(b1, this.#points[v]));
|
|
14428
|
+
if (!extra.has(src)) return void 0;
|
|
14429
|
+
const pointOf = (i) => i === src ? a1 : i === dst ? b1 : this.#points[i];
|
|
14430
|
+
const best = new Float64Array(n + 2).fill(Infinity);
|
|
14431
|
+
const prev = new Int32Array(n + 2).fill(-1);
|
|
14432
|
+
best[src] = 0;
|
|
14433
|
+
const heap = [[0, src]];
|
|
14434
|
+
const less = (x, y) => x[0] < y[0] || x[0] === y[0] && x[1] < y[1];
|
|
14435
|
+
const pop = () => {
|
|
14436
|
+
const top = heap[0];
|
|
14437
|
+
const last = heap.pop();
|
|
14438
|
+
if (heap.length > 0) {
|
|
14439
|
+
heap[0] = last;
|
|
14440
|
+
let i = 0;
|
|
14441
|
+
for (;;) {
|
|
14442
|
+
const l = 2 * i + 1;
|
|
14443
|
+
const r = l + 1;
|
|
14444
|
+
let m = i;
|
|
14445
|
+
if (l < heap.length && less(heap[l], heap[m])) m = l;
|
|
14446
|
+
if (r < heap.length && less(heap[r], heap[m])) m = r;
|
|
14447
|
+
if (m === i) break;
|
|
14448
|
+
[heap[i], heap[m]] = [heap[m], heap[i]];
|
|
14449
|
+
i = m;
|
|
14450
|
+
}
|
|
14451
|
+
}
|
|
14452
|
+
return top;
|
|
14453
|
+
};
|
|
14454
|
+
const push = (x) => {
|
|
14455
|
+
heap.push(x);
|
|
14456
|
+
let i = heap.length - 1;
|
|
14457
|
+
while (i > 0) {
|
|
14458
|
+
const p = i - 1 >> 1;
|
|
14459
|
+
if (!less(heap[i], heap[p])) break;
|
|
14460
|
+
[heap[i], heap[p]] = [heap[p], heap[i]];
|
|
14461
|
+
i = p;
|
|
14462
|
+
}
|
|
14463
|
+
};
|
|
14464
|
+
while (heap.length > 0) {
|
|
14465
|
+
const [d, u] = pop();
|
|
14466
|
+
if (d > best[u]) continue;
|
|
14467
|
+
if (u === dst) break;
|
|
14468
|
+
const edges = [...u < n ? this.#next[u] : [], ...extra.get(u) ?? []];
|
|
14469
|
+
for (const e of edges) {
|
|
14470
|
+
const nd = d + e.d;
|
|
14471
|
+
if (nd < best[e.to]) {
|
|
14472
|
+
best[e.to] = nd;
|
|
14473
|
+
prev[e.to] = u;
|
|
14474
|
+
push([nd, e.to]);
|
|
14475
|
+
}
|
|
14476
|
+
}
|
|
14477
|
+
}
|
|
14478
|
+
if (prev[dst] === -1) return void 0;
|
|
14479
|
+
const way = [];
|
|
14480
|
+
for (let i = dst; i !== -1; i = prev[i]) way.push(pointOf(i));
|
|
14481
|
+
way.reverse();
|
|
14482
|
+
return straighten([
|
|
14483
|
+
a,
|
|
14484
|
+
...way,
|
|
14485
|
+
b
|
|
14486
|
+
]);
|
|
14487
|
+
}
|
|
14488
|
+
};
|
|
14489
|
+
/** This function gives the point of the boundary of the box that is nearest to `p`. A point on the boundary gives itself. */
|
|
14490
|
+
function nearestOnBoundary(box, p) {
|
|
14491
|
+
const x0 = box.x;
|
|
14492
|
+
const y0 = box.y;
|
|
14493
|
+
const x1 = box.x + box.w;
|
|
14494
|
+
const y1 = box.y + box.h;
|
|
14495
|
+
if (!(p.x > x0 && p.x < x1 && p.y > y0 && p.y < y1)) return {
|
|
14496
|
+
x: Math.min(x1, Math.max(x0, p.x)),
|
|
14497
|
+
y: Math.min(y1, Math.max(y0, p.y))
|
|
14498
|
+
};
|
|
14499
|
+
const side = nearestSide(box, p);
|
|
14500
|
+
if (side === "north") return {
|
|
14501
|
+
x: p.x,
|
|
14502
|
+
y: y0
|
|
14503
|
+
};
|
|
14504
|
+
if (side === "south") return {
|
|
14505
|
+
x: p.x,
|
|
14506
|
+
y: y1
|
|
14507
|
+
};
|
|
14508
|
+
if (side === "west") return {
|
|
14509
|
+
x: x0,
|
|
14510
|
+
y: p.y
|
|
14511
|
+
};
|
|
14512
|
+
return {
|
|
14513
|
+
x: x1,
|
|
14514
|
+
y: p.y
|
|
14515
|
+
};
|
|
14516
|
+
}
|
|
14517
|
+
// src/repair/index.ts
|
|
14518
|
+
/** The repair step makes at most this number of fixes for one route. A route that needs more keeps its base route. */
|
|
14519
|
+
const MAX_FIXES = 64;
|
|
14520
|
+
/** This function tells if the point is on the boundary of the box, within `TOLERANCE`, as the rule `link-ends` tests it. */
|
|
14521
|
+
function onBoundary(p, b) {
|
|
14522
|
+
const inner = inflate(b, -.5);
|
|
14523
|
+
return !(p.x > inner.x && p.x < inner.x + inner.w && p.y > inner.y && p.y < inner.y + inner.h) && pointBoxDistance(p, b) <= .5;
|
|
14524
|
+
}
|
|
14525
|
+
/** This function tells if the polyline `p` holds the points of `part` in a row, within `TOLERANCE`. */
|
|
14526
|
+
function holds(p, part) {
|
|
14527
|
+
if (part.length === 0) return true;
|
|
14528
|
+
for (let i = 0; i + part.length <= p.length; i++) if (part.every((q, j) => dist(q, p[i + j]) <= .5)) return true;
|
|
14529
|
+
return false;
|
|
14530
|
+
}
|
|
14531
|
+
/** This function drops each point that is at the same place as the point before it. */
|
|
14532
|
+
function dedupe(p) {
|
|
14533
|
+
const out = [];
|
|
14534
|
+
for (const v of p) if (out.length === 0 || dist(out[out.length - 1], v) > 1e-9) out.push(v);
|
|
14535
|
+
return out;
|
|
14536
|
+
}
|
|
14537
|
+
/**
|
|
14538
|
+
* This function repairs the breaks of the hard rules in the routes of any router (RULE-046). The input result does not change.
|
|
14539
|
+
*
|
|
14540
|
+
* - It adds a route through the corner graph for each edge without a route (`complete`).
|
|
14541
|
+
* - It moves each end back to its fixed plug, or to the nearest point of the box of its node (`ends-at-plugs` and `fixed-plugs`).
|
|
14542
|
+
* - It pushes each inner leg out of a box that it goes through, to the near side of the box (`no-pass-through`).
|
|
14543
|
+
* - It replaces each other leg through a box with a detour through the corner graph.
|
|
14544
|
+
* - When a leg does not repair, it routes the whole route again through the corner graph.
|
|
14545
|
+
* Else it walks along the boundaries of the boxes (`BoundaryWalk`).
|
|
14546
|
+
* - A route that still breaks a rule keeps its base route.
|
|
14547
|
+
* - It takes an edge out of a run or a bundle when its new route does not hold the run or the trunk (`run-shared` and `bundle-trunk`).
|
|
14548
|
+
* - It moves the ends of each level link to the boundaries of its items (`link-ends`).
|
|
14549
|
+
*
|
|
14550
|
+
* A route without a break does not change. The same input gives the same output.
|
|
14551
|
+
*/
|
|
14552
|
+
function repairResult(graph, result, options = {}) {
|
|
14553
|
+
const margin = options.margin ?? 5;
|
|
14554
|
+
const doPush = options.push ?? true;
|
|
14555
|
+
const doDetour = options.detour ?? true;
|
|
14556
|
+
const doReroute = options.reroute ?? true;
|
|
14557
|
+
const doWalk = options.walk ?? true;
|
|
14558
|
+
const byId = nodeIndex(graph);
|
|
14559
|
+
const grid = new Grid(180);
|
|
14560
|
+
for (const n of graph.nodes) grid.add(n, n.box);
|
|
14561
|
+
let ways;
|
|
14562
|
+
let walker;
|
|
14563
|
+
const walkerOf = () => walker ??= new BoundaryWalk(graph.nodes, grid);
|
|
14564
|
+
const waysOf = () => ways ??= new WayFinder(graph.nodes, {
|
|
14565
|
+
margin,
|
|
14566
|
+
...options.k === void 0 ? {} : { k: options.k }
|
|
14567
|
+
});
|
|
14568
|
+
const counts = {
|
|
14569
|
+
routes: 0,
|
|
14570
|
+
faulty: 0,
|
|
14571
|
+
snapped: 0,
|
|
14572
|
+
pushed: 0,
|
|
14573
|
+
detoured: 0,
|
|
14574
|
+
rerouted: 0,
|
|
14575
|
+
walked: 0,
|
|
14576
|
+
added: 0,
|
|
14577
|
+
kept: 0,
|
|
14578
|
+
unbundled: 0,
|
|
14579
|
+
linkEnds: 0
|
|
14580
|
+
};
|
|
14581
|
+
const hitOf = (p, q, s, t) => {
|
|
14582
|
+
let hit;
|
|
14583
|
+
grid.querySegment(p, q, 0, (n) => {
|
|
14584
|
+
if (!hit && n !== s && n !== t && segmentHitsBox(p, q, inflate(n.box, -.5))) hit = n;
|
|
14585
|
+
});
|
|
14586
|
+
return hit;
|
|
14587
|
+
};
|
|
14588
|
+
const clear = (p, s, t) => {
|
|
14589
|
+
for (let i = 1; i < p.length; i++) if (hitOf(p[i - 1], p[i], s, t)) return false;
|
|
14590
|
+
return true;
|
|
14591
|
+
};
|
|
14592
|
+
const leave = (p) => {
|
|
14593
|
+
let on;
|
|
14594
|
+
grid.query({
|
|
14595
|
+
x: p.x - TOLERANCE,
|
|
14596
|
+
y: p.y - TOLERANCE,
|
|
14597
|
+
w: 2 * TOLERANCE,
|
|
14598
|
+
h: 2 * TOLERANCE
|
|
14599
|
+
}, (n) => {
|
|
14600
|
+
if (!on && pointBoxDistance(p, n.box) <= .5) on = n;
|
|
14601
|
+
});
|
|
14602
|
+
if (!on) return p;
|
|
14603
|
+
const at = nearestOnBoundary(on.box, p);
|
|
14604
|
+
const nrm = SIDE_NORMAL[nearestSide(on.box, at)];
|
|
14605
|
+
return {
|
|
14606
|
+
x: at.x + nrm.x * margin,
|
|
14607
|
+
y: at.y + nrm.y * margin
|
|
14608
|
+
};
|
|
14609
|
+
};
|
|
14610
|
+
const push = (p, i, box, s, t) => {
|
|
14611
|
+
const a = p[i - 1];
|
|
14612
|
+
const b = p[i];
|
|
14613
|
+
const len = dist(a, b);
|
|
14614
|
+
if (len <= 0) return void 0;
|
|
14615
|
+
const nx = -(b.y - a.y) / len;
|
|
14616
|
+
const ny = (b.x - a.x) / len;
|
|
14617
|
+
const big = inflate(box, margin / 2);
|
|
14618
|
+
const proj = [
|
|
14619
|
+
{
|
|
14620
|
+
x: big.x,
|
|
14621
|
+
y: big.y
|
|
14622
|
+
},
|
|
14623
|
+
{
|
|
14624
|
+
x: big.x + big.w,
|
|
14625
|
+
y: big.y
|
|
14626
|
+
},
|
|
14627
|
+
{
|
|
14628
|
+
x: big.x,
|
|
14629
|
+
y: big.y + big.h
|
|
14630
|
+
},
|
|
14631
|
+
{
|
|
14632
|
+
x: big.x + big.w,
|
|
14633
|
+
y: big.y + big.h
|
|
14634
|
+
}
|
|
14635
|
+
].map((c) => (c.x - a.x) * nx + (c.y - a.y) * ny);
|
|
14636
|
+
const shifts = [Math.max(...proj), Math.min(...proj)].sort((u, v) => Math.abs(u) - Math.abs(v));
|
|
14637
|
+
for (const d of shifts) {
|
|
14638
|
+
const moved = [...p];
|
|
14639
|
+
moved[i - 1] = {
|
|
14640
|
+
x: a.x + nx * d,
|
|
14641
|
+
y: a.y + ny * d
|
|
14642
|
+
};
|
|
14643
|
+
moved[i] = {
|
|
14644
|
+
x: b.x + nx * d,
|
|
14645
|
+
y: b.y + ny * d
|
|
14646
|
+
};
|
|
14647
|
+
if (clear([
|
|
14648
|
+
p[i - 2],
|
|
14649
|
+
moved[i - 1],
|
|
14650
|
+
moved[i],
|
|
14651
|
+
p[i + 1]
|
|
14652
|
+
], s, t)) return moved;
|
|
14653
|
+
}
|
|
14654
|
+
};
|
|
14655
|
+
const detour = (p, i, s, t) => {
|
|
14656
|
+
const a = p[i - 1];
|
|
14657
|
+
const b = p[i];
|
|
14658
|
+
const finder = waysOf();
|
|
14659
|
+
const way = finder.corners.path(leave(a), leave(b));
|
|
14660
|
+
if (!way) return void 0;
|
|
14661
|
+
const mid = dedupe([
|
|
14662
|
+
a,
|
|
14663
|
+
...finder.corners.pull(way),
|
|
14664
|
+
b
|
|
14665
|
+
]);
|
|
14666
|
+
if (!clear(mid, s, t)) return void 0;
|
|
14667
|
+
return [
|
|
14668
|
+
...p.slice(0, i - 1),
|
|
14669
|
+
...mid,
|
|
14670
|
+
...p.slice(i + 1)
|
|
14671
|
+
];
|
|
14672
|
+
};
|
|
14673
|
+
const changed = /* @__PURE__ */ new Set();
|
|
14674
|
+
const repairOne = (r, s, t, e) => {
|
|
14675
|
+
let p = dedupe(r.points);
|
|
14676
|
+
if (p.length < 2) p = [...r.points];
|
|
14677
|
+
let snapped = 0;
|
|
14678
|
+
const first = p[0];
|
|
14679
|
+
const last = p[p.length - 1];
|
|
14680
|
+
const wantA = fixedPortPoint(s, e.source) ?? (pointBoxDistance(first, s.box) > .5 ? nearestOnBoundary(s.box, first) : void 0);
|
|
14681
|
+
const wantB = fixedPortPoint(t, e.target) ?? (pointBoxDistance(last, t.box) > .5 ? nearestOnBoundary(t.box, last) : void 0);
|
|
14682
|
+
if (wantA && dist(wantA, first) > .5) {
|
|
14683
|
+
p[0] = wantA;
|
|
14684
|
+
snapped++;
|
|
14685
|
+
}
|
|
14686
|
+
if (wantB && dist(wantB, last) > .5) {
|
|
14687
|
+
p[p.length - 1] = wantB;
|
|
14688
|
+
snapped++;
|
|
14689
|
+
}
|
|
14690
|
+
let fault = snapped > 0;
|
|
14691
|
+
let pushed = 0;
|
|
14692
|
+
let detoured = 0;
|
|
14693
|
+
let fixes = 0;
|
|
14694
|
+
let failed = false;
|
|
14695
|
+
for (let i = 1; i < p.length;) {
|
|
14696
|
+
const hit = hitOf(p[i - 1], p[i], s, t);
|
|
14697
|
+
if (!hit) {
|
|
14698
|
+
i++;
|
|
14699
|
+
continue;
|
|
14700
|
+
}
|
|
14701
|
+
fault = true;
|
|
14702
|
+
if (++fixes > MAX_FIXES) {
|
|
14703
|
+
failed = true;
|
|
14704
|
+
break;
|
|
14705
|
+
}
|
|
14706
|
+
const moved = doPush && i >= 2 && i + 1 < p.length ? push(p, i, hit.box, s, t) : void 0;
|
|
14707
|
+
if (moved) {
|
|
14708
|
+
p = moved;
|
|
14709
|
+
pushed++;
|
|
14710
|
+
i++;
|
|
14711
|
+
continue;
|
|
14712
|
+
}
|
|
14713
|
+
const around = doDetour ? detour(p, i, s, t) : void 0;
|
|
14714
|
+
if (!around) {
|
|
14715
|
+
failed = true;
|
|
14716
|
+
break;
|
|
14717
|
+
}
|
|
14718
|
+
i += around.length - p.length + 1;
|
|
14719
|
+
p = around;
|
|
14720
|
+
detoured++;
|
|
14721
|
+
}
|
|
14722
|
+
if (!fault) return r;
|
|
14723
|
+
counts.faulty++;
|
|
14724
|
+
let whole;
|
|
14725
|
+
if (failed) {
|
|
14726
|
+
const a = p[0];
|
|
14727
|
+
const b = p[p.length - 1];
|
|
14728
|
+
const finder = doReroute ? waysOf() : void 0;
|
|
14729
|
+
const way = finder?.corners.path(leave(a), leave(b));
|
|
14730
|
+
const again = way ? dedupe([
|
|
14731
|
+
a,
|
|
14732
|
+
...finder.corners.pull(way),
|
|
14733
|
+
b
|
|
14734
|
+
]) : void 0;
|
|
14735
|
+
if (again && clear(again, s, t)) {
|
|
14736
|
+
p = again;
|
|
14737
|
+
whole = "rerouted";
|
|
14738
|
+
} else {
|
|
14739
|
+
const walk = doWalk ? walkerOf().path(a, s, b, t) : void 0;
|
|
14740
|
+
if (walk && clear(walk, s, t)) {
|
|
14741
|
+
p = walk;
|
|
14742
|
+
whole = "walked";
|
|
14743
|
+
}
|
|
14744
|
+
}
|
|
14745
|
+
if (!whole) {
|
|
14746
|
+
counts.kept++;
|
|
14747
|
+
return r;
|
|
14748
|
+
}
|
|
14749
|
+
counts[whole]++;
|
|
14750
|
+
}
|
|
14751
|
+
counts.snapped += snapped;
|
|
14752
|
+
if (!whole) {
|
|
14753
|
+
counts.pushed += pushed;
|
|
14754
|
+
counts.detoured += detoured;
|
|
14755
|
+
}
|
|
14756
|
+
changed.add(r.edge);
|
|
14757
|
+
return {
|
|
14758
|
+
edge: r.edge,
|
|
14759
|
+
points: p
|
|
14760
|
+
};
|
|
14761
|
+
};
|
|
14762
|
+
const routeOf = /* @__PURE__ */ new Map();
|
|
14763
|
+
for (const r of result.routes) if (!routeOf.has(r.edge)) routeOf.set(r.edge, r);
|
|
14764
|
+
const repaired = /* @__PURE__ */ new Map();
|
|
14765
|
+
for (const e of graph.edges) {
|
|
14766
|
+
const s = byId.get(e.source.node);
|
|
14767
|
+
const t = byId.get(e.target.node);
|
|
14768
|
+
if (!s || !t || repaired.has(e.id)) continue;
|
|
14769
|
+
counts.routes++;
|
|
14770
|
+
const r = routeOf.get(e.id);
|
|
14771
|
+
if (r && r.points.length >= 2) {
|
|
14772
|
+
repaired.set(e.id, repairOne(r, s, t, e));
|
|
14773
|
+
continue;
|
|
14774
|
+
}
|
|
14775
|
+
counts.faulty++;
|
|
14776
|
+
const ps = placePlug(s, e.source, center(t.box));
|
|
14777
|
+
const pt = placePlug(t, e.target, center(s.box));
|
|
14778
|
+
let way = waysOf().between(ps, pt, /* @__PURE__ */ new Set([s, t]));
|
|
14779
|
+
if (!clear(way, s, t)) way = doWalk ? walkerOf().path(ps.point, s, pt.point, t) : void 0;
|
|
14780
|
+
if (!way || !clear(way, s, t)) {
|
|
14781
|
+
counts.kept++;
|
|
14782
|
+
continue;
|
|
14783
|
+
}
|
|
14784
|
+
counts.added++;
|
|
14785
|
+
changed.add(e.id);
|
|
14786
|
+
repaired.set(e.id, {
|
|
14787
|
+
edge: e.id,
|
|
14788
|
+
points: way
|
|
14789
|
+
});
|
|
14790
|
+
}
|
|
14791
|
+
const out = [];
|
|
14792
|
+
const placed = /* @__PURE__ */ new Set();
|
|
14793
|
+
for (const r of result.routes) {
|
|
14794
|
+
const first = !placed.has(r.edge);
|
|
14795
|
+
placed.add(r.edge);
|
|
14796
|
+
out.push(first ? repaired.get(r.edge) ?? r : r);
|
|
14797
|
+
}
|
|
14798
|
+
for (const e of graph.edges) if (!placed.has(e.id) && repaired.has(e.id)) out.push(repaired.get(e.id));
|
|
14799
|
+
const final = /* @__PURE__ */ new Map();
|
|
14800
|
+
for (const r of out) if (!final.has(r.edge)) final.set(r.edge, r);
|
|
14801
|
+
let runs;
|
|
14802
|
+
if (result.runs) {
|
|
14803
|
+
runs = [];
|
|
14804
|
+
for (const run of result.runs) {
|
|
14805
|
+
const edges = run.edges.filter((id) => {
|
|
14806
|
+
if (!changed.has(id)) return true;
|
|
14807
|
+
const r = final.get(id);
|
|
14808
|
+
const ok = r !== void 0 && (holds(r.points, run.points) || holds(r.points, [...run.points].reverse()));
|
|
14809
|
+
if (!ok) counts.unbundled++;
|
|
14810
|
+
return ok;
|
|
14811
|
+
});
|
|
14812
|
+
if (edges.length === run.edges.length) runs.push(run);
|
|
14813
|
+
else if (edges.length >= 2) runs.push({
|
|
14814
|
+
...run,
|
|
14815
|
+
edges
|
|
14816
|
+
});
|
|
14817
|
+
else counts.unbundled += edges.length;
|
|
14818
|
+
}
|
|
14819
|
+
}
|
|
14820
|
+
let bundles;
|
|
14821
|
+
if (result.bundles) {
|
|
14822
|
+
bundles = [];
|
|
14823
|
+
for (const b of result.bundles) {
|
|
14824
|
+
const edges = b.edges.filter((id) => {
|
|
14825
|
+
if (!changed.has(id)) return true;
|
|
14826
|
+
const r = final.get(id);
|
|
14827
|
+
const ok = r !== void 0 && r.points.length >= b.trunk.length && b.trunk.every((q, i) => dist(q, r.points[i]) <= .5);
|
|
14828
|
+
if (!ok) counts.unbundled++;
|
|
14829
|
+
return ok;
|
|
14830
|
+
});
|
|
14831
|
+
if (edges.length === b.edges.length) bundles.push(b);
|
|
14832
|
+
else if (edges.length > 0) bundles.push({
|
|
14833
|
+
...b,
|
|
14834
|
+
edges
|
|
14835
|
+
});
|
|
14836
|
+
}
|
|
14837
|
+
}
|
|
14838
|
+
let levels;
|
|
14839
|
+
if (result.levels) levels = result.levels.map((level) => {
|
|
14840
|
+
const boxOf = new Map(level.items.map((it) => [it.id, it.box]));
|
|
14841
|
+
let touched = false;
|
|
14842
|
+
const links = level.links.map((l) => {
|
|
14843
|
+
const sb = boxOf.get(l.source);
|
|
14844
|
+
const tb = boxOf.get(l.target);
|
|
14845
|
+
const p = l.points;
|
|
14846
|
+
if (!sb || !tb || p.length < 2) return l;
|
|
14847
|
+
const a = onBoundary(p[0], sb) ? void 0 : nearestOnBoundary(sb, p[0]);
|
|
14848
|
+
const b = onBoundary(p[p.length - 1], tb) ? void 0 : nearestOnBoundary(tb, p[p.length - 1]);
|
|
14849
|
+
if (!a && !b) return l;
|
|
14850
|
+
touched = true;
|
|
14851
|
+
counts.linkEnds += (a ? 1 : 0) + (b ? 1 : 0);
|
|
14852
|
+
const points = [...p];
|
|
14853
|
+
if (a) points[0] = a;
|
|
14854
|
+
if (b) points[points.length - 1] = b;
|
|
14855
|
+
return {
|
|
14856
|
+
...l,
|
|
14857
|
+
points
|
|
14858
|
+
};
|
|
14859
|
+
});
|
|
14860
|
+
return touched ? {
|
|
14861
|
+
...level,
|
|
14862
|
+
links
|
|
14863
|
+
} : level;
|
|
14864
|
+
});
|
|
14865
|
+
return {
|
|
14866
|
+
result: {
|
|
14867
|
+
...result,
|
|
14868
|
+
routes: out,
|
|
14869
|
+
...runs ? { runs } : {},
|
|
14870
|
+
...bundles ? { bundles } : {},
|
|
14871
|
+
...levels ? { levels } : {}
|
|
14872
|
+
},
|
|
14873
|
+
counts
|
|
14874
|
+
};
|
|
14875
|
+
}
|
|
14876
|
+
/** This function gives a router that repairs the routes of `router` with `repairResult`. It keeps the name and the `maxNodes` of the router. */
|
|
14877
|
+
function withRepair(router, options = {}) {
|
|
14878
|
+
return {
|
|
14879
|
+
...router,
|
|
14880
|
+
route: (graph) => repairResult(graph, router.route(graph), options).result
|
|
14881
|
+
};
|
|
14882
|
+
}
|
|
14883
|
+
/**
|
|
14884
|
+
* This function gives a router strategy that runs `strategy` and then the repair step (RULE-046) with `repair`.
|
|
14885
|
+
* The new strategy has the name `<name>+repair`, and the options and the schema of `strategy`. The strategy `strategy` does not change.
|
|
14886
|
+
*/
|
|
14887
|
+
function repairStrategy(strategy, repair = {}) {
|
|
14888
|
+
return defineStrategy({
|
|
14889
|
+
step: "router",
|
|
14890
|
+
name: `${strategy.name}+repair`,
|
|
14891
|
+
describe: `${strategy.describe} Then one repair step fixes the breaks of the hard rules.`,
|
|
14892
|
+
defaults: { ...strategy.defaults },
|
|
14893
|
+
...strategy.schema === void 0 ? {} : { schema: strategy.schema },
|
|
14894
|
+
...strategy.maxNodes === void 0 ? {} : { maxNodes: strategy.maxNodes },
|
|
14895
|
+
run: (graph, options) => repairResult(graph, strategy.run(graph, options), repair).result
|
|
14896
|
+
});
|
|
14897
|
+
}
|
|
13038
14898
|
// src/bench/screen/screen.ts
|
|
13039
14899
|
/** The steps of the screen, in the order of the pipeline. */
|
|
13040
14900
|
const SCREEN_STEPS = [
|
|
@@ -13049,6 +14909,8 @@ const HIGHWAY_BASE_ROUTERS = [
|
|
|
13049
14909
|
"shared",
|
|
13050
14910
|
"trunks"
|
|
13051
14911
|
];
|
|
14912
|
+
/** The wrappers of `ScreenWrap`. */
|
|
14913
|
+
const SCREEN_WRAPS = ["stable", "repair"];
|
|
13052
14914
|
/** The hard rules of the routes of a router, or of a plan after `follow`. */
|
|
13053
14915
|
const ROUTE_RULES = [
|
|
13054
14916
|
"complete",
|
|
@@ -13117,8 +14979,11 @@ function screenRegistry(step, strategies, baseline) {
|
|
|
13117
14979
|
}
|
|
13118
14980
|
/** This function tells if a step of the screen gives routes. The step `router` gives routes, and the step `highway` gives routes after `follow`. */
|
|
13119
14981
|
const givesRoutes = (step) => step === "router" || step === "highway";
|
|
13120
|
-
/**
|
|
13121
|
-
function
|
|
14982
|
+
/**
|
|
14983
|
+
* This function gives the values of a row of `compareStep`, with the measures of `measureQuality` for a router.
|
|
14984
|
+
* A router also gets the bundled share (B2), the merge angle (B6) and the bundle stretch (B3) against the routes `base` of `corners`.
|
|
14985
|
+
*/
|
|
14986
|
+
function rowValues(step, graph, row, base) {
|
|
13122
14987
|
const values = { ...row.values };
|
|
13123
14988
|
delete values["ms"];
|
|
13124
14989
|
if (givesRoutes(step) && row.result) {
|
|
@@ -13128,6 +14993,11 @@ function rowValues(step, graph, row) {
|
|
|
13128
14993
|
values["R8.tight"] = q.tight;
|
|
13129
14994
|
values["R10.plugAngle"] = q.plugAngle;
|
|
13130
14995
|
values["R15.inflections"] = q.inflections;
|
|
14996
|
+
values["B2.share"] = bundledShare(row.result);
|
|
14997
|
+
const st = bundleStretch(row.result.routes, base());
|
|
14998
|
+
values["B3.median"] = st.median;
|
|
14999
|
+
values["B3.q95"] = st.q95;
|
|
15000
|
+
values["B6.max"] = mergeAngle(row.result.routes);
|
|
13131
15001
|
}
|
|
13132
15002
|
return values;
|
|
13133
15003
|
}
|
|
@@ -13165,6 +15035,21 @@ function rowRules(step, graph, row, again) {
|
|
|
13165
15035
|
deterministic
|
|
13166
15036
|
};
|
|
13167
15037
|
}
|
|
15038
|
+
/** This function adds the router `<name>+<w>` for each router of the registry and each wrapper `w`. It gives the new registry and the new names. */
|
|
15039
|
+
function wrapRouters(registry, wraps) {
|
|
15040
|
+
const routers = listStrategies(registry, "router");
|
|
15041
|
+
const added = [];
|
|
15042
|
+
for (const w of wraps) for (const s of routers) added.push(w === "stable" ? stableOrderStrategy(s) : repairStrategy(s));
|
|
15043
|
+
return {
|
|
15044
|
+
registry: added.length === 0 ? registry : register(registry, ...added),
|
|
15045
|
+
added: added.map((s) => s.name)
|
|
15046
|
+
};
|
|
15047
|
+
}
|
|
15048
|
+
/** This function gives the name of a router without the suffix of a wrapper, for example `shared` for `shared+repair`. */
|
|
15049
|
+
function unwrappedName(name) {
|
|
15050
|
+
for (const w of SCREEN_WRAPS) if (name.endsWith(`+${w}`)) return name.slice(0, -(w.length + 1));
|
|
15051
|
+
return name;
|
|
15052
|
+
}
|
|
13168
15053
|
/** This function keeps the finite values and gives them the names of a record. */
|
|
13169
15054
|
function named(values) {
|
|
13170
15055
|
const out = {};
|
|
@@ -13187,11 +15072,22 @@ function screen(step, strategies = [], options = {}) {
|
|
|
13187
15072
|
const screened = screenRegistry(step, strategies, options.baseline ?? true);
|
|
13188
15073
|
const candidates = screened.candidates;
|
|
13189
15074
|
const base = step === "highway" ? (options.baseRouters ?? HIGHWAY_BASE_ROUTERS).map((name) => getStrategy(STRATEGIES, "router", name)) : [];
|
|
13190
|
-
const
|
|
15075
|
+
const unwrapped = step === "highway" ? createRegistry([...base, ...listStrategies(screened.registry, "highway").map((s) => highwayRouterStrategy(s))]) : screened.registry;
|
|
15076
|
+
const wrapped = givesRoutes(step) ? wrapRouters(unwrapped, options.wrap ?? []) : {
|
|
15077
|
+
registry: unwrapped,
|
|
15078
|
+
added: []
|
|
15079
|
+
};
|
|
15080
|
+
const registry = wrapped.registry;
|
|
15081
|
+
for (const name of wrapped.added) candidates.add(name);
|
|
13191
15082
|
const compareAs = step === "highway" ? "router" : step;
|
|
13192
15083
|
const baseNames = new Set(base.map((s) => s.name));
|
|
13193
|
-
const stepOf = (name) => baseNames.has(name) ? "router" : step;
|
|
13194
|
-
const own = options.options
|
|
15084
|
+
const stepOf = (name) => baseNames.has(unwrappedName(name)) ? "router" : step;
|
|
15085
|
+
const own = { ...options.options };
|
|
15086
|
+
for (const name of wrapped.added) {
|
|
15087
|
+
const from = options.options?.[unwrappedName(name)];
|
|
15088
|
+
if (own[name] === void 0 && from !== void 0) own[name] = from;
|
|
15089
|
+
}
|
|
15090
|
+
const shuffles = givesRoutes(step) ? Math.max(0, Math.floor(options.shuffles ?? 0)) : 0;
|
|
13195
15091
|
const seed = options.seed ?? 1;
|
|
13196
15092
|
const log = options.log ?? (() => {});
|
|
13197
15093
|
const families = options.families;
|
|
@@ -13219,6 +15115,8 @@ function screen(step, strategies = [], options = {}) {
|
|
|
13219
15115
|
graph,
|
|
13220
15116
|
results: new Map(cmp.rows.flatMap((r) => r.result ? [[r.strategy, r.result]] : []))
|
|
13221
15117
|
});
|
|
15118
|
+
let cornersRoutes;
|
|
15119
|
+
const cornersBase = () => cornersRoutes ??= (cmp.rows.find((r) => r.strategy === "corners" && r.result)?.result ?? asRouter(getStrategy(STRATEGIES, "router", "corners")).route(graph)).routes;
|
|
13222
15120
|
cmp.rows.forEach((row, i) => {
|
|
13223
15121
|
const strategy = byName.get(row.strategy);
|
|
13224
15122
|
const head = {
|
|
@@ -13257,9 +15155,26 @@ function screen(step, strategies = [], options = {}) {
|
|
|
13257
15155
|
return;
|
|
13258
15156
|
}
|
|
13259
15157
|
const { hard, deterministic } = rowRules(step, graph, row, again?.rows[i]);
|
|
15158
|
+
const values = rowValues(step, graph, row, cornersBase);
|
|
15159
|
+
if (shuffles > 0 && row.result) {
|
|
15160
|
+
const router = asRouter(strategy, own[row.strategy]);
|
|
15161
|
+
const f = orderFragility(graph, (g) => router.route(g), {
|
|
15162
|
+
orders: shuffles,
|
|
15163
|
+
seed,
|
|
15164
|
+
base: row.result
|
|
15165
|
+
});
|
|
15166
|
+
Object.assign(values, {
|
|
15167
|
+
"W3.crossings": f.crossings,
|
|
15168
|
+
"W3.detour": f.detour,
|
|
15169
|
+
"W3.inkSaved": f.inkSaved,
|
|
15170
|
+
"W3.ink": f.ink,
|
|
15171
|
+
"W3.changed": f.changed,
|
|
15172
|
+
"W3.poa": f.poa
|
|
15173
|
+
});
|
|
15174
|
+
}
|
|
13260
15175
|
const record = {
|
|
13261
15176
|
...head,
|
|
13262
|
-
measures: named(
|
|
15177
|
+
measures: named(values),
|
|
13263
15178
|
hard,
|
|
13264
15179
|
tolerance: { through: TOLERANCE },
|
|
13265
15180
|
deterministic,
|
|
@@ -13449,6 +15364,6 @@ function rectScattered(seed, params, set = "xcheck-v1") {
|
|
|
13449
15364
|
hash: documentHash(body)
|
|
13450
15365
|
};
|
|
13451
15366
|
}
|
|
13452
|
-
export { BACKTRACK_MIN, BACKTRACK_SHARE, BREAK, BREAK_EVEN, BUILT_IN_STRATEGIES, CAP_SWEEP, CASES, CLEARANCE_CAP, COMPARE_STEPS, CORRIDOR_DEFAULTS, CURVE_TYPES, CUT_DEFAULTS, CornerGraph, FIXTURES, FRONT_OBJECTIVES, GROWTH_SWEEP, Grid, HIGHWAY_BASE_ROUTERS, KD_DEFAULTS, KENDALL_EXACT, LANE_ORDERS, MEASURES, OUTSIDE_SLACK, Obstacles, PICK, PROPOSED_RULES, ROOM_FAMILIES, ROUTERS, SCREEN_RULES, SCREEN_SETS, SCREEN_STEPS, SCREEN_V1, SECTION_DEFAULTS, SIDE_NORMAL, SLACK_SWEEP, SPACING_LIMIT, STEPS, STRATEGIES, STRETCH_SWEEP, SUITE_COLUMNS, SoftGraph, TOLERANCE, WayFinder, adjustedRandIndex, allSections, ambiguity, arcCurve, asRouter, backtrackOf, balance, benchBendRoom, benchCurveStrategies, benchCurves, benchHighways, benchLevelRoom, benchOne, benchRoom, benchRun, bendCount, bendingEnergy, boardGaps, boardUnit, boxExit, boxesOverlap, buildSections, byCollection, byDataGroup, canonicalJson, capSettings, center, changeGraph, checkLevels, checkOptions, checkPlan, checkProperties, checkSchema, checkSections, clampOptions, clothoidCurve, compareStep, cornersRouter, corpusManifest, corridorPlan, countCrossings, countOverlaps, createRegistry, crossesItself, crossingAngle, crossingAngles, cubicAt, curveFaults, curveJoints, curveOf, curveRuns, curveSpace, curveStrategies, cutGroups, cutShare, defineStrategy, denseGroupsBoard, detourSpread, dist, distinctPoints, documentHash, dominates, drawnSegments, familySeed, farLevel, findStrategy, fixedPortPoint, fixtureGroupOptions, fnv1a32, follow, formatBendRoom, formatCurves, formatFaults, formatFronts, formatHighways, formatInput, formatLevelRoom, formatLevels, formatMeasureTime, formatRoom, formatRules, formatScreen, formatSections, formatSummaries, formatTable, frechet, g1Defect, g2Defect, gapLanes, getStrategy, goesOutAndBack, graphInput, groupEdges, groupRun, groupSettings, groupStability, groupStrategies, growthSettings, hasCusp, highwayCurve, highwayCurves, highwayRouter, highwayRouterStrategy, highwayStrategies, hubChainBoard, inflate, inflectionCount, inflectionCurve, inflectionSector, inkSaved, inputGraph, inputId, itemSizes, jogPeaks, joinReports, jsonSafe, kdGroups, kendallTau, keyGroups, keyedSections, keysPlan, laneOrder, laneRoutes, laneSettings, leafGroups, lengthBetween, levelCut, levelGrouping, levelMeasures, levelPairs, levelRoom, listOptions, listStrategies, makeFixture, makeRoom, manyGroupsBoard, measureBoard, measureByKey, measureByName, measureHighways, measureJogs, measureLevel, measureLevelFaults, measureLinks, measureName, measureQuality, measureRouteFaults, measureStability, memberAt, minHash, modularity, mulberry32, namedObjectives, nearestSide, nestedGridBoard, nestingFaults, nodeIndex, offsetPolyline, openGaps, optionDefaults, orderChanges, orderLanes, orderRows, orderStability, orderSuiteRows, packedGridBoard, paretoFront, parseLines, passSettings, pathPoints, peakCurvature, pickLevel, placePlug, planOfResult, plugSpacing, plugsRouter, pointAt, pointBoxDistance, pointOnSide, pointSegmentDistance, polylineLength, positionStability, pullApart, quadtreeGroups, quantile, randomInt, recordLines, rectScattered, regionBounds, register, rng, roomDemand, roomLinks, roomOrderFaults, roomStrategies, round3, roundCorners, roundedCurve, routeMeasures, routeStability, routerStrategies, routersOf, ruleCounts, runBenchmark, runKey, runSuite, sameData, sampleCurve, screen, screenInputs, sectionsRouter, segmentBounds, segmentBoxDistance, segmentHitsBox, segmentHitsRect, segmentKey, segmentsCross, segmentsOf, sha256Hex, sharedRouter, sharedWaysPlan, sideAngle, sideToward, slackSettings, smoothCurve, splineCurve, splitPoint, stepSeed, straightCurve, straightRouter, stretchSettings, suiteFronts, sumFaults, summarize, summarizeRows, sweepGrid, sweepOf, sweepRoom, sweepSections, sweepValues, toSvgPath, totalTurn, treeCost, treeDepth, trunksRouter, turns, utf8, withBends };
|
|
15367
|
+
export { BACKTRACK_MIN, BACKTRACK_SHARE, BREAK, BREAK_EVEN, BUILT_IN_STRATEGIES, BoundaryWalk, CAP_SWEEP, CASES, CLEARANCE_CAP, COMPARE_STEPS, CORRIDOR_DEFAULTS, CURVE_TYPES, CUT_DEFAULTS, CornerGraph, FIXTURES, FRONT_OBJECTIVES, GROWTH_SWEEP, Grid, HIGHWAY_BASE_ROUTERS, KD_DEFAULTS, KENDALL_EXACT, LANE_ORDERS, LANE_TRACKS, MEASURES, OUTSIDE_SLACK, Obstacles, PICK, PROPOSED_RULES, ROOM_FAMILIES, ROUTERS, SCREEN_RULES, SCREEN_SETS, SCREEN_STEPS, SCREEN_V1, SCREEN_WRAPS, SECTION_DEFAULTS, SIDE_NORMAL, SLACK_SWEEP, SPACING_LIMIT, STEPS, STRATEGIES, STRETCH_SWEEP, SUITE_COLUMNS, SoftGraph, TOLERANCE, WayFinder, adjustedRandIndex, allSections, ambiguity, arcCurve, asRouter, backtrackOf, balance, benchBendRoom, benchCurveStrategies, benchCurves, benchHighways, benchLevelRoom, benchOne, benchRoom, benchRun, benchTracks, bendCount, bendingEnergy, boardGaps, boardUnit, boxExit, boxesOverlap, buildSections, bundleStretch, bundledShare, byCollection, byDataGroup, canonicalJson, capSettings, center, changeGraph, checkLevels, checkOptions, checkPlan, checkProperties, checkSchema, checkSections, clampOptions, clothoidCurve, compareStep, cornersRouter, corpusManifest, corridorPlan, countCrossings, countOverlaps, createRegistry, crossesItself, crossingAngle, crossingAngles, cubicAt, curveFaults, curveJoints, curveOf, curveRuns, curveSpace, curveStrategies, cutGroups, cutShare, defineStrategy, denseGroupsBoard, detourSpread, dist, distinctPoints, documentHash, dominates, drawnSegments, familySeed, farLevel, findStrategy, fixedPortPoint, fixtureGroupOptions, fnv1a32, follow, formatBendRoom, formatCurves, formatFaults, formatFronts, formatHighways, formatInput, formatLevelRoom, formatLevels, formatMeasureTime, formatRoom, formatRules, formatScreen, formatSections, formatSummaries, formatTable, formatTracks, frechet, g1Defect, g2Defect, gapLaneCounts, gapLanes, getStrategy, goesOutAndBack, graphInput, groupEdges, groupRun, groupSettings, groupStability, groupStrategies, growthSettings, hasCusp, highwayCurve, highwayCurves, highwayRouter, highwayRouterStrategy, highwayStrategies, hubChainBoard, inflate, inflectionCount, inflectionCurve, inflectionSector, inkSaved, inputGraph, inputId, itemSizes, jogPeaks, joinReports, jsonSafe, kdGroups, kendallTau, keyGroups, keyedSections, keysPlan, laneOrder, laneRoutes, laneSeparation, laneSettings, leafGroups, lengthBetween, levelCut, levelGrouping, levelMeasures, levelPairs, levelRoom, listOptions, listStrategies, makeFixture, makeRoom, manyGroupsBoard, measureBoard, measureByKey, measureByName, measureHighways, measureJogs, measureLevel, measureLevelFaults, measureLinks, measureName, measureQuality, measureRouteFaults, measureStability, memberAt, mergeAngle, minHash, modularity, mulberry32, namedObjectives, nearestOnBoundary, nearestSide, nestedGridBoard, nestingFaults, nodeIndex, offsetPolyline, openGaps, optionDefaults, orderChanges, orderFragility, orderLanes, orderRows, orderStability, orderSuiteRows, packedGridBoard, paretoFront, parseLines, passSettings, pathPoints, peakCurvature, pickLevel, placePlug, planOfResult, plugSpacing, plugsRouter, pointAt, pointBoxDistance, pointOnSide, pointSegmentDistance, polylineLength, positionStability, pullApart, quadtreeGroups, quantile, randomInt, recordLines, rectScattered, regionBounds, register, repairResult, repairStrategy, rng, roomDemand, roomLinks, roomOrderFaults, roomStrategies, round3, roundCorners, roundedCurve, routeInStableOrder, routeMeasures, routeStability, routerStrategies, routersOf, ruleCounts, runBenchmark, runKey, runSuite, sameData, sampleCurve, screen, screenInputs, sectionsRouter, segmentBounds, segmentBoxDistance, segmentHitsBox, segmentHitsRect, segmentKey, segmentsCross, segmentsOf, sha256Hex, sharedRouter, sharedWaysPlan, shuffleEdges, sideAngle, sideToward, slackSettings, smoothCurve, splineCurve, splitPoint, stableOrder, stableOrderStrategy, stepSeed, straightCurve, straightRouter, stretchSettings, suiteFronts, sumFaults, summarize, summarizeRows, sweepGrid, sweepOf, sweepRoom, sweepSections, sweepValues, toSvgPath, totalTurn, treeCost, treeDepth, trunksRouter, turns, unwrappedName, utf8, withBends, withRepair, withStableOrder };
|
|
13453
15368
|
|
|
13454
15369
|
//# sourceMappingURL=index.js.map
|