@expofp/wayfinding-compiler 0.0.0-bootstrap.0

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.
Files changed (110) hide show
  1. package/README.md +446 -0
  2. package/dist/index.d.ts +14 -0
  3. package/dist/index.js +3 -0
  4. package/dist/lib/attach.d.ts +43 -0
  5. package/dist/lib/attach.js +199 -0
  6. package/dist/lib/axes.d.ts +76 -0
  7. package/dist/lib/axes.js +103 -0
  8. package/dist/lib/blockers.d.ts +134 -0
  9. package/dist/lib/blockers.js +289 -0
  10. package/dist/lib/cell-array.d.ts +14 -0
  11. package/dist/lib/cell-array.js +25 -0
  12. package/dist/lib/centering.d.ts +14 -0
  13. package/dist/lib/centering.js +88 -0
  14. package/dist/lib/compile-layer.d.ts +41 -0
  15. package/dist/lib/compile-layer.js +241 -0
  16. package/dist/lib/compile-wayfinding.d.ts +90 -0
  17. package/dist/lib/compile-wayfinding.js +203 -0
  18. package/dist/lib/connect.d.ts +35 -0
  19. package/dist/lib/connect.js +206 -0
  20. package/dist/lib/diagnostics.d.ts +190 -0
  21. package/dist/lib/diagnostics.js +634 -0
  22. package/dist/lib/doorways.d.ts +78 -0
  23. package/dist/lib/doorways.js +331 -0
  24. package/dist/lib/emit-network.d.ts +47 -0
  25. package/dist/lib/emit-network.js +105 -0
  26. package/dist/lib/emit-v1.d.ts +31 -0
  27. package/dist/lib/emit-v1.js +56 -0
  28. package/dist/lib/entries.d.ts +123 -0
  29. package/dist/lib/entries.js +448 -0
  30. package/dist/lib/floor-grids.d.ts +48 -0
  31. package/dist/lib/floor-grids.js +185 -0
  32. package/dist/lib/framing.d.ts +20 -0
  33. package/dist/lib/framing.js +187 -0
  34. package/dist/lib/geom/boolean-ops.d.ts +21 -0
  35. package/dist/lib/geom/boolean-ops.js +1 -0
  36. package/dist/lib/geom/clipper2-ops.d.ts +7 -0
  37. package/dist/lib/geom/clipper2-ops.js +50 -0
  38. package/dist/lib/geom/footprint.d.ts +26 -0
  39. package/dist/lib/geom/footprint.js +203 -0
  40. package/dist/lib/geom/frames.d.ts +116 -0
  41. package/dist/lib/geom/frames.js +233 -0
  42. package/dist/lib/geom/mesh-outline.d.ts +13 -0
  43. package/dist/lib/geom/mesh-outline.js +90 -0
  44. package/dist/lib/geom/normalize.d.ts +28 -0
  45. package/dist/lib/geom/normalize.js +118 -0
  46. package/dist/lib/geom/point-key.d.ts +16 -0
  47. package/dist/lib/geom/point-key.js +15 -0
  48. package/dist/lib/geom/primitives.d.ts +105 -0
  49. package/dist/lib/geom/primitives.js +184 -0
  50. package/dist/lib/geom/split-crossings.d.ts +14 -0
  51. package/dist/lib/geom/split-crossings.js +171 -0
  52. package/dist/lib/geom/types.d.ts +9 -0
  53. package/dist/lib/geom/types.js +1 -0
  54. package/dist/lib/geom/walls.d.ts +31 -0
  55. package/dist/lib/geom/walls.js +71 -0
  56. package/dist/lib/graph-invariants.d.ts +49 -0
  57. package/dist/lib/graph-invariants.js +129 -0
  58. package/dist/lib/graph-types.d.ts +143 -0
  59. package/dist/lib/graph-types.js +7 -0
  60. package/dist/lib/grid-network.d.ts +20 -0
  61. package/dist/lib/grid-network.js +349 -0
  62. package/dist/lib/lattice-map.d.ts +19 -0
  63. package/dist/lib/lattice-map.js +92 -0
  64. package/dist/lib/lattice-network.d.ts +32 -0
  65. package/dist/lib/lattice-network.js +289 -0
  66. package/dist/lib/mask.d.ts +54 -0
  67. package/dist/lib/mask.js +210 -0
  68. package/dist/lib/min-heap.d.ts +24 -0
  69. package/dist/lib/min-heap.js +73 -0
  70. package/dist/lib/network-build.d.ts +21 -0
  71. package/dist/lib/network-build.js +4 -0
  72. package/dist/lib/network-graph.d.ts +80 -0
  73. package/dist/lib/network-graph.js +467 -0
  74. package/dist/lib/network-setup.d.ts +50 -0
  75. package/dist/lib/network-setup.js +76 -0
  76. package/dist/lib/network-sizes.d.ts +29 -0
  77. package/dist/lib/network-sizes.js +22 -0
  78. package/dist/lib/network-store.d.ts +173 -0
  79. package/dist/lib/network-store.js +430 -0
  80. package/dist/lib/openings.d.ts +54 -0
  81. package/dist/lib/openings.js +387 -0
  82. package/dist/lib/place-path.d.ts +47 -0
  83. package/dist/lib/place-path.js +346 -0
  84. package/dist/lib/post-passes.d.ts +31 -0
  85. package/dist/lib/post-passes.js +305 -0
  86. package/dist/lib/reachability.d.ts +58 -0
  87. package/dist/lib/reachability.js +220 -0
  88. package/dist/lib/report.d.ts +49 -0
  89. package/dist/lib/report.js +374 -0
  90. package/dist/lib/router.d.ts +111 -0
  91. package/dist/lib/router.js +388 -0
  92. package/dist/lib/runs.d.ts +92 -0
  93. package/dist/lib/runs.js +232 -0
  94. package/dist/lib/scale.d.ts +58 -0
  95. package/dist/lib/scale.js +185 -0
  96. package/dist/lib/steiner-network.d.ts +19 -0
  97. package/dist/lib/steiner-network.js +183 -0
  98. package/dist/lib/strip.d.ts +43 -0
  99. package/dist/lib/strip.js +200 -0
  100. package/dist/lib/stubs.d.ts +84 -0
  101. package/dist/lib/stubs.js +316 -0
  102. package/dist/lib/transitions.d.ts +72 -0
  103. package/dist/lib/transitions.js +191 -0
  104. package/dist/lib/types.d.ts +126 -0
  105. package/dist/lib/types.js +1 -0
  106. package/dist/lib/validate-source.d.ts +41 -0
  107. package/dist/lib/validate-source.js +133 -0
  108. package/dist/lib/walkable.d.ts +104 -0
  109. package/dist/lib/walkable.js +140 -0
  110. package/package.json +37 -0
