@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,72 @@
1
+ import type { Vec2 } from './geom/types.js';
2
+ import type { RouteNetwork } from './graph-types.js';
3
+ import type { SourceTransition, SourceTransitionPoint, WayfindingLayerSource, WayfindingSource, WfV1Graph } from './types.js';
4
+ /** A stop that never reached the graph, reported per point. */
5
+ export interface TransitionOrphan {
6
+ /** The transition the stop belongs to, when the host gave it an id. */
7
+ readonly transitionId?: string;
8
+ /** The host's own label for the stop. */
9
+ readonly pointId?: string;
10
+ /** Layer the stop claims. */
11
+ readonly layer: string;
12
+ /** Stop x, in plan units. */
13
+ readonly x: number;
14
+ /** Stop y, in plan units. */
15
+ readonly y: number;
16
+ /** `unknown-layer` — no such layer in the source; `no-dock` — the footprint got no docking stub. */
17
+ readonly reason: 'unknown-layer' | 'no-dock';
18
+ }
19
+ /** One stop and the destination whose dock it rides in on. */
20
+ interface PlannedStop {
21
+ readonly transition: SourceTransition;
22
+ readonly point: SourceTransitionPoint;
23
+ /** The destination carrying the dock: an existing one, or a pad stood up for this stop. */
24
+ readonly destinationId: string;
25
+ /** True when the stop stands in the open and got a pad of its own. */
26
+ readonly pad: boolean;
27
+ /** Other destinations the stop also stands in; it rides in on the first. */
28
+ readonly alsoIn: readonly string[];
29
+ }
30
+ /** A shaft whose attached stops all stand on one floor: it joins nothing after docking. */
31
+ export interface SingleFloorTransition {
32
+ /** The shaft. */
33
+ readonly transition: SourceTransition;
34
+ /** The one layer its attached stops stand on. */
35
+ readonly layer: string;
36
+ /** The docked stops' tips on that layer, in meters. */
37
+ readonly at: readonly Vec2[];
38
+ }
39
+ /** What emitting the shafts found out. */
40
+ interface TransitionEmit {
41
+ /** Stops that reached no dock, besides those {@link planTransitions} already rejected. */
42
+ readonly orphans: readonly TransitionOrphan[];
43
+ /** Shafts left with attached stops on one floor only. */
44
+ readonly singleFloor: readonly SingleFloorTransition[];
45
+ }
46
+ /** The layers to compile plus the stops to wire up once their networks exist. */
47
+ export interface TransitionPlan {
48
+ /** Layer sources, with a footprint added wherever a stop landed in the open. */
49
+ readonly layers: readonly WayfindingLayerSource[];
50
+ /** Stops that found a layer, in source order. */
51
+ readonly stops: readonly PlannedStop[];
52
+ /** Stops that will never reach the graph. */
53
+ readonly orphans: readonly TransitionOrphan[];
54
+ }
55
+ /**
56
+ * How every stop reaches its floor's network, decided before the layers compile. A stop inside an existing
57
+ * footprint rides in on its dock; a stop in the open gets a crossable pad, docked like any destination.
58
+ * @param source The plan, in meters, with its transitions.
59
+ * @returns The layers to compile, the stops to wire up, and the stops that cannot be wired.
60
+ */
61
+ export declare function planTransitions(source: WayfindingSource): TransitionPlan;
62
+ /**
63
+ * Emit each shaft as a clique over its stops, appending into `out`: one edge per pair of stops on different
64
+ * layers, so floor 1 to 3 is a single edge. Pairs on one layer are walking, not a shaft, and are skipped.
65
+ * @param plan The stops resolved by {@link planTransitions}.
66
+ * @param networks The compiled route network per layer.
67
+ * @param out The graph being built; mutated.
68
+ * @returns Stops that reached no dock, and shafts whose attached stops stand on one floor only.
69
+ */
70
+ export declare function emitTransitionEdges(plan: TransitionPlan, networks: ReadonlyMap<string, RouteNetwork>, out: WfV1Graph): TransitionEmit;
71
+ export {};
72
+ //# sourceMappingURL=transitions.d.ts.map
@@ -0,0 +1,191 @@
1
+ // Vertical connections: a transition is a shaft with one stop per floor it serves, and every pair of stops on
2
+ // different layers becomes one graph edge, priced by the runtime from its `kind`. Walking between two shafts
3
+ // on one floor is the route network's job.
4
+ import { line, point } from './emit-v1.js';
5
+ import { snapCm } from './geom/point-key.js';
6
+ import { pointInRing, signedArea } from './geom/primitives.js';
7
+ /**
8
+ * Half-side, in meters, of the pad stood up for a stop in the open: a 2 m square, about a lift cabin. Its
9
+ * faces are long enough to pass the network's face filter and let the stub line up with the aisle.
10
+ */
11
+ const PAD_METERS = 1;
12
+ /**
13
+ * The destinations a stop stands in — a lift is usually drawn as a POI, and may stand in a hall drawn
14
+ * as one too.
15
+ * @param destinations The layer's destinations, in canonical order.
16
+ * @param x Stop x.
17
+ * @param y Stop y.
18
+ * @returns Every destination whose dock or outline holds the stop, the smallest first; canonical
19
+ * order between equals.
20
+ */
21
+ function footprintsAt(destinations, x, y) {
22
+ return destinations
23
+ .filter((d) => (d.dock !== undefined && pointInRing(d.dock, [x, y])) || pointInRing(d.polygon, [x, y]))
24
+ .map((d, index) => ({ d, area: Math.abs(signedArea(d.polygon)), index }))
25
+ .sort((a, b) => a.area - b.area || a.index - b.index)
26
+ .map(({ d }) => d);
27
+ }
28
+ /**
29
+ * A square footprint around a stop standing in the open, so the network docks into it like any other.
30
+ * @param x Stop x.
31
+ * @param y Stop y.
32
+ * @param pad Half-side.
33
+ * @returns The ring.
34
+ */
35
+ function padRing(x, y, pad) {
36
+ return [
37
+ [x - pad, y - pad],
38
+ [x + pad, y - pad],
39
+ [x + pad, y + pad],
40
+ [x - pad, y + pad],
41
+ ];
42
+ }
43
+ /**
44
+ * How every stop reaches its floor's network, decided before the layers compile. A stop inside an existing
45
+ * footprint rides in on its dock; a stop in the open gets a crossable pad, docked like any destination.
46
+ * @param source The plan, in meters, with its transitions.
47
+ * @returns The layers to compile, the stops to wire up, and the stops that cannot be wired.
48
+ */
49
+ export function planTransitions(source) {
50
+ // A transition that names no kind is left out; the validator reports it.
51
+ const transitions = (source.transitions ?? []).filter((transition) => transition.kind?.trim());
52
+ if (transitions.length === 0)
53
+ return { layers: source.layers, stops: [], orphans: [] };
54
+ const pad = PAD_METERS;
55
+ const added = new Map();
56
+ const stops = [];
57
+ const orphans = [];
58
+ for (const transition of transitions)
59
+ for (const point of transition.points) {
60
+ const layer = source.layers.find((candidate) => candidate.layer === point.layer);
61
+ if (!layer) {
62
+ orphans.push({
63
+ transitionId: transition.id,
64
+ pointId: point.id,
65
+ layer: point.layer,
66
+ x: point.x,
67
+ y: point.y,
68
+ reason: 'unknown-layer',
69
+ });
70
+ continue;
71
+ }
72
+ const [standing, ...alsoIn] = footprintsAt(layer.destinations, point.x, point.y);
73
+ if (standing) {
74
+ stops.push({
75
+ transition,
76
+ point,
77
+ destinationId: standing.id,
78
+ pad: false,
79
+ alsoIn: alsoIn.map((d) => d.id),
80
+ });
81
+ continue;
82
+ }
83
+ // Named by where it stands, so ids never collide and the order of the transitions changes
84
+ // nothing; shafts stopping on one spot share the pad.
85
+ const id = `transition:${point.layer}:${snapCm(point.x)},${snapCm(point.y)}`;
86
+ const list = added.get(point.layer) ?? [];
87
+ // Terminal: a shaft is entered the way its doors face, so one connection is the whole approach.
88
+ if (!list.some((d) => d.id === id)) {
89
+ const ring = padRing(point.x, point.y, pad);
90
+ list.push({ id, polygon: ring, dock: ring, crossable: true, terminal: true });
91
+ }
92
+ added.set(point.layer, list);
93
+ stops.push({ transition, point, destinationId: id, pad: true, alsoIn: [] });
94
+ }
95
+ const layers = source.layers.map((layer) => {
96
+ const extra = added.get(layer.layer);
97
+ if (!extra)
98
+ return layer;
99
+ return { ...layer, destinations: [...layer.destinations, ...extra] };
100
+ });
101
+ return { layers, stops, orphans };
102
+ }
103
+ /**
104
+ * The dock tip of the stop's destination closest to the stop.
105
+ * @param network The layer's route network.
106
+ * @param destinationId The destination the stop rides in on.
107
+ * @param x Stop x.
108
+ * @param y Stop y.
109
+ * @returns The tip, or null when the destination has no dock.
110
+ */
111
+ function dockNear(network, destinationId, x, y) {
112
+ let best = null;
113
+ let bestDistance = Infinity;
114
+ for (const dock of network.docks) {
115
+ if (dock.id !== destinationId)
116
+ continue;
117
+ const distance = (dock.to[0] - x) ** 2 + (dock.to[1] - y) ** 2;
118
+ if (distance < bestDistance) {
119
+ bestDistance = distance;
120
+ best = dock.to;
121
+ }
122
+ }
123
+ return best;
124
+ }
125
+ /**
126
+ * One edge of a shaft's clique, priced by the runtime from its kind.
127
+ * @param transition The shaft.
128
+ * @param p0 One stop.
129
+ * @param p1 The other stop.
130
+ * @returns The edge.
131
+ */
132
+ function transitionLine(transition, p0, p1) {
133
+ // `virtual` is how the v1 runtime tells a floor change from a walk; a reader of `kind` ignores it.
134
+ return {
135
+ ...line(p0, p1),
136
+ virtual: true,
137
+ unaccessible: transition.accessible === false,
138
+ kind: transition.kind,
139
+ ...(transition.id != null ? { transitionId: transition.id } : {}),
140
+ };
141
+ }
142
+ /**
143
+ * Emit each shaft as a clique over its stops, appending into `out`: one edge per pair of stops on different
144
+ * layers, so floor 1 to 3 is a single edge. Pairs on one layer are walking, not a shaft, and are skipped.
145
+ * @param plan The stops resolved by {@link planTransitions}.
146
+ * @param networks The compiled route network per layer.
147
+ * @param out The graph being built; mutated.
148
+ * @returns Stops that reached no dock, and shafts whose attached stops stand on one floor only.
149
+ */
150
+ export function emitTransitionEdges(plan, networks, out) {
151
+ const orphans = [];
152
+ const singleFloor = [];
153
+ const attached = new Map();
154
+ for (const stop of plan.stops) {
155
+ const network = networks.get(stop.point.layer);
156
+ const tip = network ? dockNear(network, stop.destinationId, stop.point.x, stop.point.y) : null;
157
+ if (!tip) {
158
+ orphans.push({
159
+ transitionId: stop.transition.id,
160
+ pointId: stop.point.id,
161
+ layer: stop.point.layer,
162
+ x: stop.point.x,
163
+ y: stop.point.y,
164
+ reason: 'no-dock',
165
+ });
166
+ continue;
167
+ }
168
+ const list = attached.get(stop.transition) ?? [];
169
+ list.push(point(tip[0], tip[1], stop.point.layer));
170
+ attached.set(stop.transition, list);
171
+ }
172
+ const known = new Set(plan.layers.map((layer) => layer.layer));
173
+ for (const [transition, points] of attached) {
174
+ const layers = new Set(points.map((p) => p.layer));
175
+ // Floors the shaft was planned for among the plan's own: an unknown layer is its stop's own
176
+ // fact, not a floor this shaft could have joined.
177
+ const planned = new Set(transition.points.map((p) => p.layer).filter((layer) => known.has(layer)));
178
+ // Planned for several floors, docked on one: the shaft joins nothing.
179
+ if (layers.size === 1 && planned.size > 1)
180
+ singleFloor.push({
181
+ transition,
182
+ layer: points[0].layer,
183
+ at: points.map((p) => [p.x, p.y]),
184
+ });
185
+ for (let i = 0; i < points.length; i++)
186
+ for (let j = i + 1; j < points.length; j++)
187
+ if (points[i].layer !== points[j].layer)
188
+ out.lines.push(transitionLine(transition, points[i], points[j]));
189
+ }
190
+ return { orphans, singleFloor };
191
+ }
@@ -0,0 +1,126 @@
1
+ import type { Ring } from './geom/types.js';
2
+ /** A footprint a route must reach: obstacle, POI and connection point in one, booth-agnostic. */
3
+ export interface SourceDestination {
4
+ /** Stable identifier; labels the destination's docks and diagnostics. */
5
+ readonly id: string;
6
+ /** Footprint polygon in plan units; an obstacle unless {@link crossable}. */
7
+ readonly polygon: Ring;
8
+ /**
9
+ * The footprint as raw pieces (a mesh, sub-shapes) for a shape {@link polygon} cannot state.
10
+ * Replaces `polygon`: the outline is read off the pieces (`geom/footprint.ts`). Plan units,
11
+ * already positioned.
12
+ */
13
+ readonly parts?: readonly Ring[];
14
+ /**
15
+ * A CONVEX ring inside the footprint that route endpoints land in, for a host that identifies the
16
+ * destination by a rectangle of its own. Ways in are laid from its sides: a dock ends at a side's
17
+ * middle and runs along its outward normal to the footprint.
18
+ */
19
+ readonly dock?: Ring;
20
+ /** A route may pass THROUGH it (a bag check, a gate); it stays part of the walkable area. */
21
+ readonly crossable?: boolean;
22
+ /**
23
+ * ONE connection is the whole approach, from the face that reaches the network first (a toilet, a
24
+ * lift door). Otherwise every open face grows a stub.
25
+ */
26
+ readonly terminal?: boolean;
27
+ /**
28
+ * A way into the building: routes reach it through the wall it stands across, inside its bounds
29
+ * ({@link dock}, else {@link polygon}), and one outside the building through the nearest wall with
30
+ * floor behind it. The walls stay whole; the entrance's dock is the one line through them.
31
+ */
32
+ readonly entrance?: boolean;
33
+ }
34
+ /** One layer's (floor's) raw geometry input. */
35
+ export interface WayfindingLayerSource {
36
+ /** Globally unique layer id, carried onto every emitted point. */
37
+ readonly layer: string;
38
+ /** Wall polylines as flat `[x0, y0, x1, y1, …]` runs, raw; the compiler cleans them. */
39
+ readonly walls: readonly (readonly number[])[];
40
+ /** Footprints on this layer to route to. */
41
+ readonly destinations: readonly SourceDestination[];
42
+ }
43
+ /** Where a shaft opens onto one floor; bound to the floor's network by position. */
44
+ export interface SourceTransitionPoint {
45
+ /** The host's label for this stop, for diagnostics. */
46
+ readonly id?: string;
47
+ /** A {@link WayfindingLayerSource.layer}. */
48
+ readonly layer: string;
49
+ /** Horizontal coordinate in plan units. */
50
+ readonly x: number;
51
+ /** Vertical coordinate in plan units. */
52
+ readonly y: number;
53
+ }
54
+ /**
55
+ * One shaft: a lift serving three floors is one transition with three points, each pair linked.
56
+ * Points on the same layer are never linked; walking between them is the route network's job.
57
+ */
58
+ export interface SourceTransition {
59
+ /** Host identifier; labels emitted edges and diagnostics. */
60
+ readonly id?: string;
61
+ /** `'stairs' | 'escalator' | 'elevator' | …`; the routing policy prices the transition by it. */
62
+ readonly kind: string;
63
+ /** One point per floor served. */
64
+ readonly points: readonly SourceTransitionPoint[];
65
+ /** `false` excludes it from accessible-only routes. */
66
+ readonly accessible?: boolean;
67
+ }
68
+ /** Raw plan geometry the compiler consumes, one entry per layer. */
69
+ export interface WayfindingSource {
70
+ /** Per-layer geometry. */
71
+ readonly layers: readonly WayfindingLayerSource[];
72
+ /** Vertical connections between layers. */
73
+ readonly transitions?: readonly SourceTransition[];
74
+ /**
75
+ * Plan units per meter: `1` for meters, about `3.28` for feet, ten times that for tenths. Scales
76
+ * the compiler's thresholds; absent, the plan is read as meters.
77
+ */
78
+ readonly unitsPerMeter?: number;
79
+ }
80
+ /** A vertex of the emitted v1 graph. */
81
+ export interface WfV1Point {
82
+ /** Horizontal coordinate in plan units. */
83
+ readonly x: number;
84
+ /** Vertical coordinate in plan units. */
85
+ readonly y: number;
86
+ /** Globally unique layer id. */
87
+ readonly layer: string;
88
+ }
89
+ /** An edge of the emitted v1 graph. */
90
+ export interface WfV1Line {
91
+ /** Start vertex. */
92
+ readonly p0: WfV1Point;
93
+ /** End vertex. */
94
+ readonly p1: WfV1Point;
95
+ /**
96
+ * True on a transition edge: how the v1 runtime tells a floor change from a walk. A runtime that
97
+ * reads {@link kind} ignores it.
98
+ */
99
+ readonly virtual: boolean;
100
+ /** Excluded when routing accessible-only. */
101
+ readonly unaccessible: boolean;
102
+ /** One-way edge; always false. */
103
+ readonly unidirection: boolean;
104
+ /** `0`: the edge costs its length. */
105
+ readonly weight: number;
106
+ /**
107
+ * Transition edges only: `'stairs'`, `'escalator'` or `'elevator'`. A v1 extension the current
108
+ * runtime ignores; the v2 runtime prices the edge by it.
109
+ */
110
+ readonly kind?: string;
111
+ /** Transition edges only: the shaft this edge belongs to. A v1 extension, read like {@link kind}. */
112
+ readonly transitionId?: string;
113
+ }
114
+ /**
115
+ * The compiled graph in the shape of the v1 format; its lines are not cut where they meet, the
116
+ * converter that builds wf.data.js cuts them.
117
+ */
118
+ export interface WfV1Graph {
119
+ /** Route-network segments plus docks. */
120
+ readonly lines: WfV1Line[];
121
+ /**
122
+ * One point per dock, just inside its destination: a destination is reached through the ends inside its ring.
123
+ */
124
+ readonly lineEnds: WfV1Point[];
125
+ }
126
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ import type { SourceDestination, WayfindingSource } from './types.js';
2
+ /** What is wrong with the plan handed to the compiler, and where. */
3
+ export interface SourceIssue {
4
+ /**
5
+ * `'error'` leaves the plan unbuilt — or, when the issue names a layer, that floor alone;
6
+ * `'warning'` is reported and compiled around, leaving out the object it names when it must.
7
+ */
8
+ readonly severity: 'error' | 'warning';
9
+ /** Stable machine-readable code. */
10
+ readonly code: string;
11
+ /** What is wrong, in one sentence. */
12
+ readonly message: string;
13
+ /** The layer it belongs to, when it belongs to one. */
14
+ readonly layer?: string;
15
+ /** The id of the destination or transition, when the source gave one. */
16
+ readonly id?: string;
17
+ /** A destination's index in its layer's `destinations`, or a transition's in `transitions`. */
18
+ readonly index?: number;
19
+ }
20
+ /**
21
+ * Whether every ring of a destination is made of finite points. The compile leaves a destination
22
+ * failing this out; the validator reports it.
23
+ * @param destination The destination.
24
+ * @returns True when `polygon`, `parts` and `dock` hold no `NaN` or infinite coordinate.
25
+ */
26
+ export declare function destinationFinite(destination: SourceDestination): boolean;
27
+ /**
28
+ * Whether a layer's walls are made of finite numbers. The compile skips a floor failing this; the
29
+ * validator reports it.
30
+ * @param walls The layer's raw wall entries.
31
+ * @returns True when no coordinate is `NaN` or infinite.
32
+ */
33
+ export declare function wallsFinite(walls: readonly (readonly number[])[]): boolean;
34
+ /**
35
+ * Check the structure of a plan before it is compiled: ids, references between the parts, rings that
36
+ * enclose nothing, coordinates that are not finite numbers, and transitions that name no kind.
37
+ * @param source The plan to check.
38
+ * @returns Every issue found, errors and warnings together.
39
+ */
40
+ export declare function validateWayfindingSource(source: WayfindingSource): SourceIssue[];
41
+ //# sourceMappingURL=validate-source.d.ts.map
@@ -0,0 +1,133 @@
1
+ import { cleanRing } from './geom/normalize.js';
2
+ import { POINT_EPS } from './geom/point-key.js';
3
+ /** The fewest vertices a ring can enclose an area with. */
4
+ const MIN_RING_VERTICES = 3;
5
+ function issue(severity, code, message, where = {}) {
6
+ return {
7
+ severity,
8
+ code,
9
+ message,
10
+ ...(where.layer !== undefined && { layer: where.layer }),
11
+ ...(where.id !== undefined && { id: where.id }),
12
+ ...(where.index !== undefined && { index: where.index }),
13
+ };
14
+ }
15
+ /**
16
+ * Whether every vertex of a ring is a finite point.
17
+ * @param ring The ring.
18
+ * @returns True when no coordinate is `NaN` or infinite.
19
+ */
20
+ function finiteRing(ring) {
21
+ return ring.every(([x, y]) => Number.isFinite(x) && Number.isFinite(y));
22
+ }
23
+ /**
24
+ * Whether every ring of a destination is made of finite points. The compile leaves a destination
25
+ * failing this out; the validator reports it.
26
+ * @param destination The destination.
27
+ * @returns True when `polygon`, `parts` and `dock` hold no `NaN` or infinite coordinate.
28
+ */
29
+ export function destinationFinite(destination) {
30
+ return [destination.polygon, ...(destination.parts ?? []), destination.dock ?? []].every(finiteRing);
31
+ }
32
+ /**
33
+ * Whether a layer's walls are made of finite numbers. The compile skips a floor failing this; the
34
+ * validator reports it.
35
+ * @param walls The layer's raw wall entries.
36
+ * @returns True when no coordinate is `NaN` or infinite.
37
+ */
38
+ export function wallsFinite(walls) {
39
+ return walls.every((run) => run.every(Number.isFinite));
40
+ }
41
+ function checkLayers(layers, out) {
42
+ const known = new Set();
43
+ // An empty plan has nothing drawn yet and compiles to an empty graph.
44
+ if (!layers.length)
45
+ out.push(issue('warning', 'no-layers', 'the plan has no layers'));
46
+ for (const layer of layers) {
47
+ // `''` is a valid id (the single unnamed layer); only a missing id is an error.
48
+ if (layer.layer === undefined) {
49
+ out.push(issue('error', 'layer-id-missing', 'a layer has no id'));
50
+ continue;
51
+ }
52
+ // Duplicate ids still compile and their graphs merge, but the route network is kept per id, so a
53
+ // transition may dock into the wrong one.
54
+ if (known.has(layer.layer))
55
+ out.push(issue('warning', 'layer-id-duplicate', 'two layers share an id', { layer: layer.layer }));
56
+ known.add(layer.layer);
57
+ }
58
+ return known;
59
+ }
60
+ function checkDestination(destination, layer, index, eps, seen, out) {
61
+ const where = { layer, id: destination.id, index };
62
+ // A blank or repeated id is reported; the destination still docks by its geometry.
63
+ if (!destination.id) {
64
+ out.push(issue('warning', 'destination-id-missing', 'a destination has no id', { layer, index }));
65
+ }
66
+ else {
67
+ if (seen.has(destination.id))
68
+ out.push(issue('warning', 'destination-id-duplicate', 'two destinations share an id', where));
69
+ seen.add(destination.id);
70
+ }
71
+ // The rest of the plan compiles; this destination is left out and reported unconnected.
72
+ if (!destinationFinite(destination)) {
73
+ out.push(issue('warning', 'destination-coordinate-not-finite', 'a coordinate of the footprint is not a finite number', where));
74
+ return;
75
+ }
76
+ // As the compiler reads it: `parts` enclosing nothing fall back to the polygon, and a footprint with no
77
+ // shape left is a warning — the rest of the plan compiles.
78
+ const parts = [...(destination.parts ?? []), destination.polygon];
79
+ // Measured after the compiler's cleanup: repeated and collinear vertices removed.
80
+ const usable = parts.filter((ring) => cleanRing(ring, eps).length >= MIN_RING_VERTICES);
81
+ if (!usable.length)
82
+ out.push(issue('warning', 'destination-footprint-empty', 'the footprint encloses no area', where));
83
+ // A dock of no area (a zero-width rectangle) is kept: no route arrives in it, so the destination is
84
+ // reported unconnected.
85
+ if (destination.dock && cleanRing(destination.dock, eps).length < MIN_RING_VERTICES)
86
+ out.push(issue('warning', 'destination-dock-empty', 'the dock ring encloses no area', where));
87
+ }
88
+ /**
89
+ * Check the structure of a plan before it is compiled: ids, references between the parts, rings that
90
+ * enclose nothing, coordinates that are not finite numbers, and transitions that name no kind.
91
+ * @param source The plan to check.
92
+ * @returns Every issue found, errors and warnings together.
93
+ */
94
+ export function validateWayfindingSource(source) {
95
+ const issues = [];
96
+ const layers = checkLayers(source.layers, issues);
97
+ // The compiler cleans rings in meters, so the epsilon is scaled to plan units here.
98
+ const unit = source.unitsPerMeter;
99
+ const eps = POINT_EPS * (unit !== undefined && Number.isFinite(unit) && unit > 0 ? unit : 1);
100
+ // Ids are unique per layer — the scope the network docks in.
101
+ for (const layer of source.layers) {
102
+ const seen = new Set();
103
+ if (!wallsFinite(layer.walls))
104
+ issues.push(issue('error', 'wall-coordinate-not-finite', 'a wall coordinate is not a finite number', {
105
+ layer: layer.layer,
106
+ }));
107
+ layer.destinations.forEach((destination, index) => checkDestination(destination, layer.layer, index, eps, seen, issues));
108
+ }
109
+ for (const [index, transition] of (source.transitions ?? []).entries()) {
110
+ // The kind prices the shaft and tells it from a walk; without one it is left out.
111
+ if (!transition.kind?.trim()) {
112
+ issues.push(issue('warning', 'transition-kind-missing', 'a transition names no kind', {
113
+ id: transition.id,
114
+ }));
115
+ continue;
116
+ }
117
+ // The rest of the plan compiles; this transition is left out.
118
+ if (!finiteRing(transition.points.map(({ x, y }) => [x, y]))) {
119
+ issues.push(issue('warning', 'transition-coordinate-not-finite', 'a transition stop coordinate is not a finite number', { id: transition.id, index }));
120
+ continue;
121
+ }
122
+ const onKnownLayers = transition.points.filter((point) => layers.has(point.layer));
123
+ // Floors, not stops: two stops on one layer connect nothing and yield no edge.
124
+ if (new Set(onKnownLayers.map((point) => point.layer)).size < 2)
125
+ issues.push(issue('warning', 'transition-too-few-stops', 'a transition serves fewer than two floors', {
126
+ id: transition.id,
127
+ }));
128
+ }
129
+ if (source.unitsPerMeter !== undefined &&
130
+ !(Number.isFinite(source.unitsPerMeter) && source.unitsPerMeter > 0))
131
+ issues.push(issue('error', 'units-per-meter-invalid', 'unitsPerMeter must be a finite number above zero'));
132
+ return issues;
133
+ }
@@ -0,0 +1,104 @@
1
+ import type { BooleanOps } from './geom/boolean-ops.js';
2
+ import type { WallLine } from './geom/normalize.js';
3
+ import type { Ring, Segment, Vec2 } from './geom/types.js';
4
+ import { type PreparedWalls } from './geom/walls.js';
5
+ /**
6
+ * Wall half-thickness in meters. Square caps extend each jamb by it, so a doorway narrows by twice
7
+ * this.
8
+ */
9
+ export declare const WALL_HALF_WIDTH = 0.12;
10
+ /** One layer's raw geometry the walkable stage consumes. */
11
+ interface WalkableInput {
12
+ /** Wall polylines as flat `[x0, y0, x1, y1, …]` runs in meters, raw; cleaned internally. */
13
+ readonly walls: readonly (readonly number[])[];
14
+ /** Obstacle footprints to carve out, already cut back by {@link carveFootprints}. */
15
+ readonly obstacles: readonly Ring[];
16
+ }
17
+ /**
18
+ * Wall geometry already built for this layer, shared by the stages. Strips: same walls, same
19
+ * half-width.
20
+ */
21
+ interface SharedWalls {
22
+ /** The layer's walls, read once. */
23
+ readonly walls?: PreparedWalls;
24
+ /** The wall strips, from {@link wallStripsOf}. */
25
+ readonly strips?: readonly Ring[];
26
+ /** The outer contours, from {@link outerContoursOf}. */
27
+ readonly contours?: readonly Ring[];
28
+ }
29
+ /** The walkable area: a polygon with holes, plus overlay layers and stats. */
30
+ export interface WalkableArea {
31
+ /** Outer rings plus hole rings (obstacles, wall thickness). */
32
+ readonly rings: Ring[];
33
+ /** The building's outline: the outer contours joined, before carving. */
34
+ readonly outline: Ring[];
35
+ /** Union of the wall strips, for the overlay. */
36
+ readonly wallStrips: Ring[];
37
+ /** Cleaned wall centerlines, for the overlay. */
38
+ readonly wallLines: WallLine[];
39
+ /** Counts and walkable area (m²), for diagnostics. */
40
+ readonly stats: {
41
+ readonly walls: number;
42
+ readonly outerContours: number;
43
+ readonly rings: number;
44
+ /** Rounded to 0.1 m², for the overlay. */
45
+ readonly area: number;
46
+ /** As computed, so a tiny floor does not read as none; absent in artifacts built before it. */
47
+ readonly areaExact?: number;
48
+ };
49
+ }
50
+ /**
51
+ * Whether a destination stands on a piece of floor: its middle or a corner lies inside it, or a
52
+ * corner lies on its boundary.
53
+ * @param piece The piece of floor.
54
+ * @param ring The destination's ring.
55
+ * @returns True when the destination stands on it.
56
+ */
57
+ export declare function standsOn(piece: Ring, ring: Ring): boolean;
58
+ /**
59
+ * Whether a point is inside the building: inside its outline and not in a courtyard.
60
+ * @param outline The building's outline, from {@link WalkableArea.outline}.
61
+ * @param point The point.
62
+ * @returns True inside.
63
+ */
64
+ export declare function inBuilding(outline: readonly Ring[], point: Vec2): boolean;
65
+ /**
66
+ * The walls as strips wound positive, one per wall segment.
67
+ * @param ops Polygon boolean backend.
68
+ * @param segments The wall segments, loops' closing pieces included ({@link prepareWalls}).
69
+ * @param halfWidth Wall half-thickness in meters.
70
+ * @returns One strip per wall segment.
71
+ */
72
+ export declare function wallStripsOf(ops: BooleanOps, segments: readonly Segment[], halfWidth: number): Ring[];
73
+ /**
74
+ * Footprints cut back to the walls that bound them, in input order: the shapes the floor is carved
75
+ * with.
76
+ * @param obstacles The obstacle rings, in any winding.
77
+ * @param strips The wall strips, from {@link wallStripsOf}.
78
+ * @param ops Polygon boolean backend.
79
+ * @returns One ring per obstacle, in input order, and which of them the walls cut back.
80
+ */
81
+ export declare function carveFootprints(obstacles: readonly Ring[], strips: readonly Ring[], ops: BooleanOps): {
82
+ rings: Ring[];
83
+ clipped: boolean[];
84
+ };
85
+ /**
86
+ * The outer contours: the building outlines the walls draw, openings closed with no width limit. A
87
+ * bridge closing an outline borders the floor and is no wall.
88
+ * @param walls Cleaned wall centerlines with their closed/open flag.
89
+ * @returns The outlines, largest first, wound positive.
90
+ */
91
+ export declare function outerContoursOf(walls: readonly WallLine[]): Ring[];
92
+ /**
93
+ * Build one layer's walkable area:
94
+ *
95
+ * walkable = interior(outer contours) − ⋃(obstacles) − ⋃(wall strips)
96
+ * @param input One layer's walls (flat polylines) and obstacles from {@link carveFootprints}.
97
+ * @param ops Polygon boolean backend; defaults to clipper2, which is the only one there is.
98
+ * @param halfWidth Wall half-thickness in meters.
99
+ * @param shared The same walls already read and offset, when the caller holds them.
100
+ * @returns The walkable polygon with holes, plus diagnostic layers and stats.
101
+ */
102
+ export declare function buildWalkableArea(input: WalkableInput, ops?: BooleanOps, halfWidth?: number, shared?: SharedWalls): WalkableArea;
103
+ export {};
104
+ //# sourceMappingURL=walkable.d.ts.map