@expofp/wayfinding-compiler 0.0.0-experimental.3b0db16 → 0.0.0-stage

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 (100) hide show
  1. package/README.md +2 -445
  2. package/package.json +3 -34
  3. package/dist/index.d.ts +0 -14
  4. package/dist/index.js +0 -3
  5. package/dist/lib/attach.d.ts +0 -43
  6. package/dist/lib/attach.js +0 -199
  7. package/dist/lib/axes.d.ts +0 -76
  8. package/dist/lib/axes.js +0 -103
  9. package/dist/lib/blockers.d.ts +0 -134
  10. package/dist/lib/blockers.js +0 -289
  11. package/dist/lib/cell-array.d.ts +0 -14
  12. package/dist/lib/cell-array.js +0 -25
  13. package/dist/lib/compile-layer.d.ts +0 -41
  14. package/dist/lib/compile-layer.js +0 -241
  15. package/dist/lib/compile-wayfinding.d.ts +0 -90
  16. package/dist/lib/compile-wayfinding.js +0 -203
  17. package/dist/lib/connect.d.ts +0 -35
  18. package/dist/lib/connect.js +0 -206
  19. package/dist/lib/diagnostics.d.ts +0 -190
  20. package/dist/lib/diagnostics.js +0 -634
  21. package/dist/lib/doorways.d.ts +0 -78
  22. package/dist/lib/doorways.js +0 -331
  23. package/dist/lib/emit-network.d.ts +0 -47
  24. package/dist/lib/emit-network.js +0 -105
  25. package/dist/lib/emit-v1.d.ts +0 -31
  26. package/dist/lib/emit-v1.js +0 -56
  27. package/dist/lib/entries.d.ts +0 -123
  28. package/dist/lib/entries.js +0 -448
  29. package/dist/lib/floor-grids.d.ts +0 -48
  30. package/dist/lib/floor-grids.js +0 -185
  31. package/dist/lib/framing.d.ts +0 -20
  32. package/dist/lib/framing.js +0 -187
  33. package/dist/lib/geom/boolean-ops.d.ts +0 -21
  34. package/dist/lib/geom/boolean-ops.js +0 -1
  35. package/dist/lib/geom/clipper2-ops.d.ts +0 -7
  36. package/dist/lib/geom/clipper2-ops.js +0 -50
  37. package/dist/lib/geom/footprint.d.ts +0 -26
  38. package/dist/lib/geom/footprint.js +0 -203
  39. package/dist/lib/geom/frames.d.ts +0 -116
  40. package/dist/lib/geom/frames.js +0 -233
  41. package/dist/lib/geom/mesh-outline.d.ts +0 -13
  42. package/dist/lib/geom/mesh-outline.js +0 -90
  43. package/dist/lib/geom/normalize.d.ts +0 -28
  44. package/dist/lib/geom/normalize.js +0 -118
  45. package/dist/lib/geom/point-key.d.ts +0 -16
  46. package/dist/lib/geom/point-key.js +0 -15
  47. package/dist/lib/geom/primitives.d.ts +0 -105
  48. package/dist/lib/geom/primitives.js +0 -184
  49. package/dist/lib/geom/split-crossings.d.ts +0 -14
  50. package/dist/lib/geom/split-crossings.js +0 -171
  51. package/dist/lib/geom/types.d.ts +0 -9
  52. package/dist/lib/geom/types.js +0 -1
  53. package/dist/lib/geom/walls.d.ts +0 -31
  54. package/dist/lib/geom/walls.js +0 -71
  55. package/dist/lib/graph-invariants.d.ts +0 -49
  56. package/dist/lib/graph-invariants.js +0 -129
  57. package/dist/lib/graph-types.d.ts +0 -143
  58. package/dist/lib/graph-types.js +0 -7
  59. package/dist/lib/mask.d.ts +0 -54
  60. package/dist/lib/mask.js +0 -210
  61. package/dist/lib/min-heap.d.ts +0 -24
  62. package/dist/lib/min-heap.js +0 -73
  63. package/dist/lib/network-build.d.ts +0 -21
  64. package/dist/lib/network-build.js +0 -4
  65. package/dist/lib/network-graph.d.ts +0 -80
  66. package/dist/lib/network-graph.js +0 -467
  67. package/dist/lib/network-sizes.d.ts +0 -29
  68. package/dist/lib/network-sizes.js +0 -22
  69. package/dist/lib/network-store.d.ts +0 -173
  70. package/dist/lib/network-store.js +0 -430
  71. package/dist/lib/openings.d.ts +0 -54
  72. package/dist/lib/openings.js +0 -387
  73. package/dist/lib/place-path.d.ts +0 -47
  74. package/dist/lib/place-path.js +0 -346
  75. package/dist/lib/post-passes.d.ts +0 -31
  76. package/dist/lib/post-passes.js +0 -305
  77. package/dist/lib/reachability.d.ts +0 -58
  78. package/dist/lib/reachability.js +0 -220
  79. package/dist/lib/report.d.ts +0 -49
  80. package/dist/lib/report.js +0 -374
  81. package/dist/lib/router.d.ts +0 -111
  82. package/dist/lib/router.js +0 -388
  83. package/dist/lib/runs.d.ts +0 -92
  84. package/dist/lib/runs.js +0 -232
  85. package/dist/lib/scale.d.ts +0 -58
  86. package/dist/lib/scale.js +0 -185
  87. package/dist/lib/steiner-network.d.ts +0 -19
  88. package/dist/lib/steiner-network.js +0 -183
  89. package/dist/lib/strip.d.ts +0 -43
  90. package/dist/lib/strip.js +0 -200
  91. package/dist/lib/stubs.d.ts +0 -84
  92. package/dist/lib/stubs.js +0 -316
  93. package/dist/lib/transitions.d.ts +0 -72
  94. package/dist/lib/transitions.js +0 -191
  95. package/dist/lib/types.d.ts +0 -126
  96. package/dist/lib/types.js +0 -1
  97. package/dist/lib/validate-source.d.ts +0 -41
  98. package/dist/lib/validate-source.js +0 -133
  99. package/dist/lib/walkable.d.ts +0 -104
  100. package/dist/lib/walkable.js +0 -140