@@ -0,0 +1,374 @@
1
+ import { poiKey } from './diagnostics.js';
2
+ import { boundsOf } from './geom/primitives.js';
3
+ import { ENTRANCE_REACH } from './openings.js';
4
+ /** How many names a message spells out before it counts the rest. */
5
+ const NAMES_SHOWN = 8;
6
+ /** Why a floor has no floor, in words. */
7
+ const FLOOR_CAUSES = {
8
+ 'layer-no-walls': {
9
+ cause: 'no-walls',
10
+ text: 'the floor has no walls, so there is nothing to build routes inside',
11
+ },
12
+ 'layer-no-outer-contour': {
13
+ cause: 'outline-open',
14
+ text: 'the outer walls do not close into a contour. Check the outer walls of this floor',
15
+ },
16
+ 'floor-carved-away': {
17
+ cause: 'carved-away',
18
+ text: 'the walls and POI drawn on it cover it whole',
19
+ },
20
+ 'floor-too-large': {
21
+ cause: 'too-large',
22
+ text: "it spans more than routes are built over. Check the plan's units, and any wall or POI drawn far from the hall",
23
+ },
24
+ };
25
+ /** Why a POI is not connected, in words — for one POI or for many. */
26
+ const POI_CAUSES = {
27
+ 'coordinate-not-finite': (one) => `${they(one)} ${one ? 'has' : 'have'} a coordinate that is not a number`,
28
+ 'footprint-empty': (one) => `${they(one)} ${one ? 'has' : 'have'} no outline the plan can read`,
29
+ 'dock-empty': () => 'the rectangle routes arrive at has no area',
30
+ 'outside-building': (one) => `${they(one)} ${one ? 'stands' : 'stand'} outside the building outline; routes are built inside it only`,
31
+ 'entrance-far-from-walls': (one) => `${they(one)} ${one ? 'stands' : 'stand'} more than ${ENTRANCE_REACH} m from the nearest wall; an entrance outside the building is connected only within ${ENTRANCE_REACH} m of a wall`,
32
+ 'no-floor-behind-wall': (one) => `no wall near ${one ? 'it' : 'them'} opens onto floor; check that the building outline closes and that nothing stands right behind the wall`,
33
+ 'no-floor-in-front': (one) => `there is no floor to stand on in front of ${one ? 'it' : 'them'}`,
34
+ 'no-way-in': (one) => `${they(one)} ${one ? 'has' : 'have'} no open side and no doorway`,
35
+ 'approach-blocked': () => 'a wall or another POI stands right in front of every way in',
36
+ 'cut-off': (one) => `the floor in front of ${one ? 'it' : 'them'} is a pocket no route reaches`,
37
+ 'lost-while-tidying': (one) => `${they(one)} ${one ? 'was' : 'were'} attached, then lost while the routes were tidied — reason unknown`,
38
+ unknown: () => 'reason unknown',
39
+ };
40
+ /**
41
+ * The pronoun for the POI a message is about.
42
+ * @param one True for a single POI.
43
+ * @returns `it` or `they`.
44
+ */
45
+ function they(one) {
46
+ return one ? 'it' : 'they';
47
+ }
48
+ /** The network stage's reason, as the report's cause. */
49
+ const REASON_CAUSE = {
50
+ 'no-floor': 'no-floor-in-front',
51
+ 'no-way-in': 'no-way-in',
52
+ 'way-in-blocked': 'approach-blocked',
53
+ 'cut-off': 'cut-off',
54
+ detached: 'lost-while-tidying',
55
+ };
56
+ /** The order problems are listed in: whole-plan first, then by what a designer fixes first. */
57
+ const KIND_ORDER = [
58
+ 'plan-invalid',
59
+ 'nothing-to-build',
60
+ 'floor-not-built',
61
+ 'poi-not-connected',
62
+ 'network-split',
63
+ 'floors-not-connected',
64
+ ];
65
+ /**
66
+ * A floor by the name the plan gave it.
67
+ * @param layer The layer id.
68
+ * @param start True at the start of a sentence.
69
+ * @returns `Floor "BG"`, or `the floor` for the one unnamed floor.
70
+ */
71
+ function floorName(layer, start = true) {
72
+ const word = start ? 'Floor' : 'floor';
73
+ if (layer === '')
74
+ return start ? 'The floor' : 'the floor';
75
+ return `${word} "${layer}"`;
76
+ }
77
+ /**
78
+ * Names as a sentence tail: the first few in full, the rest counted.
79
+ * @param refs The POI.
80
+ * @returns `A, B and 3 more`.
81
+ */
82
+ function names(refs) {
83
+ const shown = refs.slice(0, NAMES_SHOWN).map((ref) => (ref.id === '' ? 'unnamed POI' : ref.id));
84
+ const rest = refs.length - shown.length;
85
+ if (rest > 0)
86
+ return `${shown.join(', ')} and ${rest} more`;
87
+ return shown.join(', ');
88
+ }
89
+ /**
90
+ * Fold the compile's facts into what a designer reads: every problem is a diagnostic or a binding, grouped.
91
+ * @param diagnostics Every fact of the compile.
92
+ * @param reachability What the emitted graph connects.
93
+ * @param source The plan in plan units, for the outlines a problem points at.
94
+ * @returns The report.
95
+ */
96
+ export function buildReport(diagnostics, reachability, source) {
97
+ const problems = [];
98
+ const has = (code) => diagnostics.filter((d) => d.code === code);
99
+ const outline = (ref) => {
100
+ const destination = source.layers.find((l) => l.layer === ref.layer)?.destinations[ref.index];
101
+ const ring = destination?.dock ?? destination?.polygon ?? [];
102
+ // A ring with a NaN vertex would put NaN bounds into serializable data.
103
+ if (!ring.length || !ring.every(([x, y]) => Number.isFinite(x) && Number.isFinite(y)))
104
+ return [];
105
+ const [x0, y0, x1, y1] = boundsOf(ring);
106
+ return [{ type: 'bounds', layer: ref.layer, box: [x0, y0, x1, y1] }];
107
+ };
108
+ // The plan cannot be read at all: an input error that names no layer poisons the whole of it.
109
+ for (const d of diagnostics.filter((f) => f.stage === 'input' && f.severity === 'error' && f.layer === undefined)) {
110
+ const messages = {
111
+ 'units-per-meter-invalid': `The plan's units per meter is ${String(source.unitsPerMeter)}; it must be a positive number. Check the plan's units and map anchors.`,
112
+ };
113
+ problems.push({
114
+ kind: 'plan-invalid',
115
+ cause: d.code,
116
+ message: messages[d.code] ?? 'A floor has no id; the plan cannot be built.',
117
+ poi: [],
118
+ highlight: [],
119
+ });
120
+ }
121
+ const empty = source.layers.length === 0 ||
122
+ source.layers.every((l) => l.walls.length === 0 && l.destinations.length === 0);
123
+ if (!problems.length && empty)
124
+ problems.push({
125
+ kind: 'nothing-to-build',
126
+ cause: source.layers.length === 0 ? 'no-floors' : 'nothing-drawn',
127
+ message: source.layers.length === 0
128
+ ? 'Nothing to build: the plan has no floors.'
129
+ : 'Nothing to build: the plan has no walls and no POI.',
130
+ poi: [],
131
+ highlight: [],
132
+ });
133
+ // An unreadable or empty plan reports only that; everything after would repeat it.
134
+ if (problems.length)
135
+ return notBuilt(problems, source.layers.reduce((n, layer) => n + layer.destinations.length, 0));
136
+ // A floor that got no floor: one problem for the floor, none for each POI standing on it.
137
+ const unbuilt = new Set();
138
+ // A floor whose walls cannot be read as numbers is skipped whole; the rest of the plan compiles.
139
+ for (const d of diagnostics.filter((f) => f.stage === 'input' && f.severity === 'error' && f.layer !== undefined)) {
140
+ const layer = d.layer ?? '';
141
+ const messages = {
142
+ 'wall-coordinate-not-finite': `${floorName(layer)}: a wall has a coordinate that is not a number; the floor is not built.`,
143
+ };
144
+ unbuilt.add(layer);
145
+ problems.push({
146
+ kind: 'plan-invalid',
147
+ layer,
148
+ cause: d.code,
149
+ message: messages[d.code] ?? `${floorName(layer)}: its data cannot be read; the floor is not built.`,
150
+ poi: [],
151
+ highlight: [],
152
+ });
153
+ }
154
+ for (const d of diagnostics.filter((f) => f.stage === 'walkable' && FLOOR_CAUSES[f.code])) {
155
+ const layer = d.layer ?? '';
156
+ const { cause, text } = FLOOR_CAUSES[d.code];
157
+ const count = Number(d.facts?.destinations ?? 0);
158
+ unbuilt.add(layer);
159
+ problems.push({
160
+ kind: 'floor-not-built',
161
+ layer,
162
+ cause,
163
+ message: `${floorName(layer)}: no floor could be built — ${text}.${count && cause === 'no-walls'
164
+ ? ` ${count} POI ${count === 1 ? 'stands' : 'stand'} on it.`
165
+ : ''}`,
166
+ poi: [],
167
+ highlight: [],
168
+ });
169
+ }
170
+ // POI bound to nothing, grouped by floor and their proven cause.
171
+ const flagged = (code) => new Set(has(code).map(({ ref }) => poiKey(ref?.layer ?? '', ref?.index ?? -1)));
172
+ const notFinite = flagged('destination-coordinate-not-finite');
173
+ const dockEmpty = flagged('destination-dock-empty');
174
+ const footprintEmpty = flagged('destination-footprint-empty');
175
+ // One pass over the network stage's facts: the proven cause, and the tip to point at.
176
+ const unreachedOf = new Map(has('destination-unreached').map(({ ref, facts, geometry }) => [
177
+ poiKey(ref?.layer ?? '', ref?.index ?? -1),
178
+ {
179
+ cause: facts?.reason === 'no-floor' && facts.outside === true
180
+ ? facts.floorBehind === false
181
+ ? 'no-floor-behind-wall'
182
+ : typeof facts.nearestWall === 'number'
183
+ ? 'entrance-far-from-walls'
184
+ : 'outside-building'
185
+ : (REASON_CAUSE[String(facts?.reason)] ?? 'unknown'),
186
+ geometry: geometry ?? [],
187
+ },
188
+ ]));
189
+ const unconnected = new Map();
190
+ for (const { ref, part } of reachability.poi) {
191
+ if (part >= 0 || unbuilt.has(ref.layer))
192
+ continue;
193
+ const at = poiKey(ref.layer, ref.index);
194
+ const cause = notFinite.has(at)
195
+ ? 'coordinate-not-finite'
196
+ : dockEmpty.has(at)
197
+ ? 'dock-empty'
198
+ : footprintEmpty.has(at)
199
+ ? 'footprint-empty'
200
+ : (unreachedOf.get(at)?.cause ?? 'unknown');
201
+ const group = `${ref.layer}\u0000${cause}`;
202
+ const entry = unconnected.get(group) ?? { layer: ref.layer, cause, poi: [] };
203
+ entry.poi.push(ref);
204
+ unconnected.set(group, entry);
205
+ }
206
+ for (const { layer, cause, poi } of unconnected.values()) {
207
+ const one = poi.length === 1;
208
+ problems.push({
209
+ kind: 'poi-not-connected',
210
+ layer,
211
+ cause,
212
+ message: `${floorName(layer)}: ${poi.length} POI ${one ? 'is' : 'are'} not connected to the routes — ${POI_CAUSES[cause](one)}: ${names(poi)}.`,
213
+ poi,
214
+ // The POI's own rectangle, and the stub tip where the failing way in ends, when one is known.
215
+ highlight: poi.flatMap((ref) => [
216
+ ...outline(ref),
217
+ ...(unreachedOf.get(poiKey(ref.layer, ref.index))?.geometry ?? []),
218
+ ]),
219
+ });
220
+ }
221
+ // POI on a part the main one does not reach, one problem per floor.
222
+ const detached = new Map();
223
+ const splits = [];
224
+ // Floors whose network stage proved the refusal: the ties tried every link and gave up.
225
+ const untied = new Set(has('network-pieces-untied').map(({ layer }) => layer));
226
+ for (const { ref, part } of reachability.poi)
227
+ if (part > 0) {
228
+ const list = detached.get(ref.layer);
229
+ if (list)
230
+ list.push(ref);
231
+ else
232
+ detached.set(ref.layer, [ref]);
233
+ }
234
+ for (const [layer, poi] of detached) {
235
+ const onFloor = reachability.parts.filter((part) => part.layers.includes(layer));
236
+ const others = reachability.parts.slice(1).filter((part) => part.layers.includes(layer));
237
+ const lines = [
238
+ { type: 'lines', layer, lines: others.flatMap((part) => part.lines) },
239
+ ];
240
+ // The main part never reaches this floor: its routes are whole, but no shaft joins them to the plan.
241
+ if (onFloor[0] !== reachability.parts[0]) {
242
+ problems.push({
243
+ kind: 'floors-not-connected',
244
+ layer,
245
+ cause: 'floor-detached',
246
+ message: `${floorName(layer)}: its routes are not connected to the rest of the plan; no transition joins them. ${poi.length} POI ${poi.length === 1 ? 'is' : 'are'} cut off: ${names(poi)}.`,
247
+ poi,
248
+ highlight: lines,
249
+ });
250
+ continue;
251
+ }
252
+ splits.push({ layer, parts: onFloor.length });
253
+ // The proven cause needs both facts: the stage recorded the refusal, and the emitted graph
254
+ // still falls apart here — later passes may have joined what the ties could not.
255
+ const proven = untied.has(layer);
256
+ problems.push({
257
+ kind: 'network-split',
258
+ layer,
259
+ cause: proven ? 'no-link-found' : 'unknown',
260
+ message: `${floorName(layer)}: the routes fall into ${onFloor.length} separate parts${proven ? ': the growth found no lawful link and no floor path between the parts' : ''}. ${poi.length} POI ${poi.length === 1 ? 'is' : 'are'} on parts not connected to the rest: ${names(poi)}.`,
261
+ poi,
262
+ highlight: lines,
263
+ });
264
+ }
265
+ // Shafts that do not join the floors they serve.
266
+ for (const d of [
267
+ ...has('transition-kind-missing'),
268
+ ...has('transition-coordinate-not-finite'),
269
+ ...has('transition-too-few-stops'),
270
+ ...has('transition-stop-unknown-layer'),
271
+ ...has('transition-stop-no-dock'),
272
+ ...has('transition-single-floor'),
273
+ ]) {
274
+ const id = d.ref?.id ?? '';
275
+ const name = id ? `Transition "${id}"` : 'A transition';
276
+ const layer = d.layer ?? '';
277
+ const whole = d.code === 'transition-kind-missing' ||
278
+ d.code === 'transition-coordinate-not-finite' ||
279
+ d.code === 'transition-too-few-stops';
280
+ const message = d.code === 'transition-kind-missing'
281
+ ? `${name} names no kind (stairs, escalator, elevator…) and is left out.`
282
+ : d.code === 'transition-coordinate-not-finite'
283
+ ? `${name} has a stop whose coordinate is not a number and is left out.`
284
+ : d.code === 'transition-too-few-stops'
285
+ ? `${name} serves fewer than two floors.`
286
+ : d.code === 'transition-stop-unknown-layer'
287
+ ? `${name} names a floor the plan does not have: "${layer}".`
288
+ : d.code === 'transition-single-floor'
289
+ ? `${name} is connected to the routes of ${floorName(layer, false)} only, so it joins no floors.`
290
+ : `${name} does not reach the routes of ${floorName(layer, false)}${d.facts?.pointId ? ` (stop "${String(d.facts.pointId)}")` : ''}.`;
291
+ problems.push({
292
+ kind: 'floors-not-connected',
293
+ ...(whole ? {} : { layer }),
294
+ cause: d.code,
295
+ message,
296
+ poi: [],
297
+ highlight: d.geometry ?? [],
298
+ });
299
+ }
300
+ const layerOrder = new Map(source.layers.map((l, index) => [l.layer, index]));
301
+ problems.sort((a, b) => KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind) ||
302
+ (layerOrder.get(a.layer ?? '') ?? -1) - (layerOrder.get(b.layer ?? '') ?? -1));
303
+ const total = reachability.poi.length;
304
+ const connected = reachability.poi.filter(({ part }) => part >= 0).length;
305
+ const counts = {
306
+ poi: total,
307
+ poiConnected: connected,
308
+ poiNotConnected: total - connected,
309
+ poiOnDetachedParts: reachability.poi.filter(({ part }) => part > 0).length,
310
+ networkParts: reachability.parts.length,
311
+ floorsWithoutFloor: unbuilt.size,
312
+ };
313
+ const built = reachability.parts.some((part) => part.lines.length > 0);
314
+ const outcome = !built ? 'not-built' : problems.length ? 'built-with-gaps' : 'built';
315
+ return { outcome, summary: summarize(outcome, problems, counts, splits), problems, counts };
316
+ }
317
+ /**
318
+ * The report of a plan that cannot be read or has nothing on it.
319
+ * @param problems The one reason, already worded.
320
+ * @param poi How many POI the source lists.
321
+ * @returns The report.
322
+ */
323
+ function notBuilt(problems, poi) {
324
+ const counts = {
325
+ poi,
326
+ poiConnected: 0,
327
+ poiNotConnected: poi,
328
+ poiOnDetachedParts: 0,
329
+ networkParts: 0,
330
+ floorsWithoutFloor: 0,
331
+ };
332
+ return {
333
+ outcome: 'not-built',
334
+ summary: summarize('not-built', problems, counts, []),
335
+ problems,
336
+ counts,
337
+ };
338
+ }
339
+ /**
340
+ * One line for the whole plan.
341
+ * @param outcome What came of it.
342
+ * @param problems The grouped problems.
343
+ * @param counts The counts.
344
+ * @param splits The floors whose routes fall apart, with the number of parts on each.
345
+ * @returns The line.
346
+ */
347
+ function summarize(outcome, problems, counts, splits) {
348
+ if (outcome === 'not-built') {
349
+ const first = problems[0];
350
+ if (!first)
351
+ return 'Routes are not built.';
352
+ if (first.kind === 'nothing-to-build')
353
+ return first.message;
354
+ if (first.kind === 'plan-invalid')
355
+ return `Routes are not built: ${first.message}`;
356
+ if (first.kind === 'floor-not-built')
357
+ return 'Routes are not built: no floor could be built.';
358
+ return `Routes are not built: none of the ${counts.poi} POI could be connected.`;
359
+ }
360
+ if (outcome === 'built')
361
+ return counts.poi
362
+ ? `Routes are built: all ${counts.poi} POI are connected.`
363
+ : 'Routes are built.';
364
+ const gaps = [];
365
+ if (counts.poiNotConnected)
366
+ gaps.push(`${counts.poiNotConnected} of ${counts.poi} POI are not connected`);
367
+ for (const { layer, parts } of splits)
368
+ gaps.push(`the routes on ${floorName(layer, false)} fall into ${parts} parts`);
369
+ if (counts.floorsWithoutFloor)
370
+ gaps.push(`${counts.floorsWithoutFloor} floor(s) could not be built`);
371
+ if (problems.some(({ kind }) => kind === 'floors-not-connected'))
372
+ gaps.push('some floors are not connected');
373
+ return `Routes are built, with gaps: ${gaps.join(', and ')}.`;
374
+ }
@@ -0,0 +1,111 @@
1
+ import { type Frame } from './geom/frames.js';
2
+ import type { Segment, Vec2 } from './geom/types.js';
3
+ import type { MaskCtx } from './mask.js';
4
+ import { type NetworkSizes } from './network-sizes.js';
5
+ /** The floor read along one direction: its frame and the mask in frame coordinates. */
6
+ export interface Grid {
7
+ /** The direction. */
8
+ readonly frame: Frame;
9
+ /** Mask in the frame's coordinates. */
10
+ readonly ctx: MaskCtx;
11
+ }
12
+ /**
13
+ * Four-heading walk over the grids, cells numbered through all of them; stepping onto the same spot of
14
+ * another grid costs a turn.
15
+ */
16
+ export interface Router {
17
+ /** Cell count over all grids. */
18
+ readonly cells: number;
19
+ /** 1 for a walkable cell. */
20
+ readonly ok: Uint8Array;
21
+ /** Cell size, meters. */
22
+ readonly pitch: number;
23
+ /**
24
+ * Cheapest path from any seed to any target.
25
+ * @param seeds Start cells.
26
+ * @param isTarget 1 for each target cell.
27
+ * @returns Cells from the target back to its seed, or null.
28
+ */
29
+ route(seeds: Iterable<number>, isTarget: Uint8Array): number[] | null;
30
+ /**
31
+ * One growing search: each reached target group joins the tree and the search continues from it.
32
+ * @param seeds Initial tree cells; may be empty.
33
+ * @param group Target group per cell, -1 for none; the reached cell is cleared here.
34
+ * @param reseed Cells to start a new tree from once nothing is reachable; null to stop.
35
+ * @param onJoin Gets the path (target first) and the group; returns other cells joining with it, their
36
+ * `group` cleared. Must not search the router (shared state).
37
+ * @returns Every cell of every tree.
38
+ */
39
+ grow(seeds: Iterable<number>, group: Int32Array, reseed: () => Iterable<number> | null, onJoin: (cells: number[], group: number) => Iterable<number>): Set<number>;
40
+ /**
41
+ * Neighbor of `c` in direction `nd` on its grid.
42
+ * @param c The cell.
43
+ * @param nd Direction, 0…3.
44
+ * @returns The neighbor, or -1 when blocked or off the grid.
45
+ */
46
+ stepTo(c: number, nd: number): number;
47
+ /**
48
+ * A point's cell on each grid that covers it.
49
+ * @param x Point x.
50
+ * @param y Point y.
51
+ * @returns The cells.
52
+ */
53
+ cellsOf(x: number, y: number): number[];
54
+ /**
55
+ * A point's cell on the grid nearest a direction.
56
+ * @param x Point x.
57
+ * @param y Point y.
58
+ * @param dx Direction x.
59
+ * @param dy Direction y.
60
+ * @returns The cell, or -1 off that grid.
61
+ */
62
+ cellOf(x: number, y: number, dx: number, dy: number): number;
63
+ /**
64
+ * A point's cell on a grid of a frame.
65
+ * @param x Point x.
66
+ * @param y Point y.
67
+ * @param frame One of the grids' frames.
68
+ * @returns The cell, or -1 off every grid of that frame.
69
+ */
70
+ cellIn(x: number, y: number, frame: Frame): number;
71
+ /**
72
+ * Frame of a cell's grid.
73
+ * @param c The cell.
74
+ * @returns Its frame.
75
+ */
76
+ frameOf(c: number): Frame;
77
+ /**
78
+ * Center of a cell.
79
+ * @param c The cell.
80
+ * @returns The center, rounded to centimeters.
81
+ */
82
+ worldOf(c: number): Vec2;
83
+ /**
84
+ * Cells a segment passes through, on every grid or only on its own direction's (every grid when its
85
+ * own does not reach it).
86
+ * @param x0 Start x.
87
+ * @param y0 Start y.
88
+ * @param x1 End x.
89
+ * @param y1 End y.
90
+ * @param own Only the segment's own direction.
91
+ * @returns The cells, grid by grid, in order along the segment.
92
+ */
93
+ cellsAlong(x0: number, y0: number, x1: number, y1: number, own?: boolean): number[];
94
+ /**
95
+ * A cell path as segments between centers. A step between grids is laid through the corner where the
96
+ * arriving and leaving lines meet, when the floor there is free.
97
+ * @param cells The path.
98
+ * @returns Segments in path order, each at least a centimeter.
99
+ */
100
+ segmentsAlong(cells: readonly number[]): Segment[];
101
+ }
102
+ /**
103
+ * Build the router. Per grid: coarse cells free when any mask cell is, seam flags so a thin wall still
104
+ * blocks a step, and a cost that keeps off walls. A roomy cell may step onto another grid at the cost of a
105
+ * turn; along an aisle its own grid runs straight while the others zigzag, so the walk picks it naturally.
106
+ * @param grids The floor per direction, plan axes first; a direction may have several grids.
107
+ * @param cfg Network sizes.
108
+ * @returns The router.
109
+ */
110
+ export declare function makeRouter(grids: readonly Grid[], cfg: NetworkSizes): Router;
111
+ //# sourceMappingURL=router.d.ts.map