@mark1russell7/highways 0.9.3 → 0.9.5
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 +85 -5
- package/build/index.d.ts +954 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +10689 -5294
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bench/screen/exercise.ts +4 -1
- package/src/bench/screen/set.ts +58 -1
- package/src/families/cases.ts +17 -9
- package/src/families/coverage.ts +502 -0
- package/src/families/index.ts +4 -1
- package/src/highways/balance/index.ts +93 -0
- package/src/highways/bronchial/index.ts +144 -0
- package/src/highways/index.ts +5 -0
- package/src/highways/knee/index.ts +154 -0
- package/src/highways/knee/root.ts +19 -0
- package/src/highways/manifolds/index.ts +157 -0
- package/src/highways/slots/index.ts +178 -0
- package/src/index.ts +8 -0
- package/src/levels/detail/index.ts +140 -0
- package/src/levels/venation/index.ts +191 -0
- package/src/plugs/leaders/index.ts +42 -0
- package/src/plugs/spread/index.ts +39 -5
- package/src/routers/adam/index.ts +294 -0
- package/src/routers/annealing/index.ts +260 -0
- package/src/routers/canals/index.ts +191 -0
- package/src/routers/frames.ts +80 -16
- package/src/routers/gutter.ts +545 -0
- package/src/routers/index.ts +8 -2
- package/src/routers/reroute/index.ts +164 -0
- package/src/routers/shared.ts +43 -2
- package/src/routers/tight/index.ts +122 -0
- package/src/strategies/builtins.ts +158 -2
- package/src/strategies/schemas.ts +143 -0
- package/src/whole/curriculum/index.ts +243 -0
- package/src/whole/fireflies/index.ts +177 -0
- package/src/whole/portfolio/index.ts +108 -0
- package/src/whole/preferences/index.ts +247 -0
- package/src/whole/tracing/index.ts +144 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { Graph, GroupRule, Result, Router } from "../../model.ts";
|
|
2
|
+
import { defineStrategy, type Strategy } from "../../strategies/model.ts";
|
|
3
|
+
import type { OptionSchema } from "../../strategies/options.ts";
|
|
4
|
+
import { sharedRouter, type SharedSearch, type Way } from "../shared.ts";
|
|
5
|
+
import { byCollection } from "../trunks.ts";
|
|
6
|
+
|
|
7
|
+
/** The options of the router `canals` (NAT-004: canalization feeder trees to hubs). */
|
|
8
|
+
export interface CanalOptions {
|
|
9
|
+
/** The exponent `mu` of the flux in the growth of a conductance, from 1 to 3 in steps of 0.5. A value above 1 gives a tree. The default is 2. */
|
|
10
|
+
readonly mu: number;
|
|
11
|
+
/** The largest number of updates of the conductances, from 30 to 150. The updates stop when the ways do not change for 3 updates. The default is 60. */
|
|
12
|
+
readonly steps: number;
|
|
13
|
+
/** The decay `r` of a conductance, from 0.5 to 2. The default is 1. */
|
|
14
|
+
readonly decay: number;
|
|
15
|
+
/** A hub has this number of edges of one key or more, from 2 to 8. The default is 3. */
|
|
16
|
+
readonly minMembers: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The default options of the router `canals`. */
|
|
20
|
+
export const CANAL_DEFAULTS: Readonly<CanalOptions> = Object.freeze({ mu: 2, steps: 60, decay: 1, minMembers: 3 });
|
|
21
|
+
|
|
22
|
+
/** The declarations of the options of the router `canals` (NAT-004). */
|
|
23
|
+
export const CANAL_SCHEMA: OptionSchema<CanalOptions> = {
|
|
24
|
+
mu: { type: "number", min: 1, max: 3, step: 0.5, marks: [1, 2, 3], default: 2, describe: "The exponent of the flux in the growth of a conductance. A value above 1 gives a tree." },
|
|
25
|
+
steps: { type: "integer", min: 30, max: 150, step: 10, marks: [30, 60, 150], default: 60, describe: "The largest number of updates of the conductances." },
|
|
26
|
+
decay: { type: "number", min: 0.5, max: 2, step: 0.25, marks: [0.5, 1, 2], default: 1, describe: "The decay of a conductance." },
|
|
27
|
+
minMembers: { type: "integer", min: 2, max: 8, step: 1, marks: [2, 3, 5, 8], default: 3, describe: "A hub has this number of edges of one key or more." },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** The time step of one update of the conductances. */
|
|
31
|
+
const DT = 0.5;
|
|
32
|
+
|
|
33
|
+
/** The gain of a conductance in the cost of a link: a link with the conductance `D` costs `d / (1 + GAIN × D)`. */
|
|
34
|
+
const GAIN = 4;
|
|
35
|
+
|
|
36
|
+
/** The cost share of a link that another way of the key uses, as the default `discount` of `shared`. */
|
|
37
|
+
const SHARE = 0.5;
|
|
38
|
+
|
|
39
|
+
/** The updates with no change of a way, after which the updates stop. */
|
|
40
|
+
const PATIENCE = 3;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* This function gives `x^mu` for `x` from 0 to 1 and `mu` in steps of 0.5, with only `*` and `Math.sqrt`.
|
|
44
|
+
* These operations are exact to the last bit on each platform, so a decision does not depend on the engine.
|
|
45
|
+
*/
|
|
46
|
+
export function halfPower(x: number, mu: number): number {
|
|
47
|
+
const twice = Math.max(2, Math.min(6, Math.round(mu * 2)));
|
|
48
|
+
let out = 1;
|
|
49
|
+
for (let i = 0; i < Math.floor(twice / 2); i++) out *= x;
|
|
50
|
+
return twice % 2 === 1 ? out * Math.sqrt(x) : out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A hub: a node, the key of its edges, and the ways of those edges. */
|
|
54
|
+
export interface CanalHub {
|
|
55
|
+
readonly node: string;
|
|
56
|
+
readonly key: string;
|
|
57
|
+
readonly ways: readonly Way[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* This function gives the hubs of the ways. In each key, the node with the most ends is a hub when it has `minMembers` edges or more.
|
|
62
|
+
* Its edges leave the key, and the next hub of the key comes from the edges that stay. A tie goes to the smaller node id.
|
|
63
|
+
* An edge with no key is in no hub.
|
|
64
|
+
*/
|
|
65
|
+
export function canalHubs(ways: readonly Way[], minMembers: number): CanalHub[] {
|
|
66
|
+
const byKey = new Map<string, Way[]>();
|
|
67
|
+
for (const w of ways) {
|
|
68
|
+
if (!w.found || w.ids.length < 2 || w.key.startsWith("\u0000")) continue;
|
|
69
|
+
const list = byKey.get(w.key);
|
|
70
|
+
if (list) list.push(w);
|
|
71
|
+
else byKey.set(w.key, [w]);
|
|
72
|
+
}
|
|
73
|
+
const out: CanalHub[] = [];
|
|
74
|
+
for (const key of [...byKey.keys()].sort()) {
|
|
75
|
+
let left = byKey.get(key)!;
|
|
76
|
+
for (;;) {
|
|
77
|
+
const count = new Map<string, number>();
|
|
78
|
+
for (const w of left) for (const n of new Set([w.edge.source.node, w.edge.target.node])) count.set(n, (count.get(n) ?? 0) + 1);
|
|
79
|
+
let node: string | undefined;
|
|
80
|
+
let most = 0;
|
|
81
|
+
for (const [n, c] of count) if (c > most || (c === most && node !== undefined && n < node)) [node, most] = [n, c];
|
|
82
|
+
if (node === undefined || most < Math.max(2, minMembers)) break;
|
|
83
|
+
const hub = node;
|
|
84
|
+
const members = left.filter((w) => w.edge.source.node === hub || w.edge.target.node === hub);
|
|
85
|
+
out.push({ node: hub, key, ways: [...members].sort((a, b) => (a.edge.id < b.edge.id ? -1 : a.edge.id > b.edge.id ? 1 : 0)) });
|
|
86
|
+
left = left.filter((w) => !members.includes(w));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* This function grows a feeder tree to each hub (NAT-004, after the canalization of Sachs 1981 and Mitchison 1980).
|
|
94
|
+
* Each member edge is a source of one unit of flux, and the hub is the sink. The flux of a link is the share of the members whose ways use it.
|
|
95
|
+
* At each update, each conductance changes by `DT × (flux^mu - decay × D)`, and each member finds its way again with the cost `d / (1 + GAIN × D)`.
|
|
96
|
+
* A link that another way of the key uses costs half, as in `shared`.
|
|
97
|
+
* With `mu` above 1, a link with more flux grows faster, so the ways gather into one tree. The updates stop after `steps`, or when no way changes for 3 updates.
|
|
98
|
+
*/
|
|
99
|
+
export function canalWays(ways: readonly Way[], search: SharedSearch, options: Partial<CanalOptions> = {}): void {
|
|
100
|
+
const o = { ...CANAL_DEFAULTS, ...options };
|
|
101
|
+
const n = search.corners;
|
|
102
|
+
const linksOf = (w: Way): number[] => {
|
|
103
|
+
const out = new Set<number>();
|
|
104
|
+
for (let i = 1; i < w.ids.length; i++) if (w.ids[i - 1]! < n && w.ids[i]! < n) out.add(search.link(w.ids[i - 1]!, w.ids[i]!));
|
|
105
|
+
return [...out];
|
|
106
|
+
};
|
|
107
|
+
// The number of ways of each key on each link, so a link that another way of the key uses keeps the share of `shared`.
|
|
108
|
+
const usage = new Map<string, Map<number, number>>();
|
|
109
|
+
const use = (w: Way, add: number): void => {
|
|
110
|
+
let m = usage.get(w.key);
|
|
111
|
+
if (!m) usage.set(w.key, (m = new Map()));
|
|
112
|
+
for (const k of linksOf(w)) m.set(k, (m.get(k) ?? 0) + add);
|
|
113
|
+
};
|
|
114
|
+
for (const w of ways) if (w.found) use(w, 1);
|
|
115
|
+
for (const hub of canalHubs(ways, Math.floor(o.minMembers))) {
|
|
116
|
+
const members = hub.ways;
|
|
117
|
+
const used = usage.get(hub.key)!;
|
|
118
|
+
const conductance = new Map<number, number>();
|
|
119
|
+
let calm = 0;
|
|
120
|
+
for (let step = 0; step < Math.max(1, Math.floor(o.steps)); step++) {
|
|
121
|
+
const flux = new Map<number, number>();
|
|
122
|
+
for (const w of members) {
|
|
123
|
+
const seen = new Set<number>();
|
|
124
|
+
for (let i = 1; i < w.ids.length; i++) {
|
|
125
|
+
const a = w.ids[i - 1]!;
|
|
126
|
+
const b = w.ids[i]!;
|
|
127
|
+
if (a >= n || b >= n) continue;
|
|
128
|
+
const k = search.link(a, b);
|
|
129
|
+
if (seen.has(k)) continue;
|
|
130
|
+
seen.add(k);
|
|
131
|
+
flux.set(k, (flux.get(k) ?? 0) + 1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const k of new Set([...conductance.keys(), ...flux.keys()])) {
|
|
135
|
+
const phi = (flux.get(k) ?? 0) / members.length;
|
|
136
|
+
const d = conductance.get(k) ?? 0;
|
|
137
|
+
const next = d + DT * (halfPower(phi, o.mu) - o.decay * d);
|
|
138
|
+
if (next > 1e-6) conductance.set(k, next);
|
|
139
|
+
else conductance.delete(k);
|
|
140
|
+
}
|
|
141
|
+
let top = 0;
|
|
142
|
+
for (const d of conductance.values()) top = Math.max(top, d);
|
|
143
|
+
const floor = SHARE / (1 + GAIN * top);
|
|
144
|
+
let changed = 0;
|
|
145
|
+
for (const w of members) {
|
|
146
|
+
use(w, -1);
|
|
147
|
+
const next = search.find(
|
|
148
|
+
w,
|
|
149
|
+
(i, j, d) => {
|
|
150
|
+
const k = search.link(i, j);
|
|
151
|
+
return ((used.get(k) ?? 0) > 0 ? SHARE * d : d) / (1 + GAIN * (conductance.get(k) ?? 0));
|
|
152
|
+
},
|
|
153
|
+
floor,
|
|
154
|
+
);
|
|
155
|
+
if (next) {
|
|
156
|
+
if (next.ids.length !== w.ids.length || next.ids.some((id, i) => id !== w.ids[i])) changed++;
|
|
157
|
+
w.seq = next.seq;
|
|
158
|
+
w.ids = next.ids;
|
|
159
|
+
}
|
|
160
|
+
use(w, 1);
|
|
161
|
+
}
|
|
162
|
+
calm = changed === 0 ? calm + 1 : 0;
|
|
163
|
+
if (calm >= PATIENCE) break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* This function makes the router `canals` (NAT-004). It is the router `shared`, then the feeder trees of `canalWays` to the hubs of each key.
|
|
170
|
+
* The runs and the bundles come from the ways after the trees.
|
|
171
|
+
*/
|
|
172
|
+
export function canalRouter(options: Partial<CanalOptions> & { readonly name: string; readonly rule?: GroupRule; readonly maxNodes?: number }): Router {
|
|
173
|
+
const o = { ...CANAL_DEFAULTS, ...options };
|
|
174
|
+
return sharedRouter({ name: o.name, ...(o.rule === undefined ? {} : { rule: o.rule }), ...(o.maxNodes === undefined ? {} : { maxNodes: o.maxNodes }) }, undefined, {
|
|
175
|
+
refine: (ways, search) => canalWays(ways, search, o),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The router strategy `canals` (NAT-004), with the rule `byCollection` of `shared`. It is not a built-in router, so the benchmark and the hashes of `ROUTERS` do not change.
|
|
181
|
+
* A screen module or a consumer adds it to a registry.
|
|
182
|
+
*/
|
|
183
|
+
export const canalStrategy: Strategy<"router", CanalOptions> = defineStrategy({
|
|
184
|
+
step: "router",
|
|
185
|
+
name: "canals",
|
|
186
|
+
describe: "Canalization feeder trees (NAT-004). The members of a hub are sources of flux, and the links with more flux get cheaper until the ways make one tree.",
|
|
187
|
+
defaults: { ...CANAL_DEFAULTS },
|
|
188
|
+
schema: CANAL_SCHEMA,
|
|
189
|
+
maxNodes: 10_000,
|
|
190
|
+
run: (graph: Graph, o: Readonly<CanalOptions>): Result => canalRouter({ ...o, name: "canals", rule: byCollection }).route(graph),
|
|
191
|
+
});
|
package/src/routers/frames.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { center, dist, type Box, type Point } from "../geom.ts";
|
|
2
|
-
import { nodeIndex, type Graph, type InnerEdge, type Node, type Route } from "../model.ts";
|
|
2
|
+
import { fixedPortPoint, nodeIndex, type Graph, type InnerEdge, type Node, type Route } from "../model.ts";
|
|
3
3
|
import { placePlug, type Plug } from "../plugs.ts";
|
|
4
4
|
import { WayFinder, type WayOptions } from "../ways.ts";
|
|
5
|
+
import { gutterRoutes, type GutterEdge } from "./gutter.ts";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* The rule for a node box that is not fully inside the box of its frame.
|
|
@@ -10,12 +11,22 @@ import { WayFinder, type WayOptions } from "../ways.ts";
|
|
|
10
11
|
*/
|
|
11
12
|
export type Outside = "throw" | "skip" | "clamp";
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The mode of the inner routes. With `corners`, the corner graph gives each route, in the box of the frame smaller by `frameInset`.
|
|
16
|
+
* With `gutter`, each route is orthogonal in the free room of the frame box: the gaps between the nodes and the strips at the walls.
|
|
17
|
+
*/
|
|
18
|
+
export type InnerMode = "corners" | "gutter";
|
|
19
|
+
|
|
20
|
+
/** The options of the inner routes: the options of the way finder, the rule `outside`, the inset `frameInset`, the mode `inner` and `bendCost`. */
|
|
14
21
|
export interface FrameOptions extends WayOptions {
|
|
15
22
|
/** The rule for a node box outside the box of its frame. The default is `throw`. */
|
|
16
23
|
readonly outside?: Outside;
|
|
17
|
-
/** The distance from the walls of a frame to the bounds of its inner routes. The default is the margin of the router. */
|
|
24
|
+
/** The distance from the walls of a frame to the bounds of its inner routes. The default is the margin of the router. The mode `gutter` does not use it. */
|
|
18
25
|
readonly frameInset?: number;
|
|
26
|
+
/** The mode of the inner routes. The default is `corners`. */
|
|
27
|
+
readonly inner?: InnerMode;
|
|
28
|
+
/** The cost of one bend of an inner route of the mode `gutter`, in units of length. The default is 16. */
|
|
29
|
+
readonly bendCost?: number;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
/** The inside of one frame for the inner edges to one depth: the bounds, the way finder and the obstacles by id. */
|
|
@@ -41,6 +52,7 @@ export function frameRoutes(graph: Graph, inner: readonly InnerEdge[], options:
|
|
|
41
52
|
if (list) list.push(n);
|
|
42
53
|
else children.set(n.parent, [n]);
|
|
43
54
|
}
|
|
55
|
+
if (options.inner === "gutter") return gutterFrameRoutes(inner, options, byId, children);
|
|
44
56
|
const margin = options.margin ?? 5;
|
|
45
57
|
const inset = options.frameInset ?? margin;
|
|
46
58
|
const rooms = new Map<string, Room>();
|
|
@@ -48,9 +60,7 @@ export function frameRoutes(graph: Graph, inner: readonly InnerEdge[], options:
|
|
|
48
60
|
for (const { edge, frame, plug } of inner) {
|
|
49
61
|
const f = byId.get(frame)!;
|
|
50
62
|
const [own, far] = plug === "source" ? [edge.source, edge.target] : [edge.target, edge.source];
|
|
51
|
-
|
|
52
|
-
const chain: string[] = [];
|
|
53
|
-
for (let p = byId.get(far.node)!.parent!; p !== frame; p = byId.get(p)!.parent!) chain.push(p);
|
|
63
|
+
const chain = chainOf(byId, frame, far.node);
|
|
54
64
|
const key = JSON.stringify([frame, ...chain]);
|
|
55
65
|
let room = rooms.get(key);
|
|
56
66
|
if (!room) rooms.set(key, (room = makeRoom(f, chain, children, byId, inset, options)));
|
|
@@ -69,6 +79,51 @@ export function frameRoutes(graph: Graph, inner: readonly InnerEdge[], options:
|
|
|
69
79
|
return routes;
|
|
70
80
|
}
|
|
71
81
|
|
|
82
|
+
/** This function gives the nodes between the frame and the far end, the nearest first. Each of them holds the far end, so it is not an obstacle. */
|
|
83
|
+
function chainOf(byId: ReadonlyMap<string, Node>, frame: string, far: string): string[] {
|
|
84
|
+
const chain: string[] = [];
|
|
85
|
+
for (let p = byId.get(far)!.parent!; p !== frame; p = byId.get(p)!.parent!) chain.push(p);
|
|
86
|
+
return chain;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* This function gives the inner routes of the mode `gutter`. The inner edges of one room route together, so that they share the gaps in lanes.
|
|
91
|
+
* A route is orthogonal, in the free room of the frame box. An edge without a way through the free room gets no route.
|
|
92
|
+
*/
|
|
93
|
+
function gutterFrameRoutes(inner: readonly InnerEdge[], options: FrameOptions, byId: ReadonlyMap<string, Node>, children: ReadonlyMap<string, readonly Node[]>): Route[] {
|
|
94
|
+
const rooms = new Map<string, { readonly nodes: Map<string, Node>; readonly items: { at: number; edge: GutterEdge }[]; readonly frame: Box }>();
|
|
95
|
+
const routes: (Route | undefined)[] = inner.map(() => undefined);
|
|
96
|
+
inner.forEach(({ edge, frame, plug }, at) => {
|
|
97
|
+
const f = byId.get(frame)!;
|
|
98
|
+
const [own, far] = plug === "source" ? [edge.source, edge.target] : [edge.target, edge.source];
|
|
99
|
+
const chain = chainOf(byId, frame, far.node);
|
|
100
|
+
const key = JSON.stringify([frame, ...chain]);
|
|
101
|
+
let room = rooms.get(key);
|
|
102
|
+
if (!room) rooms.set(key, (room = { nodes: roomNodes(f, chain, children, byId, f.box, options), items: [], frame: f.box }));
|
|
103
|
+
const end = room.nodes.get(far.node);
|
|
104
|
+
if (!end) return;
|
|
105
|
+
const fp = placePlug(f, own, center(end.box));
|
|
106
|
+
const port = fixedPortPoint(end, far) === undefined ? undefined : placePlug(end, far, fp.point);
|
|
107
|
+
const side = far.port === undefined ? undefined : end.ports?.find((p) => p.id === far.port)?.side;
|
|
108
|
+
room.items.push({ at, edge: { plug: fp, end, ...(port ? { port } : {}), ...(side ? { side } : {}) } });
|
|
109
|
+
});
|
|
110
|
+
for (const room of rooms.values()) {
|
|
111
|
+
const found = gutterRoutes(
|
|
112
|
+
room.frame,
|
|
113
|
+
[...room.nodes.values()],
|
|
114
|
+
room.items.map((i) => i.edge),
|
|
115
|
+
{ laneGap: options.margin ?? 5, ...(options.bendCost === undefined ? {} : { bendCost: options.bendCost }) },
|
|
116
|
+
);
|
|
117
|
+
room.items.forEach(({ at }, k) => {
|
|
118
|
+
const points = found[k];
|
|
119
|
+
if (!points) return;
|
|
120
|
+
const { edge, frame, plug } = inner[at]!;
|
|
121
|
+
routes[at] = { edge: edge.id, points: plug === "source" ? points : points.reverse(), frame };
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return routes.filter((r): r is Route => r !== undefined);
|
|
125
|
+
}
|
|
126
|
+
|
|
72
127
|
/** This function puts the plug of the wall before a way that starts on the bounds. A first point on the line of the stub goes. */
|
|
73
128
|
function stub(plug: Point, way: Point[]): Point[] {
|
|
74
129
|
const [a, b] = way;
|
|
@@ -86,6 +141,24 @@ function stub(plug: Point, way: Point[]): Point[] {
|
|
|
86
141
|
function makeRoom(f: Node, chain: readonly string[], children: ReadonlyMap<string, readonly Node[]>, byId: ReadonlyMap<string, Node>, inset: number, options: FrameOptions): Room {
|
|
87
142
|
const margin = options.margin ?? 5;
|
|
88
143
|
const bounds = shrink(f.box, inset);
|
|
144
|
+
const nodes = roomNodes(f, chain, children, byId, bounds, options);
|
|
145
|
+
// The band of each wall starts half a margin outside the bounds, so that a way keeps in the bounds.
|
|
146
|
+
const w = shrink(bounds, -(margin / 2 + EPS));
|
|
147
|
+
const t = 2 * margin + 1;
|
|
148
|
+
const walls: Node[] = [
|
|
149
|
+
{ x: w.x - t, y: w.y - t, w: w.w + 2 * t, h: t },
|
|
150
|
+
{ x: w.x - t, y: w.y + w.h, w: w.w + 2 * t, h: t },
|
|
151
|
+
{ x: w.x - t, y: w.y, w: t, h: w.h },
|
|
152
|
+
{ x: w.x + w.w, y: w.y, w: t, h: w.h },
|
|
153
|
+
].map((box, i) => ({ id: `\u0000wall${i}`, box }));
|
|
154
|
+
return { ways: new WayFinder([...nodes.values(), ...walls], options), nodes };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* This function gives the obstacles of a room by id. They are the children of the frame and of each node of the chain, but not the chain.
|
|
159
|
+
* It applies the rule `outside` to each box outside the box of its holder. The rule `clamp` cuts such a box to `bounds`.
|
|
160
|
+
*/
|
|
161
|
+
function roomNodes(f: Node, chain: readonly string[], children: ReadonlyMap<string, readonly Node[]>, byId: ReadonlyMap<string, Node>, bounds: Box, options: FrameOptions): Map<string, Node> {
|
|
89
162
|
const skip = new Set(chain);
|
|
90
163
|
const nodes = new Map<string, Node>();
|
|
91
164
|
for (const holder of [f.id, ...chain]) {
|
|
@@ -104,16 +177,7 @@ function makeRoom(f: Node, chain: readonly string[], children: ReadonlyMap<strin
|
|
|
104
177
|
if (cut) nodes.set(n.id, { ...n, box: cut, ...(n.ports ? { ports: n.ports.map((p) => (p.at ? { ...p, at: { x: p.at.x + n.box.x - cut.x, y: p.at.y + n.box.y - cut.y } } : p)) } : {}) });
|
|
105
178
|
}
|
|
106
179
|
}
|
|
107
|
-
|
|
108
|
-
const w = shrink(bounds, -(margin / 2 + EPS));
|
|
109
|
-
const t = 2 * margin + 1;
|
|
110
|
-
const walls: Node[] = [
|
|
111
|
-
{ x: w.x - t, y: w.y - t, w: w.w + 2 * t, h: t },
|
|
112
|
-
{ x: w.x - t, y: w.y + w.h, w: w.w + 2 * t, h: t },
|
|
113
|
-
{ x: w.x - t, y: w.y, w: t, h: w.h },
|
|
114
|
-
{ x: w.x + w.w, y: w.y, w: t, h: w.h },
|
|
115
|
-
].map((box, i) => ({ id: `\u0000wall${i}`, box }));
|
|
116
|
-
return { ways: new WayFinder([...nodes.values(), ...walls], options), nodes };
|
|
180
|
+
return nodes;
|
|
117
181
|
}
|
|
118
182
|
|
|
119
183
|
/** This function gives the box, smaller by `d` on each side. A negative `d` makes it larger. */
|