@@ -1,71 +0,0 @@
1
- import { cleanPolylineRuns } from './normalize.js';
2
- /**
3
- * Order two points by x, then by y.
4
- * @param a One point.
5
- * @param b The other.
6
- * @returns Negative when `a` comes first.
7
- */
8
- function comparePoints(a, b) {
9
- return a[0] - b[0] || a[1] - b[1];
10
- }
11
- /**
12
- * A run in canonical direction: open from its smaller end; closed from its smallest vertex towards the
13
- * smaller neighbor.
14
- * @param run The run.
15
- * @returns The run in canonical direction.
16
- */
17
- function canonicalRun(run) {
18
- const { points, closed } = run;
19
- if (!closed)
20
- return comparePoints(points[points.length - 1], points[0]) < 0
21
- ? { points: [...points].reverse(), closed }
22
- : run;
23
- let start = 0;
24
- for (let i = 1; i < points.length; i++)
25
- if (comparePoints(points[i], points[start]) < 0)
26
- start = i;
27
- const n = points.length;
28
- const forward = comparePoints(points[(start + 1) % n], points[(start + n - 1) % n]) <= 0;
29
- return { points: points.map((_, i) => points[(start + (forward ? i : n - i)) % n]), closed };
30
- }
31
- /**
32
- * Order two runs point by point; on a shared prefix, the shorter first, then the open one.
33
- * @param a One run.
34
- * @param b The other.
35
- * @returns Negative when `a` comes first.
36
- */
37
- function compareRuns(a, b) {
38
- for (let i = 0; i < a.points.length && i < b.points.length; i++) {
39
- const order = comparePoints(a.points[i], b.points[i]);
40
- if (order)
41
- return order;
42
- }
43
- return a.points.length - b.points.length || Number(a.closed) - Number(b.closed);
44
- }
45
- /**
46
- * Reads a layer's raw walls once for every stage. Runs are canonicalized and sorted, so wall order and
47
- * drawing direction do not change the network.
48
- * @param walls Raw walls as flat `[x0, y0, …]` runs.
49
- * @returns The runs and their segments.
50
- */
51
- export function prepareWalls(walls) {
52
- const cleaned = walls.map((flat) => cleanPolylineRuns(flat));
53
- const entries = cleaned
54
- .map((entryRuns, entry) => ({
55
- entry,
56
- runs: entryRuns.length,
57
- oddTail: walls[entry].length % 2 === 1,
58
- }))
59
- .filter(({ entry, runs, oddTail }) => runs > 1 || oddTail || (runs === 0 && walls[entry].length > 0));
60
- const runs = cleaned.flat().map(canonicalRun).sort(compareRuns);
61
- const segments = [];
62
- for (const run of runs) {
63
- const n = run.closed ? run.points.length : run.points.length - 1;
64
- for (let i = 0; i < n; i++) {
65
- const a = run.points[i];
66
- const b = run.points[(i + 1) % run.points.length];
67
- segments.push([a[0], a[1], b[0], b[1]]);
68
- }
69
- }
70
- return { runs, segments, entries };
71
- }
@@ -1,49 +0,0 @@
1
- import type { SourceDestination, WfV1Graph } from './types.js';
2
- type Seg = [number, number, number, number];
3
- type Pt = readonly [number, number];
4
- /** What a finding breaches. */
5
- type InvariantCode =
6
- /** A network segment or a dock crosses a wall of its layer. */
7
- 'line-crosses-wall'
8
- /** Two network segments cross, or one ends inside another, with no node at the meeting. */
9
- /** A segment or a dock passes through a solid footprint it is not serving. */
10
- | 'line-crosses-poi'
11
- /** A dock's tip lies inside no ring a destination is bound by. */
12
- | 'dock-outside-ring';
13
- /** One breach of a graph invariant, in plan units. */
14
- export interface InvariantFinding {
15
- /** What is breached. */
16
- readonly code: InvariantCode;
17
- /** The layer it is on. */
18
- readonly layer: string;
19
- /** Indices into `graph.lines` of the offending segments. */
20
- readonly lines: readonly number[];
21
- /** The dock's tip, when the offending segment is a dock. */
22
- readonly at?: Pt;
23
- }
24
- /**
25
- * Do two segments properly cross — endpoints and collinear touching excluded?
26
- * @param a One segment.
27
- * @param b The other.
28
- * @returns True when each passes strictly through the other.
29
- */
30
- export declare function crosses(a: Seg, b: Seg): boolean;
31
- /** What one layer carries into the checks, as `LayerDebug` already holds it. */
32
- export interface InvariantLayer {
33
- /** The layer id. */
34
- readonly layer: string;
35
- /** The wall polylines as given (flat `[x0, y0, …]`). */
36
- readonly rawWalls: readonly (readonly number[])[];
37
- /** The destinations as the stages saw them. */
38
- readonly destinations: readonly SourceDestination[];
39
- }
40
- /**
41
- * Checks the emitted graph against the geometric invariants, layer by layer. Reads only; computes
42
- * no geometry the checks themselves do not need.
43
- * @param graph The emitted graph, in plan units.
44
- * @param layers The layers' raw walls and destinations, as `LayerDebug` carries them.
45
- * @returns Every breach found, in layer order, walls → crossings → footprints → rings.
46
- */
47
- export declare function checkGraphInvariants(graph: WfV1Graph, layers: readonly InvariantLayer[]): InvariantFinding[];
48
- export {};
49
- //# sourceMappingURL=graph-invariants.d.ts.map
@@ -1,129 +0,0 @@
1
- // The geometric promises the emitted graph is held to, as pure functions over what the compile
2
- // already computed: no line through a wall, no line through a destination it is not serving, every
3
- // dock ending inside its ring. Lines may cross or end on one another: the converter that builds
4
- // wf.data.js cuts them there. The debug compile runs them.
5
- import { cleanPolylineRuns } from './geom/normalize.js';
6
- /**
7
- * Do two segments properly cross — endpoints and collinear touching excluded?
8
- * @param a One segment.
9
- * @param b The other.
10
- * @returns True when each passes strictly through the other.
11
- */
12
- export function crosses(a, b) {
13
- const side = (s, px, py) => Math.sign((s[2] - s[0]) * (py - s[1]) - (s[3] - s[1]) * (px - s[0]));
14
- const d1 = side(a, b[0], b[1]);
15
- const d2 = side(a, b[2], b[3]);
16
- const d3 = side(b, a[0], a[1]);
17
- const d4 = side(b, a[2], a[3]);
18
- return d1 !== d2 && d3 !== d4 && d1 !== 0 && d2 !== 0 && d3 !== 0 && d4 !== 0;
19
- }
20
- /**
21
- * Ray-cast even-odd point-in-ring test, matching the engine's own containment check.
22
- * @param ring The ring.
23
- * @param p The point.
24
- * @returns True inside.
25
- */
26
- function ringContains(ring, p) {
27
- let inside = false;
28
- for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
29
- const [xi, yi] = ring[i];
30
- const [xj, yj] = ring[j];
31
- if (yi > p[1] !== yj > p[1] && p[0] < ((xj - xi) * (p[1] - yi)) / (yj - yi) + xi)
32
- inside = !inside;
33
- }
34
- return inside;
35
- }
36
- /**
37
- * Every wall of a layer, segment by segment.
38
- * @param flats The layer's wall runs as flat coordinate lists.
39
- * @returns The segments.
40
- */
41
- function wallsOf(flats) {
42
- const walls = [];
43
- for (const flat of flats)
44
- for (const run of cleanPolylineRuns(flat)) {
45
- const points = run.closed ? [...run.points, run.points[0]] : run.points;
46
- for (let i = 0; i + 1 < points.length; i++)
47
- walls.push([points[i][0], points[i][1], points[i + 1][0], points[i + 1][1]]);
48
- }
49
- return walls;
50
- }
51
- /**
52
- * The edges of a ring.
53
- * @param ring The ring.
54
- * @returns One segment per edge.
55
- */
56
- function edgesOf(ring) {
57
- return ring.map((a, i) => {
58
- const b = ring[(i + 1) % ring.length];
59
- return [a[0], a[1], b[0], b[1]];
60
- });
61
- }
62
- /**
63
- * One floor as the engine sees it: its route lines, and its docks — the lines that end in a
64
- * `lineEnd` — each with its index into `graph.lines`.
65
- * @param graph The emitted graph.
66
- * @param layer The floor.
67
- * @returns The lines, split so.
68
- */
69
- function floorOf(graph, layer) {
70
- const nodeId = (x, y, l) => `${l}_${x}_${y}`;
71
- const ends = new Set(graph.lineEnds.map((end) => nodeId(end.x, end.y, end.layer)));
72
- const net = [];
73
- const docks = [];
74
- graph.lines.forEach(({ p0, p1 }, index) => {
75
- if (p0.layer !== layer || p1.layer !== layer)
76
- return;
77
- const seg = [p0.x, p0.y, p1.x, p1.y];
78
- if (ends.has(nodeId(p1.x, p1.y, layer)))
79
- docks.push({ seg, to: [p1.x, p1.y], index });
80
- else if (ends.has(nodeId(p0.x, p0.y, layer)))
81
- docks.push({ seg, to: [p0.x, p0.y], index });
82
- else
83
- net.push({ seg, index });
84
- });
85
- return { net, docks };
86
- }
87
- /**
88
- * Checks the emitted graph against the geometric invariants, layer by layer. Reads only; computes
89
- * no geometry the checks themselves do not need.
90
- * @param graph The emitted graph, in plan units.
91
- * @param layers The layers' raw walls and destinations, as `LayerDebug` carries them.
92
- * @returns Every breach found, in layer order, walls → crossings → footprints → rings.
93
- */
94
- export function checkGraphInvariants(graph, layers) {
95
- const out = [];
96
- for (const { layer, rawWalls, destinations } of layers) {
97
- const { net, docks } = floorOf(graph, layer);
98
- const walls = wallsOf(rawWalls);
99
- // An entrance's dock is the way through the wall it stands across or beside: it may cross one.
100
- const entrances = destinations
101
- .filter((d) => d.entrance && (d.dock ?? d.polygon).length >= 3)
102
- .map((d) => d.dock ?? d.polygon);
103
- for (const { seg, index } of net)
104
- if (walls.some((wall) => crosses(seg, wall)))
105
- out.push({ code: 'line-crosses-wall', layer, lines: [index] });
106
- for (const { seg, to, index } of docks)
107
- if (!entrances.some((ring) => ringContains(ring, to)) &&
108
- walls.some((wall) => crosses(seg, wall)))
109
- out.push({ code: 'line-crosses-wall', layer, lines: [index], at: to });
110
- const solid = destinations
111
- .filter((d) => !d.crossable && d.polygon.length >= 3)
112
- .map((d) => ({ id: d.id, edges: edgesOf(d.polygon) }));
113
- const ownersOf = (to) => destinations
114
- .filter((d) => (d.dock ?? d.polygon).length >= 3 && ringContains(d.dock ?? d.polygon, to))
115
- .map((d) => d.id);
116
- for (const { seg, index } of net)
117
- if (solid.some((poi) => poi.edges.some((edge) => crosses(seg, edge))))
118
- out.push({ code: 'line-crosses-poi', layer, lines: [index] });
119
- for (const { seg, to, index } of docks) {
120
- const owners = ownersOf(to);
121
- if (solid.some((poi) => !owners.includes(poi.id) && poi.edges.some((edge) => crosses(seg, edge))))
122
- out.push({ code: 'line-crosses-poi', layer, lines: [index], at: to });
123
- }
124
- for (const { to, index } of docks)
125
- if (!destinations.some((d) => (d.dock ?? d.polygon).length >= 3 && ringContains(d.dock ?? d.polygon, to)))
126
- out.push({ code: 'dock-outside-ring', layer, lines: [index], at: to });
127
- }
128
- return out;
129
- }
@@ -1,143 +0,0 @@
1
- import type { Ring, Vec2 } from './geom/types.js';
2
- /**
3
- * Distance at which two network points are one node, in meters. The network welds ends at it before
4
- * the emit, since the graph joins nodes only at exact coordinates.
5
- */
6
- export declare const NODE_SNAP = 0.05;
7
- /** A destination a route must reach, as the network stage sees it. */
8
- export interface RouteNetworkDestination {
9
- /** Stable identifier; labels docks. */
10
- readonly id: string;
11
- /** Footprint polygon; an obstacle unless {@link crossable}. */
12
- readonly polygon: Ring;
13
- /** Convex ring the dock lands in; see `SourceDestination.dock`. */
14
- readonly dock?: Ring;
15
- /** A route may pass THROUGH it. */
16
- readonly crossable?: boolean;
17
- /** One connection is the whole approach; see `SourceDestination.terminal`. */
18
- readonly terminal?: boolean;
19
- /** A way into the building; see `SourceDestination.entrance`. */
20
- readonly entrance?: boolean;
21
- }
22
- /** A short stub from the route network into a destination. */
23
- export interface RouteNetworkDock {
24
- /** The destination it reaches. */
25
- readonly id: string;
26
- /** Where it meets the route network. */
27
- readonly from: Vec2;
28
- /** Its tip, inside the destination. */
29
- readonly to: Vec2;
30
- }
31
- /** Why a destination has no dock a route can end at. */
32
- export type UnreachedReason =
33
- /** No open face and no doorway: walled in on every side. */
34
- 'no-way-in'
35
- /** Every way in breaks a dock rule: a wall, another outline, or no end inside the ring. */
36
- | 'way-in-blocked'
37
- /** Outside the building, or its ways in end on no routable floor. */
38
- | 'no-floor'
39
- /** Its tip is on floor no network reaches. */
40
- | 'cut-off'
41
- /** Attached, then lost to a later pass (pruned, moved off its line, or its dock broke a rule). */
42
- | 'detached';
43
- /** Which check at the emit took a `detached` destination's whisker away. */
44
- type DetachedBy =
45
- /** The whisker's tip is no vertex of the final network: a later pass moved its line. */
46
- 'not-a-vertex'
47
- /** From the final tip, no lawful dock reaches the destination. */
48
- | 'dock-unlawful';
49
- /** A destination without a dock a route can end at, with the facts the stage proved. */
50
- export interface Unreached {
51
- /** The destination's id, as it compiled. */
52
- readonly id: string;
53
- /** Why. */
54
- readonly reason: UnreachedReason;
55
- /** `no-floor` only: true when the destination stands outside the building outline. */
56
- readonly outside?: boolean;
57
- /** An entrance outside the building only: how far the nearest wall stands, in meters. */
58
- readonly nearestWall?: number;
59
- /** An entrance outside the building only: false when no wall in reach has floor behind it. */
60
- readonly floorBehind?: boolean;
61
- /** Where the failing way in ends: the stub tip, in the network's units. Absent when no way in exists. */
62
- readonly at?: Vec2;
63
- /** `detached` only: which check removed the whisker. */
64
- readonly by?: DetachedBy;
65
- }
66
- /** The build's entities, for the debug overlay. */
67
- export interface RouteNetworkAnatomy {
68
- /** Plan units per meter, so markers are sized alike on every plan. */
69
- readonly unit?: number;
70
- /** Every doorway: its gap, jamb to jamb, and its middle. */
71
- readonly doors: readonly {
72
- readonly gap: readonly [Vec2, Vec2];
73
- readonly mid: Vec2;
74
- }[];
75
- /** Growth targets: the destination id and the stub tip no row reached, that growth had to. */
76
- readonly anchors: readonly {
77
- readonly id: string;
78
- readonly at: Vec2;
79
- }[];
80
- /** Ways in, each with its foot and outward normal. */
81
- readonly ways: readonly {
82
- readonly id: string;
83
- readonly kind: string;
84
- readonly foot: Vec2;
85
- readonly normal: Vec2;
86
- }[];
87
- /** Whiskers laid, foot to tip. */
88
- readonly whiskers: readonly {
89
- readonly id: string;
90
- readonly kind: string;
91
- readonly foot: Vec2;
92
- readonly tip: Vec2;
93
- }[];
94
- /**
95
- * The turned directions that got a grid of their own: the angle of the first axis (radians, from the
96
- * plan's x), the grid's outline, and the destinations drawn in that direction.
97
- */
98
- readonly frames: readonly {
99
- readonly angle: number;
100
- readonly box: readonly Vec2[];
101
- readonly ids: readonly string[];
102
- }[];
103
- /**
104
- * Turned destinations with no grid — alone, or in a hall drawn square: reached on the plan's grid,
105
- * never framed.
106
- */
107
- readonly loose: readonly string[];
108
- /**
109
- * The aisle axes the stub tips define: the angle of the line's direction (radians, from the
110
- * plan's x), and the line's two ends.
111
- */
112
- readonly axes: readonly {
113
- readonly angle: number;
114
- readonly from: Vec2;
115
- readonly to: Vec2;
116
- }[];
117
- }
118
- /** The route network: polylines, docks and stats. */
119
- export interface RouteNetwork {
120
- /** Polylines. */
121
- readonly paths: Vec2[][];
122
- /** One dock per way in that reached a destination. */
123
- readonly docks: RouteNetworkDock[];
124
- /** Doorways, growth anchors, ways in and whiskers, for the overlay. */
125
- readonly anatomy?: RouteNetworkAnatomy;
126
- /** Counts, for diagnostics. */
127
- readonly stats: {
128
- /** Destinations without a dock, with the reason, in input order: no route reaches them. */
129
- readonly unreached: readonly Unreached[];
130
- /** Connected components of the network; > 1 means parts of the plan never link up. */
131
- readonly components?: number;
132
- /** Doorways the walls leave, as found. */
133
- readonly doorways?: number;
134
- /** Shortcut links cut in by the detour pass. */
135
- readonly shortcuts?: number;
136
- /** Network pieces the growth and the ties could not join; present only when > 0. */
137
- readonly untiedPieces?: number;
138
- /** Walkable floor pieces no destination or doorway serves, left out of the network. */
139
- readonly unservedFloorPieces?: number;
140
- };
141
- }
142
- export {};
143
- //# sourceMappingURL=graph-types.d.ts.map
@@ -1,7 +0,0 @@
1
- // The route network contract: the steiner network produces a {@link RouteNetwork}, `emit-v1` turns
2
- // it into the v1 graph.
3
- /**
4
- * Distance at which two network points are one node, in meters. The network welds ends at it before
5
- * the emit, since the graph joins nodes only at exact coordinates.
6
- */
7
- export const NODE_SNAP = 0.05;
@@ -1,54 +0,0 @@
1
- import type { Box, Ring } from './geom/types.js';
2
- import { type NetworkSizes } from './network-sizes.js';
3
- /** The walkable floor as a grid of cells, with how far each of them is from anything blocked. */
4
- export interface MaskCtx {
5
- /** Cell size. */
6
- readonly cs: number;
7
- /** Width in cells. */
8
- readonly w: number;
9
- /** Height in cells. */
10
- readonly h: number;
11
- /** Grid origin x. */
12
- readonly ox: number;
13
- /** Grid origin y. */
14
- readonly oy: number;
15
- /** 1 for floor, row-major. */
16
- readonly mask: Uint8Array;
17
- /** Meters from each cell to the nearest blocked one. */
18
- readonly dist: Float32Array;
19
- /**
20
- * Whether a point is on the floor.
21
- * @param x Point x.
22
- * @param y Point y.
23
- * @returns True on the floor.
24
- */
25
- freeAt(x: number, y: number): boolean;
26
- /**
27
- * Whether a straight line stays on floor (sampled at under a cell).
28
- * @param ax Start x.
29
- * @param ay Start y.
30
- * @param bx End x.
31
- * @param by End y.
32
- * @returns True when every sample is floor.
33
- */
34
- lineFree(ax: number, ay: number, bx: number, by: number): boolean;
35
- }
36
- /**
37
- * Rasterize the walkable rings (walls and footprints already cut out) and their clearance field.
38
- * @param rings The walkable polygon (outer rings + holes).
39
- * @param cfg Network sizes.
40
- * @param within Box to read; the rings' bounds by default.
41
- * @returns The mask and its clearance field.
42
- */
43
- export declare function buildMaskCtx(rings: readonly Ring[], cfg: NetworkSizes, within?: Box): MaskCtx;
44
- /**
45
- * Opens a doorway's throat to the router and to the lines laid through it: floor cells within `r`
46
- * of the middle count a person's clearance, so a route can pass a doorway narrower than two
47
- * clearances. Only cells already free are lifted; the jambs and walls stay closed.
48
- * @param ctx The mask, in its own coordinates.
49
- * @param mx Doorway middle x.
50
- * @param my Doorway middle y.
51
- * @param r The throat's radius.
52
- */
53
- export declare function openDoorMiddle(ctx: MaskCtx, mx: number, my: number, r: number): void;
54
- //# sourceMappingURL=mask.d.ts.map
package/dist/lib/mask.js DELETED
@@ -1,210 +0,0 @@
1
- // The raster mask the route network grows over: the walkable floor as cells, with how far each cell is
2
- // from anything blocked.
3
- import { cellArray } from './cell-array.js';
4
- import { boundsOf } from './geom/primitives.js';
5
- import { MIN_PASS } from './network-sizes.js';
6
- /**
7
- * Rasterize the walkable rings (walls and footprints already cut out) and their clearance field.
8
- * @param rings The walkable polygon (outer rings + holes).
9
- * @param cfg Network sizes.
10
- * @param within Box to read; the rings' bounds by default.
11
- * @returns The mask and its clearance field.
12
- */
13
- export function buildMaskCtx(rings, cfg, within) {
14
- let [minx, miny, maxx, maxy] = within ?? boundsOf(rings.flat());
15
- if (!isFinite(minx)) {
16
- minx = 0;
17
- miny = 0;
18
- maxx = 1;
19
- maxy = 1;
20
- }
21
- const cs = cfg.cs;
22
- const ox = minx - 1;
23
- const oy = miny - 1;
24
- const w = Math.max(4, Math.ceil((maxx - minx + 2) / cs));
25
- const h = Math.max(4, Math.ceil((maxy - miny + 2) / cs));
26
- const mask = cellArray(Uint8Array, w * h);
27
- // Nonzero-winding scanline fill over an active edge list.
28
- const fillPolys = (polys, value) => {
29
- const edges = [];
30
- for (const poly of polys)
31
- for (let i = 0; i < poly.length; i++) {
32
- const [ax, ay] = poly[i];
33
- const [bx, by] = poly[(i + 1) % poly.length];
34
- if (ay !== by)
35
- edges.push({
36
- ax,
37
- ay,
38
- bx,
39
- by,
40
- lo: Math.min(ay, by),
41
- hi: Math.max(ay, by),
42
- dir: by > ay ? 1 : -1,
43
- });
44
- }
45
- edges.sort((p, q) => p.lo - q.lo);
46
- const active = [];
47
- let next = 0;
48
- for (let gy = 0; gy < h; gy++) {
49
- const y = oy + (gy + 0.5) * cs;
50
- const xs = [];
51
- while (next < edges.length && edges[next].lo <= y)
52
- active.push(edges[next++]);
53
- for (let k = active.length - 1; k >= 0; k--)
54
- if (active[k].hi <= y) {
55
- active[k] = active[active.length - 1];
56
- active.pop();
57
- }
58
- for (const { ax, ay, bx, by, dir } of active)
59
- xs.push([ax + ((bx - ax) * (y - ay)) / (by - ay), dir]);
60
- xs.sort((u, v) => u[0] - v[0]);
61
- let wind = 0;
62
- for (let k = 0; k + 1 < xs.length; k++) {
63
- wind += xs[k][1];
64
- if (wind === 0)
65
- continue;
66
- const i0 = Math.max(0, Math.ceil((xs[k][0] - ox) / cs - 0.5));
67
- const i1 = Math.min(w - 1, Math.floor((xs[k + 1][0] - ox) / cs - 0.5));
68
- for (let i = i0; i <= i1; i++)
69
- mask[gy * w + i] = value;
70
- }
71
- }
72
- };
73
- fillPolys(rings, 1);
74
- // A cell is floor only when no boundary edge passes through it, so a strip thinner than a cell still
75
- // closes the cells it crosses. Cells are found by exact grid traversal of each edge.
76
- const close = (gx, gy) => {
77
- if (gx >= 0 && gy >= 0 && gx < w && gy < h)
78
- mask[gy * w + gx] = 0;
79
- };
80
- const cover = (ax, ay, bx, by) => {
81
- let gx = Math.floor((ax - ox) / cs);
82
- let gy = Math.floor((ay - oy) / cs);
83
- const ex = Math.floor((bx - ox) / cs);
84
- const ey = Math.floor((by - oy) / cs);
85
- const dx = bx - ax;
86
- const dy = by - ay;
87
- const sx = Math.sign(dx);
88
- const sy = Math.sign(dy);
89
- const tdx = dx ? cs / Math.abs(dx) : Infinity;
90
- const tdy = dy ? cs / Math.abs(dy) : Infinity;
91
- let tx = dx ? (ox + (gx + (dx > 0 ? 1 : 0)) * cs - ax) / dx : Infinity;
92
- let ty = dy ? (oy + (gy + (dy > 0 ? 1 : 0)) * cs - ay) / dy : Infinity;
93
- close(gx, gy);
94
- for (let guard = Math.abs(ex - gx) + Math.abs(ey - gy) + 2; guard > 0; guard--) {
95
- if (gx === ex && gy === ey)
96
- break;
97
- if (tx < ty) {
98
- gx += sx;
99
- tx += tdx;
100
- }
101
- else if (ty < tx) {
102
- gy += sy;
103
- ty += tdy;
104
- }
105
- else {
106
- // Through a grid corner: both cells beside it are touched.
107
- close(gx + sx, gy);
108
- close(gx, gy + sy);
109
- gx += sx;
110
- gy += sy;
111
- tx += tdx;
112
- ty += tdy;
113
- }
114
- close(gx, gy);
115
- }
116
- };
117
- for (const ring of rings)
118
- for (let i = 0; i < ring.length; i++) {
119
- const a = ring[i];
120
- const b = ring[(i + 1) % ring.length];
121
- cover(a[0], a[1], b[0], b[1]);
122
- }
123
- // Chamfer distance (meters) to the nearest blocked cell.
124
- const dist = cellArray(Float32Array, w * h).fill(1e9);
125
- for (let i = 0; i < w * h; i++)
126
- if (!mask[i])
127
- dist[i] = 0;
128
- const d1 = cs;
129
- const d2 = cs * Math.SQRT2;
130
- for (let y = 0; y < h; y++)
131
- for (let x = 0; x < w; x++) {
132
- const i = y * w + x;
133
- if (x > 0)
134
- dist[i] = Math.min(dist[i], dist[i - 1] + d1);
135
- if (y > 0)
136
- dist[i] = Math.min(dist[i], dist[i - w] + d1);
137
- if (x > 0 && y > 0)
138
- dist[i] = Math.min(dist[i], dist[i - w - 1] + d2);
139
- if (x < w - 1 && y > 0)
140
- dist[i] = Math.min(dist[i], dist[i - w + 1] + d2);
141
- }
142
- for (let y = h - 1; y >= 0; y--)
143
- for (let x = w - 1; x >= 0; x--) {
144
- const i = y * w + x;
145
- if (x < w - 1)
146
- dist[i] = Math.min(dist[i], dist[i + 1] + d1);
147
- if (y < h - 1)
148
- dist[i] = Math.min(dist[i], dist[i + w] + d1);
149
- if (x < w - 1 && y < h - 1)
150
- dist[i] = Math.min(dist[i], dist[i + w + 1] + d2);
151
- if (x > 0 && y < h - 1)
152
- dist[i] = Math.min(dist[i], dist[i + w - 1] + d2);
153
- }
154
- const cellAt = (x, y) => {
155
- const gx = Math.floor((x - ox) / cs);
156
- const gy = Math.floor((y - oy) / cs);
157
- return gx >= 0 && gy >= 0 && gx < w && gy < h ? gy * w + gx : -1;
158
- };
159
- const freeAt = (x, y) => {
160
- const c = cellAt(x, y);
161
- return c >= 0 && mask[c] === 1;
162
- };
163
- const lineFree = (ax, ay, bx, by) => {
164
- const len = Math.hypot(bx - ax, by - ay);
165
- const n = Math.max(2, Math.ceil(len / (cs * 0.6)));
166
- for (let i = 1; i < n; i++) {
167
- const s = i / n;
168
- if (!freeAt(ax + (bx - ax) * s, ay + (by - ay) * s))
169
- return false;
170
- }
171
- return true;
172
- };
173
- return {
174
- cs,
175
- w,
176
- h,
177
- ox,
178
- oy,
179
- mask,
180
- dist,
181
- freeAt,
182
- lineFree,
183
- };
184
- }
185
- /**
186
- * Opens a doorway's throat to the router and to the lines laid through it: floor cells within `r`
187
- * of the middle count a person's clearance, so a route can pass a doorway narrower than two
188
- * clearances. Only cells already free are lifted; the jambs and walls stay closed.
189
- * @param ctx The mask, in its own coordinates.
190
- * @param mx Doorway middle x.
191
- * @param my Doorway middle y.
192
- * @param r The throat's radius.
193
- */
194
- export function openDoorMiddle(ctx, mx, my, r) {
195
- const { cs, ox, oy, w, h, mask, dist } = ctx;
196
- const gx0 = Math.max(0, Math.floor((mx - r - ox) / cs));
197
- const gx1 = Math.min(w - 1, Math.floor((mx + r - ox) / cs));
198
- const gy0 = Math.max(0, Math.floor((my - r - oy) / cs));
199
- const gy1 = Math.min(h - 1, Math.floor((my + r - oy) / cs));
200
- for (let gy = gy0; gy <= gy1; gy++)
201
- for (let gx = gx0; gx <= gx1; gx++) {
202
- const cx = ox + (gx + 0.5) * cs;
203
- const cy = oy + (gy + 0.5) * cs;
204
- if (Math.hypot(cx - mx, cy - my) > r)
205
- continue;
206
- const c = gy * w + gx;
207
- if (mask[c] === 1 && dist[c] < MIN_PASS)
208
- dist[c] = MIN_PASS;
209
- }
210
- }