@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.
- package/README.md +2 -445
- package/package.json +3 -34
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -3
- package/dist/lib/attach.d.ts +0 -43
- package/dist/lib/attach.js +0 -199
- package/dist/lib/axes.d.ts +0 -76
- package/dist/lib/axes.js +0 -103
- package/dist/lib/blockers.d.ts +0 -134
- package/dist/lib/blockers.js +0 -289
- package/dist/lib/cell-array.d.ts +0 -14
- package/dist/lib/cell-array.js +0 -25
- package/dist/lib/compile-layer.d.ts +0 -41
- package/dist/lib/compile-layer.js +0 -241
- package/dist/lib/compile-wayfinding.d.ts +0 -90
- package/dist/lib/compile-wayfinding.js +0 -203
- package/dist/lib/connect.d.ts +0 -35
- package/dist/lib/connect.js +0 -206
- package/dist/lib/diagnostics.d.ts +0 -190
- package/dist/lib/diagnostics.js +0 -634
- package/dist/lib/doorways.d.ts +0 -78
- package/dist/lib/doorways.js +0 -331
- package/dist/lib/emit-network.d.ts +0 -47
- package/dist/lib/emit-network.js +0 -105
- package/dist/lib/emit-v1.d.ts +0 -31
- package/dist/lib/emit-v1.js +0 -56
- package/dist/lib/entries.d.ts +0 -123
- package/dist/lib/entries.js +0 -448
- package/dist/lib/floor-grids.d.ts +0 -48
- package/dist/lib/floor-grids.js +0 -185
- package/dist/lib/framing.d.ts +0 -20
- package/dist/lib/framing.js +0 -187
- package/dist/lib/geom/boolean-ops.d.ts +0 -21
- package/dist/lib/geom/boolean-ops.js +0 -1
- package/dist/lib/geom/clipper2-ops.d.ts +0 -7
- package/dist/lib/geom/clipper2-ops.js +0 -50
- package/dist/lib/geom/footprint.d.ts +0 -26
- package/dist/lib/geom/footprint.js +0 -203
- package/dist/lib/geom/frames.d.ts +0 -116
- package/dist/lib/geom/frames.js +0 -233
- package/dist/lib/geom/mesh-outline.d.ts +0 -13
- package/dist/lib/geom/mesh-outline.js +0 -90
- package/dist/lib/geom/normalize.d.ts +0 -28
- package/dist/lib/geom/normalize.js +0 -118
- package/dist/lib/geom/point-key.d.ts +0 -16
- package/dist/lib/geom/point-key.js +0 -15
- package/dist/lib/geom/primitives.d.ts +0 -105
- package/dist/lib/geom/primitives.js +0 -184
- package/dist/lib/geom/split-crossings.d.ts +0 -14
- package/dist/lib/geom/split-crossings.js +0 -171
- package/dist/lib/geom/types.d.ts +0 -9
- package/dist/lib/geom/types.js +0 -1
- package/dist/lib/geom/walls.d.ts +0 -31
- package/dist/lib/geom/walls.js +0 -71
- package/dist/lib/graph-invariants.d.ts +0 -49
- package/dist/lib/graph-invariants.js +0 -129
- package/dist/lib/graph-types.d.ts +0 -143
- package/dist/lib/graph-types.js +0 -7
- package/dist/lib/mask.d.ts +0 -54
- package/dist/lib/mask.js +0 -210
- package/dist/lib/min-heap.d.ts +0 -24
- package/dist/lib/min-heap.js +0 -73
- package/dist/lib/network-build.d.ts +0 -21
- package/dist/lib/network-build.js +0 -4
- package/dist/lib/network-graph.d.ts +0 -80
- package/dist/lib/network-graph.js +0 -467
- package/dist/lib/network-sizes.d.ts +0 -29
- package/dist/lib/network-sizes.js +0 -22
- package/dist/lib/network-store.d.ts +0 -173
- package/dist/lib/network-store.js +0 -430
- package/dist/lib/openings.d.ts +0 -54
- package/dist/lib/openings.js +0 -387
- package/dist/lib/place-path.d.ts +0 -47
- package/dist/lib/place-path.js +0 -346
- package/dist/lib/post-passes.d.ts +0 -31
- package/dist/lib/post-passes.js +0 -305
- package/dist/lib/reachability.d.ts +0 -58
- package/dist/lib/reachability.js +0 -220
- package/dist/lib/report.d.ts +0 -49
- package/dist/lib/report.js +0 -374
- package/dist/lib/router.d.ts +0 -111
- package/dist/lib/router.js +0 -388
- package/dist/lib/runs.d.ts +0 -92
- package/dist/lib/runs.js +0 -232
- package/dist/lib/scale.d.ts +0 -58
- package/dist/lib/scale.js +0 -185
- package/dist/lib/steiner-network.d.ts +0 -19
- package/dist/lib/steiner-network.js +0 -183
- package/dist/lib/strip.d.ts +0 -43
- package/dist/lib/strip.js +0 -200
- package/dist/lib/stubs.d.ts +0 -84
- package/dist/lib/stubs.js +0 -316
- package/dist/lib/transitions.d.ts +0 -72
- package/dist/lib/transitions.js +0 -191
- package/dist/lib/types.d.ts +0 -126
- package/dist/lib/types.js +0 -1
- package/dist/lib/validate-source.d.ts +0 -41
- package/dist/lib/validate-source.js +0 -133
- package/dist/lib/walkable.d.ts +0 -104
- package/dist/lib/walkable.js +0 -140
package/dist/lib/blockers.js
DELETED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
// What a straight run may not cross — walls, outlines of destinations nobody walks through, doorway
|
|
2
|
-
// gaps — indexed by the cells they cover, and the rays and boxes read through the index.
|
|
3
|
-
import { rayVsSegment, ringEdges, segmentsCross } from './geom/primitives.js';
|
|
4
|
-
import { NODE_SNAP } from './graph-types.js';
|
|
5
|
-
/** {@link SegmentGrid} cell side, meters: a booth or two, so a query reads a few cells. */
|
|
6
|
-
const GRID_CELL = 4;
|
|
7
|
-
/** Padding a segment is filed with, so a hit a hair past its end or on a cell border still finds it. */
|
|
8
|
-
const GRID_PAD = 1e-3;
|
|
9
|
-
/** What a blocker segment is. */
|
|
10
|
-
export var BlockerKind;
|
|
11
|
-
(function (BlockerKind) {
|
|
12
|
-
BlockerKind[BlockerKind["Wall"] = 0] = "Wall";
|
|
13
|
-
BlockerKind[BlockerKind["Solid"] = 1] = "Solid";
|
|
14
|
-
BlockerKind[BlockerKind["Gap"] = 2] = "Gap";
|
|
15
|
-
})(BlockerKind || (BlockerKind = {}));
|
|
16
|
-
/**
|
|
17
|
-
* Blocker segments bucketed by the cells their bounds cover. It only narrows the candidates: a segment it
|
|
18
|
-
* leaves out has bounds apart from the query's, so no test could hold for it.
|
|
19
|
-
*/
|
|
20
|
-
export class SegmentGrid {
|
|
21
|
-
/** Every blocker segment, in {@link Blockers} order: walls, then outlines, then gaps. */
|
|
22
|
-
segs = [];
|
|
23
|
-
/** What each segment is. */
|
|
24
|
-
from = [];
|
|
25
|
-
/** Owning destination of a solid edge; empty for walls and gaps. */
|
|
26
|
-
owner = [];
|
|
27
|
-
cells = new Map();
|
|
28
|
-
seen;
|
|
29
|
-
stamp = 0;
|
|
30
|
-
/** Bounds of the filed cells, so a ray knows when it has left them all. */
|
|
31
|
-
c0 = Infinity;
|
|
32
|
-
c1 = -Infinity;
|
|
33
|
-
r0 = Infinity;
|
|
34
|
-
r1 = -Infinity;
|
|
35
|
-
constructor(blockers) {
|
|
36
|
-
const add = (seg, from, owner) => {
|
|
37
|
-
this.segs.push(seg);
|
|
38
|
-
this.from.push(from);
|
|
39
|
-
this.owner.push(owner);
|
|
40
|
-
};
|
|
41
|
-
for (const wall of blockers.walls)
|
|
42
|
-
add(wall, BlockerKind.Wall, '');
|
|
43
|
-
for (const poi of blockers.solid)
|
|
44
|
-
for (const edge of poi.edges)
|
|
45
|
-
add(edge, BlockerKind.Solid, poi.id);
|
|
46
|
-
for (const gap of blockers.gaps)
|
|
47
|
-
add(gap, BlockerKind.Gap, '');
|
|
48
|
-
this.seen = new Int32Array(this.segs.length);
|
|
49
|
-
// The cells the segments reach: a query looks no farther.
|
|
50
|
-
for (const seg of this.segs) {
|
|
51
|
-
this.c0 = Math.min(this.c0, Math.floor((Math.min(seg[0], seg[2]) - GRID_PAD) / GRID_CELL));
|
|
52
|
-
this.c1 = Math.max(this.c1, Math.floor((Math.max(seg[0], seg[2]) + GRID_PAD) / GRID_CELL));
|
|
53
|
-
this.r0 = Math.min(this.r0, Math.floor((Math.min(seg[1], seg[3]) - GRID_PAD) / GRID_CELL));
|
|
54
|
-
this.r1 = Math.max(this.r1, Math.floor((Math.max(seg[1], seg[3]) + GRID_PAD) / GRID_CELL));
|
|
55
|
-
}
|
|
56
|
-
this.segs.forEach((seg, i) => this.cover(Math.min(seg[0], seg[2]) - GRID_PAD, Math.min(seg[1], seg[3]) - GRID_PAD, Math.max(seg[0], seg[2]) + GRID_PAD, Math.max(seg[1], seg[3]) + GRID_PAD, (key) => {
|
|
57
|
-
const list = this.cells.get(key);
|
|
58
|
-
if (list)
|
|
59
|
-
list.push(i);
|
|
60
|
-
else
|
|
61
|
-
this.cells.set(key, [i]);
|
|
62
|
-
}));
|
|
63
|
-
}
|
|
64
|
-
cover(x0, y0, x1, y1, visit) {
|
|
65
|
-
const c0 = Math.max(this.c0, Math.floor(x0 / GRID_CELL));
|
|
66
|
-
const c1 = Math.min(this.c1, Math.floor(x1 / GRID_CELL));
|
|
67
|
-
const r0 = Math.max(this.r0, Math.floor(y0 / GRID_CELL));
|
|
68
|
-
const r1 = Math.min(this.r1, Math.floor(y1 / GRID_CELL));
|
|
69
|
-
for (let c = c0; c <= c1; c++)
|
|
70
|
-
for (let r = r0; r <= r1; r++)
|
|
71
|
-
visit(c * 1_000_003 + r);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Walk the cells a ray passes through, nearest first, handing each segment once.
|
|
75
|
-
* @param ox Ray origin x.
|
|
76
|
-
* @param oy Ray origin y.
|
|
77
|
-
* @param nx Ray direction x.
|
|
78
|
-
* @param ny Ray direction y.
|
|
79
|
-
* @param from Ray parameter to start at.
|
|
80
|
-
* @param visit Gets a cell's new segments and the parameter where the ray leaves it; true stops.
|
|
81
|
-
*/
|
|
82
|
-
alongRay(ox, oy, nx, ny, from, visit) {
|
|
83
|
-
const sx = ox + nx * from;
|
|
84
|
-
const sy = oy + ny * from;
|
|
85
|
-
let c = Math.floor(sx / GRID_CELL);
|
|
86
|
-
let r = Math.floor(sy / GRID_CELL);
|
|
87
|
-
const stepC = nx > 0 ? 1 : nx < 0 ? -1 : 0;
|
|
88
|
-
const stepR = ny > 0 ? 1 : ny < 0 ? -1 : 0;
|
|
89
|
-
// Ray parameter at the next column/row border, and its growth per cell.
|
|
90
|
-
const border = (p, d, cell) => d > 0 ? ((cell + 1) * GRID_CELL - p) / d : d < 0 ? (cell * GRID_CELL - p) / d : Infinity;
|
|
91
|
-
let nextC = from + border(sx, nx, c);
|
|
92
|
-
let nextR = from + border(sy, ny, r);
|
|
93
|
-
const perC = nx ? GRID_CELL / Math.abs(nx) : Infinity;
|
|
94
|
-
const perR = ny ? GRID_CELL / Math.abs(ny) : Infinity;
|
|
95
|
-
this.stamp++;
|
|
96
|
-
// A ray going nowhere meets only what its start cell holds.
|
|
97
|
-
const still = !stepC && !stepR;
|
|
98
|
-
for (;;) {
|
|
99
|
-
const fresh = [];
|
|
100
|
-
for (const i of this.cells.get(c * 1_000_003 + r) ?? []) {
|
|
101
|
-
if (this.seen[i] === this.stamp)
|
|
102
|
-
continue;
|
|
103
|
-
this.seen[i] = this.stamp;
|
|
104
|
-
fresh.push(i);
|
|
105
|
-
}
|
|
106
|
-
if (visit(fresh, Math.min(nextC, nextR)) || still)
|
|
107
|
-
return;
|
|
108
|
-
if (nextC < nextR) {
|
|
109
|
-
c += stepC;
|
|
110
|
-
nextC += perC;
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
r += stepR;
|
|
114
|
-
nextR += perR;
|
|
115
|
-
}
|
|
116
|
-
// Past the last filed cell the ray meets nothing more.
|
|
117
|
-
if ((stepC > 0 && c > this.c1) || (stepC < 0 && c < this.c0))
|
|
118
|
-
return;
|
|
119
|
-
if ((stepR > 0 && r > this.r1) || (stepR < 0 && r < this.r0))
|
|
120
|
-
return;
|
|
121
|
-
if (!stepC && (c < this.c0 || c > this.c1))
|
|
122
|
-
return;
|
|
123
|
-
if (!stepR && (r < this.r0 || r > this.r1))
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Segments whose bounds meet a box, each once, in {@link Blockers} order.
|
|
129
|
-
* @param x0 Box min x.
|
|
130
|
-
* @param y0 Box min y.
|
|
131
|
-
* @param x1 Box max x.
|
|
132
|
-
* @param y1 Box max y.
|
|
133
|
-
* @returns Their positions in {@link segs}.
|
|
134
|
-
*/
|
|
135
|
-
near(x0, y0, x1, y1) {
|
|
136
|
-
const found = [];
|
|
137
|
-
this.stamp++;
|
|
138
|
-
this.cover(x0, y0, x1, y1, (key) => {
|
|
139
|
-
for (const i of this.cells.get(key) ?? []) {
|
|
140
|
-
if (this.seen[i] === this.stamp)
|
|
141
|
-
continue;
|
|
142
|
-
this.seen[i] = this.stamp;
|
|
143
|
-
const seg = this.segs[i];
|
|
144
|
-
if (Math.max(seg[0], seg[2]) >= x0 &&
|
|
145
|
-
Math.min(seg[0], seg[2]) <= x1 &&
|
|
146
|
-
Math.max(seg[1], seg[3]) >= y0 &&
|
|
147
|
-
Math.min(seg[1], seg[3]) <= y1)
|
|
148
|
-
found.push(i);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
return found.sort((a, b) => a - b);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Whether a segment crosses any blocker of one kind (exact `segmentsCross` on the candidates).
|
|
155
|
-
* @param seg The segment.
|
|
156
|
-
* @param from Blocker kind.
|
|
157
|
-
* @param except Destination whose own edges do not count.
|
|
158
|
-
* @returns True when one crosses it.
|
|
159
|
-
*/
|
|
160
|
-
crosses(seg, from, except) {
|
|
161
|
-
return this.crossing(seg, from, except).length > 0;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* The blockers of one kind a segment crosses (exact `segmentsCross` on the candidates).
|
|
165
|
-
* @param seg The segment.
|
|
166
|
-
* @param from Blocker kind.
|
|
167
|
-
* @param except Destination whose own edges do not count.
|
|
168
|
-
* @returns The blockers crossed.
|
|
169
|
-
*/
|
|
170
|
-
crossing(seg, from, except) {
|
|
171
|
-
return this.near(Math.min(seg[0], seg[2]), Math.min(seg[1], seg[3]), Math.max(seg[0], seg[2]), Math.max(seg[1], seg[3]))
|
|
172
|
-
.filter((i) => this.from[i] === from &&
|
|
173
|
-
(except === undefined || this.owner[i] !== except) &&
|
|
174
|
-
segmentsCross(seg, this.segs[i]))
|
|
175
|
-
.map((i) => this.segs[i]);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Walls and outlines whose bounds meet a box.
|
|
180
|
-
* @param blockers The blockers.
|
|
181
|
-
* @param box The box.
|
|
182
|
-
* @returns Each segment with its owning destination; no owner for a wall.
|
|
183
|
-
*/
|
|
184
|
-
export function blockersNear(blockers, box) {
|
|
185
|
-
const grid = blockerGrid(blockers);
|
|
186
|
-
const [x0, y0, x1, y1] = box;
|
|
187
|
-
return grid
|
|
188
|
-
.near(x0, y0, x1, y1)
|
|
189
|
-
.filter((i) => grid.from[i] !== BlockerKind.Gap)
|
|
190
|
-
.map((i) => grid.from[i] === BlockerKind.Solid
|
|
191
|
-
? { seg: grid.segs[i], owner: grid.owner[i] }
|
|
192
|
-
: { seg: grid.segs[i] });
|
|
193
|
-
}
|
|
194
|
-
/** One grid per blockers object, built lazily. */
|
|
195
|
-
const grids = new WeakMap();
|
|
196
|
-
/**
|
|
197
|
-
* The index of a blockers object, built once and kept with it.
|
|
198
|
-
* @param blockers The blockers.
|
|
199
|
-
* @returns Their index.
|
|
200
|
-
*/
|
|
201
|
-
export function blockerGrid(blockers) {
|
|
202
|
-
let grid = grids.get(blockers);
|
|
203
|
-
if (!grid) {
|
|
204
|
-
grid = new SegmentGrid(blockers);
|
|
205
|
-
grids.set(blockers, grid);
|
|
206
|
-
}
|
|
207
|
-
return grid;
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Whether a segment crosses a wall, a closed doorway side or the outline of a destination nobody
|
|
211
|
-
* walks through: the rule every network line keeps.
|
|
212
|
-
* @param blockers The blockers.
|
|
213
|
-
* @param seg The segment.
|
|
214
|
-
* @returns True when it crosses one.
|
|
215
|
-
*/
|
|
216
|
-
export function crossesBlocker(blockers, seg) {
|
|
217
|
-
const grid = blockerGrid(blockers);
|
|
218
|
-
return grid.crosses(seg, BlockerKind.Wall) || grid.crosses(seg, BlockerKind.Solid);
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* What a run out of a destination may not cross, read once for the layer.
|
|
222
|
-
* @param destinations Every destination on the layer.
|
|
223
|
-
* @param walls The layer's wall segments.
|
|
224
|
-
* @param doors The layer's doorways.
|
|
225
|
-
* @param drawn The rest of each destination's drawing, by the destination's id.
|
|
226
|
-
* @param walled Destinations entered through doorways: the walls they line stand in for them.
|
|
227
|
-
* @param passes Wall spans an entrance's dock may cross, by the entrance's id.
|
|
228
|
-
* @returns The blockers.
|
|
229
|
-
*/
|
|
230
|
-
export function blockersOf(destinations, walls, doors, drawn = [], walled = new Set(), passes) {
|
|
231
|
-
return {
|
|
232
|
-
walls,
|
|
233
|
-
solid: [
|
|
234
|
-
...destinations
|
|
235
|
-
.filter((d) => !d.crossable && d.polygon.length >= 3)
|
|
236
|
-
.map((d) => ({ id: d.id, edges: ringEdges(d.polygon) })),
|
|
237
|
-
...drawn.map((block) => ({ id: block.id, edges: ringEdges(block.ring) })),
|
|
238
|
-
],
|
|
239
|
-
gaps: doors.map((door) => door.gap),
|
|
240
|
-
walled,
|
|
241
|
-
...(passes && { passes }),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* How far a ray runs before it meets a blocker, by exact intersection, and what it met.
|
|
246
|
-
* @param ox Ray origin x.
|
|
247
|
-
* @param oy Ray origin y.
|
|
248
|
-
* @param nx Ray direction x.
|
|
249
|
-
* @param ny Ray direction y.
|
|
250
|
-
* @param ownId Destination the ray leaves.
|
|
251
|
-
* @param own `'skip'` ignores its outline; `'ahead'` counts only the outline ahead of the start.
|
|
252
|
-
* @param withGaps Whether a doorway gap stops the ray.
|
|
253
|
-
* @param blockers What the ray may meet.
|
|
254
|
-
* @returns The distance, or `Infinity` when the ray meets nothing.
|
|
255
|
-
*/
|
|
256
|
-
export function freeRun(ox, oy, nx, ny, ownId, own, withGaps, blockers) {
|
|
257
|
-
let best = Infinity;
|
|
258
|
-
const hit = [0, 0];
|
|
259
|
-
// `from`: where hits start to count. A wall along the start face closes the ray; the own outline there
|
|
260
|
-
// is the face itself and does not.
|
|
261
|
-
const test = (seg, from) => {
|
|
262
|
-
if (!rayVsSegment(hit, ox, oy, nx, ny, seg[0], seg[1], seg[2], seg[3]))
|
|
263
|
-
return;
|
|
264
|
-
const [t, u] = hit;
|
|
265
|
-
if (u < -1e-9 || u > 1 + 1e-9 || t <= from)
|
|
266
|
-
return;
|
|
267
|
-
if (t < best)
|
|
268
|
-
best = Math.max(0, t);
|
|
269
|
-
};
|
|
270
|
-
const grid = blockerGrid(blockers);
|
|
271
|
-
// Cell by cell outward: a hit's segment is filed in the hit's cell, so once the best hit is nearer than
|
|
272
|
-
// the cell being left, no later cell holds a nearer one.
|
|
273
|
-
grid.alongRay(ox, oy, nx, ny, -2 * NODE_SNAP, (ids, leaves) => {
|
|
274
|
-
for (const i of ids) {
|
|
275
|
-
const seg = grid.segs[i];
|
|
276
|
-
const owner = grid.owner[i];
|
|
277
|
-
if (grid.from[i] === BlockerKind.Wall)
|
|
278
|
-
test(seg, -NODE_SNAP);
|
|
279
|
-
else if (grid.from[i] === BlockerKind.Gap) {
|
|
280
|
-
if (withGaps)
|
|
281
|
-
test(seg, NODE_SNAP);
|
|
282
|
-
}
|
|
283
|
-
else if (owner !== ownId || own !== 'skip')
|
|
284
|
-
test(seg, owner === ownId ? NODE_SNAP : -NODE_SNAP);
|
|
285
|
-
}
|
|
286
|
-
return best < leaves - GRID_PAD;
|
|
287
|
-
});
|
|
288
|
-
return best;
|
|
289
|
-
}
|
package/dist/lib/cell-array.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/** The engine refused the memory a floor's raster needs: the floor is too large for this device. */
|
|
2
|
-
export declare class CellsRefusedError extends Error {
|
|
3
|
-
constructor(length: number);
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* A raster-sized typed array. An engine refuses a valid length only for want of memory, whatever its
|
|
7
|
-
* message says, so any refusal of one throws {@link CellsRefusedError}; an invalid length is a bug and
|
|
8
|
-
* its error passes through.
|
|
9
|
-
* @param Ctor The typed array class.
|
|
10
|
-
* @param length Its length, in cells.
|
|
11
|
-
* @returns The zeroed array.
|
|
12
|
-
*/
|
|
13
|
-
export declare function cellArray<T>(Ctor: new (length: number) => T, length: number): T;
|
|
14
|
-
//# sourceMappingURL=cell-array.d.ts.map
|
package/dist/lib/cell-array.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/** The engine refused the memory a floor's raster needs: the floor is too large for this device. */
|
|
2
|
-
export class CellsRefusedError extends Error {
|
|
3
|
-
constructor(length) {
|
|
4
|
-
super(`the engine refused a raster of ${length} cells`);
|
|
5
|
-
this.name = 'CellsRefusedError';
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* A raster-sized typed array. An engine refuses a valid length only for want of memory, whatever its
|
|
10
|
-
* message says, so any refusal of one throws {@link CellsRefusedError}; an invalid length is a bug and
|
|
11
|
-
* its error passes through.
|
|
12
|
-
* @param Ctor The typed array class.
|
|
13
|
-
* @param length Its length, in cells.
|
|
14
|
-
* @returns The zeroed array.
|
|
15
|
-
*/
|
|
16
|
-
export function cellArray(Ctor, length) {
|
|
17
|
-
try {
|
|
18
|
-
return new Ctor(length);
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
if (Number.isSafeInteger(length) && length >= 0)
|
|
22
|
-
throw new CellsRefusedError(length);
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { NormalizeFacts } from './diagnostics.js';
|
|
2
|
-
import type { BooleanOps } from './geom/boolean-ops.js';
|
|
3
|
-
import type { RouteNetwork } from './graph-types.js';
|
|
4
|
-
import type { SourceDestination, WayfindingLayerSource } from './types.js';
|
|
5
|
-
import { type WalkableArea } from './walkable.js';
|
|
6
|
-
/** One layer's stage artifacts. */
|
|
7
|
-
interface CompiledLayer {
|
|
8
|
-
/** Globally unique layer id. */
|
|
9
|
-
readonly layer: string;
|
|
10
|
-
/** The destinations as the stages saw them, rings normalized and carved. */
|
|
11
|
-
readonly destinations: SourceDestination[];
|
|
12
|
-
/** Walkable area. */
|
|
13
|
-
readonly walkable: WalkableArea;
|
|
14
|
-
/** Route network. */
|
|
15
|
-
readonly network: RouteNetwork;
|
|
16
|
-
/** What normalization did to the drawing, for the diagnostics. */
|
|
17
|
-
readonly facts: NormalizeFacts;
|
|
18
|
-
}
|
|
19
|
-
/** Options of one layer's compile. */
|
|
20
|
-
interface CompileLayerOptions {
|
|
21
|
-
/** A walkable area already built for this layer; the stage is skipped. */
|
|
22
|
-
readonly walkable?: WalkableArea;
|
|
23
|
-
/** Called as each stage finishes; `cached` when it was handed its artifact. */
|
|
24
|
-
readonly onStage?: (stage: 'walkable' | 'network', cached: boolean) => void;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* One layer's pipeline: walkable area, then route network.
|
|
28
|
-
* @param source One layer's walls and destinations.
|
|
29
|
-
* @param ops Polygon boolean backend.
|
|
30
|
-
* @param options A ready walkable area and the stage callback.
|
|
31
|
-
* @returns The layer's walkable area and route network.
|
|
32
|
-
*/
|
|
33
|
-
export declare function compileLayer(source: WayfindingLayerSource, ops: BooleanOps, options?: CompileLayerOptions): CompiledLayer;
|
|
34
|
-
/**
|
|
35
|
-
* Destinations sorted by id, then outline, so input order does not change the network.
|
|
36
|
-
* @param destinations The destinations as given.
|
|
37
|
-
* @returns A sorted copy.
|
|
38
|
-
*/
|
|
39
|
-
export declare function inCanonicalOrder(destinations: readonly SourceDestination[]): SourceDestination[];
|
|
40
|
-
export {};
|
|
41
|
-
//# sourceMappingURL=compile-layer.d.ts.map
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
// One layer's pipeline: the destinations read as the stages see them — rings normalized, footprints
|
|
2
|
-
// carved back to the walls, zones and blocking stands read crossable — then the walkable area and the
|
|
3
|
-
// route network over it.
|
|
4
|
-
import { blockersOf } from './blockers.js';
|
|
5
|
-
import { doorOwners, doorwaySides, findDoorways } from './doorways.js';
|
|
6
|
-
import { dockFor, standingBefore, stubLength, waysInto } from './entries.js';
|
|
7
|
-
import { footprintOf } from './geom/footprint.js';
|
|
8
|
-
import { cleanRing } from './geom/normalize.js';
|
|
9
|
-
import { boundsOf, pointInRing, signedArea, vertexMean } from './geom/primitives.js';
|
|
10
|
-
import { prepareWalls } from './geom/walls.js';
|
|
11
|
-
import { NETWORK_SIZES } from './network-sizes.js';
|
|
12
|
-
import { entranceOpenings } from './openings.js';
|
|
13
|
-
import { buildSteinerNetwork } from './steiner-network.js';
|
|
14
|
-
import { buildWalkableArea, carveFootprints, inBuilding, outerContoursOf, WALL_HALF_WIDTH, wallStripsOf, } from './walkable.js';
|
|
15
|
-
/**
|
|
16
|
-
* One layer's pipeline: walkable area, then route network.
|
|
17
|
-
* @param source One layer's walls and destinations.
|
|
18
|
-
* @param ops Polygon boolean backend.
|
|
19
|
-
* @param options A ready walkable area and the stage callback.
|
|
20
|
-
* @returns The layer's walkable area and route network.
|
|
21
|
-
*/
|
|
22
|
-
export function compileLayer(source, ops, options) {
|
|
23
|
-
// Walls first: the building's outline is theirs alone. The walls are read and offset once and shared
|
|
24
|
-
// by every stage below.
|
|
25
|
-
const prepared = prepareWalls(source.walls);
|
|
26
|
-
const contours = options?.walkable ? undefined : outerContoursOf(prepared.runs);
|
|
27
|
-
const outline = options?.walkable?.outline ?? ops.union(contours ?? []);
|
|
28
|
-
// A destination with neither its middle nor a corner inside the building never reaches the network:
|
|
29
|
-
// it is reported off the floor without being traced, carved or entered.
|
|
30
|
-
const offFloor = (d) => {
|
|
31
|
-
const ring = cleanRing(d.dock ?? d.polygon);
|
|
32
|
-
return outline.length > 0 && ![vertexMean(ring), ...ring].some((p) => inBuilding(outline, p));
|
|
33
|
-
};
|
|
34
|
-
const sorted = inCanonicalOrder(source.destinations);
|
|
35
|
-
// Entrances go through the walls they stand across, or the nearest wall when they stand outside;
|
|
36
|
-
// the walls stay whole. An entrance that got an opening is on the floor.
|
|
37
|
-
const opened = entranceOpenings(prepared, outline, sorted, offFloor);
|
|
38
|
-
const openedIds = new Set(opened.openings.map((o) => o.id));
|
|
39
|
-
const outside = sorted.filter((d) => offFloor(d) && !openedIds.has(d.id));
|
|
40
|
-
const shapes = [];
|
|
41
|
-
const zones = [];
|
|
42
|
-
let sized;
|
|
43
|
-
// Normalize rings once so docking grows one stub per real face; a ring that normalizes below 3
|
|
44
|
-
// vertices stays raw, visible to diagnostics. `parts` become the footprint and are dropped from
|
|
45
|
-
// the artifacts.
|
|
46
|
-
const destinations = sorted
|
|
47
|
-
.filter((d) => !outside.includes(d))
|
|
48
|
-
.map((d) => {
|
|
49
|
-
const { parts, ...rest } = d;
|
|
50
|
-
const shape = parts?.length
|
|
51
|
-
? footprintOf(parts, ops, rest.dock)
|
|
52
|
-
: { ring: null, drawn: [] };
|
|
53
|
-
const polygon = shape.ring ?? cleanRing(rest.polygon);
|
|
54
|
-
const dock = rest.dock ? cleanRing(rest.dock) : undefined;
|
|
55
|
-
const cleaned = dock && dock.length >= 3 ? { ...rest, dock } : rest;
|
|
56
|
-
const drawn = shape.drawn.length ? { ...cleaned, drawnShape: shape.drawn } : cleaned;
|
|
57
|
-
if (parts?.length && shape.how)
|
|
58
|
-
shapes.push({ id: d.id, how: shape.how, parts: parts.length });
|
|
59
|
-
return polygon.length >= 3 ? { ...drawn, polygon } : drawn;
|
|
60
|
-
})
|
|
61
|
-
.map((d, index, all) => {
|
|
62
|
-
sized ??= all.map(sizeOf);
|
|
63
|
-
if (d.crossable || !holdsAnother(sized[index], sized))
|
|
64
|
-
return d;
|
|
65
|
-
zones.push(d.id);
|
|
66
|
-
return { ...d, crossable: true };
|
|
67
|
-
});
|
|
68
|
-
// Footprints are carved back to their walls here, and every later stage sees the carved shape.
|
|
69
|
-
const shared = {
|
|
70
|
-
walls: prepared,
|
|
71
|
-
strips: wallStripsOf(ops, prepared.segments, WALL_HALF_WIDTH),
|
|
72
|
-
...(contours && { contours }),
|
|
73
|
-
};
|
|
74
|
-
// Every piece of a solid destination is carved: footprints first, so carved rings pair back by
|
|
75
|
-
// position, then the rest of the drawing. Owners are positions, not ids: ids may repeat or be
|
|
76
|
-
// blank.
|
|
77
|
-
const solid = [];
|
|
78
|
-
const rings = [];
|
|
79
|
-
const owner = [];
|
|
80
|
-
destinations.forEach((d, index) => {
|
|
81
|
-
if (d.crossable)
|
|
82
|
-
return;
|
|
83
|
-
solid.push(index);
|
|
84
|
-
rings.push(d.polygon);
|
|
85
|
-
owner.push(index);
|
|
86
|
-
});
|
|
87
|
-
// A piece of the drawing another destination stands in is ground around it, not a block.
|
|
88
|
-
const holdsOther = (block, index) => destinations.some((other, k) => k !== index && other.polygon.length >= 3 && pointInRing(block, vertexMean(other.polygon)));
|
|
89
|
-
for (const index of solid)
|
|
90
|
-
for (const block of destinations[index].drawnShape ?? []) {
|
|
91
|
-
rings.push(block);
|
|
92
|
-
owner.push(index);
|
|
93
|
-
}
|
|
94
|
-
const { rings: obstacles, clipped } = carveFootprints(rings, shared.strips, ops);
|
|
95
|
-
// The rest of the drawing is input only; it stays off the artifacts, which are in plan units.
|
|
96
|
-
let next = 0;
|
|
97
|
-
const carved = destinations.map(({ drawnShape: _block, ...rest }) => rest.crossable ? rest : { ...rest, polygon: obstacles[next++] });
|
|
98
|
-
// The rest of each drawing blocks ways in.
|
|
99
|
-
const drawnOf = (index) => (destinations[index].drawnShape ?? [])
|
|
100
|
-
.filter((ring) => !holdsOther(ring, index))
|
|
101
|
-
.map((ring) => ({ id: destinations[index].id, ring }));
|
|
102
|
-
// Read on the carved outlines, as every later stage sees them. A destination named here stops
|
|
103
|
-
// being an obstacle: its ring leaves the floor, without re-carving.
|
|
104
|
-
const passable = crossableByStanding(carved, prepared, NETWORK_SIZES, solid.flatMap(drawnOf));
|
|
105
|
-
const open = carved.map((d, index) => (passable.has(index) ? { ...d, crossable: true } : d));
|
|
106
|
-
const standing = obstacles.filter((_, index) => !passable.has(owner[index]));
|
|
107
|
-
const walkable = options?.walkable ??
|
|
108
|
-
buildWalkableArea({ walls: source.walls, obstacles: standing }, ops, WALL_HALF_WIDTH, shared);
|
|
109
|
-
options?.onStage?.('walkable', options.walkable != null);
|
|
110
|
-
// Raw walls too: doorways and ways in use their exact geometry.
|
|
111
|
-
const drawnBlocks = solid.filter((index) => !passable.has(index)).flatMap(drawnOf);
|
|
112
|
-
const network = buildSteinerNetwork(walkable.rings, open, source.walls, prepared, drawnBlocks, opened.openings);
|
|
113
|
-
options?.onStage?.('network', false);
|
|
114
|
-
const off = outside.map(({ parts: _parts, ...d }) => d);
|
|
115
|
-
// An entrance outside that opened no wall keeps what the search found.
|
|
116
|
-
const noFloor = new Map(opened.unreached.map((u) => [u.id, u]));
|
|
117
|
-
const byEntrance = new Map();
|
|
118
|
-
for (const { id, at, by, distance } of opened.openings) {
|
|
119
|
-
const entry = byEntrance.get(id) ?? {
|
|
120
|
-
id,
|
|
121
|
-
by,
|
|
122
|
-
at: [],
|
|
123
|
-
...(distance !== undefined && { distance }),
|
|
124
|
-
};
|
|
125
|
-
// A wall open to both sides is one place.
|
|
126
|
-
const seen = entry.at.some(([x, y]) => x === at[0] && y === at[1]);
|
|
127
|
-
byEntrance.set(id, seen ? entry : { ...entry, at: [...entry.at, at] });
|
|
128
|
-
}
|
|
129
|
-
const facts = {
|
|
130
|
-
wallEntries: prepared.entries,
|
|
131
|
-
shapes,
|
|
132
|
-
// Footprint rings only: a clipped auxiliary drawn block is not the footprint being cut back.
|
|
133
|
-
clipped: clipped
|
|
134
|
-
.slice(0, solid.length)
|
|
135
|
-
.flatMap((cut, index) => (cut ? [destinations[owner[index]].id] : [])),
|
|
136
|
-
crossable: [
|
|
137
|
-
...zones.map((id) => ({ id, by: 'holds-another' })),
|
|
138
|
-
...[...passable].map(([index, victims]) => ({
|
|
139
|
-
id: carved[index].id,
|
|
140
|
-
by: 'stands-before',
|
|
141
|
-
victims,
|
|
142
|
-
})),
|
|
143
|
-
],
|
|
144
|
-
openings: [...byEntrance.values()],
|
|
145
|
-
entrancesUnopened: open.flatMap((d) => (d.entrance && !openedIds.has(d.id) ? [d.id] : [])),
|
|
146
|
-
};
|
|
147
|
-
return {
|
|
148
|
-
layer: source.layer,
|
|
149
|
-
destinations: [...open, ...off],
|
|
150
|
-
walkable,
|
|
151
|
-
network: {
|
|
152
|
-
...network,
|
|
153
|
-
stats: {
|
|
154
|
-
...network.stats,
|
|
155
|
-
unreached: [
|
|
156
|
-
...network.stats.unreached,
|
|
157
|
-
...off.map((d) => noFloor.get(d.id) ?? { id: d.id, reason: 'no-floor', outside: true }),
|
|
158
|
-
],
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
facts,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Destinations a route must cross because they stand in front of another destination's only way in.
|
|
166
|
-
* Only when that destination has no way in at all, and the one in front is reachable itself.
|
|
167
|
-
* @param destinations The layer's destinations, footprints already carved.
|
|
168
|
-
* @param prepared The layer's walls as drawn, already read: the doorways are theirs.
|
|
169
|
-
* @param cfg Network sizes.
|
|
170
|
-
* @param drawn The rest of each destination's drawing, as the network will see it.
|
|
171
|
-
* @returns The positions of the destinations to walk through, each with the ids it walled in.
|
|
172
|
-
*/
|
|
173
|
-
function crossableByStanding(destinations, prepared, cfg, drawn) {
|
|
174
|
-
// Every gap in the walls, including ones a footprint stands across.
|
|
175
|
-
const doors = findDoorways(prepared);
|
|
176
|
-
const owners = doorOwners(doors, destinations, prepared.segments, cfg);
|
|
177
|
-
const blockers = blockersOf(destinations, [...prepared.segments, ...doorwaySides(doors, cfg)], doors, drawn);
|
|
178
|
-
// Usable as the network judges it: a person fits in front, and its stub docks lawfully.
|
|
179
|
-
const usable = (d, ways) => ways.some((way) => {
|
|
180
|
-
const run = stubLength(way, cfg);
|
|
181
|
-
return (way.depth >= cfg.clear &&
|
|
182
|
-
dockFor(d, way, [way.mx + way.nx * run, way.my + way.ny * run], blockers) !== null);
|
|
183
|
-
});
|
|
184
|
-
const ways = destinations.map((d) => waysInto(d, owners, blockers, cfg));
|
|
185
|
-
const reachable = destinations.map((d, index) => usable(d, ways[index]));
|
|
186
|
-
// A repeated id names none of its destinations (-1).
|
|
187
|
-
const at = new Map();
|
|
188
|
-
destinations.forEach((d, index) => at.set(d.id, at.has(d.id) ? -1 : index));
|
|
189
|
-
const passable = new Map();
|
|
190
|
-
for (const [index, victim] of destinations.entries()) {
|
|
191
|
-
if (victim.crossable || reachable[index])
|
|
192
|
-
continue;
|
|
193
|
-
// Whoever stands in the strip a person needs in front of a way in closed it.
|
|
194
|
-
for (const way of ways[index])
|
|
195
|
-
for (const id of standingBefore(way, victim.id, blockers, cfg.clear)) {
|
|
196
|
-
const who = at.get(id) ?? -1;
|
|
197
|
-
if (who < 0 || !reachable[who])
|
|
198
|
-
continue;
|
|
199
|
-
const victims = passable.get(who) ?? [];
|
|
200
|
-
if (!victims.includes(victim.id))
|
|
201
|
-
passable.set(who, [...victims, victim.id]);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return passable;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Destinations sorted by id, then outline, so input order does not change the network.
|
|
208
|
-
* @param destinations The destinations as given.
|
|
209
|
-
* @returns A sorted copy.
|
|
210
|
-
*/
|
|
211
|
-
export function inCanonicalOrder(destinations) {
|
|
212
|
-
const outline = (d) => JSON.stringify([d.polygon, d.parts ?? [], d.dock ?? []]);
|
|
213
|
-
const compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
214
|
-
return [...destinations].sort((a, b) => compare(a.id, b.id) || compare(outline(a), outline(b)));
|
|
215
|
-
}
|
|
216
|
-
function sizeOf({ polygon }) {
|
|
217
|
-
return {
|
|
218
|
-
polygon,
|
|
219
|
-
area: polygon.length < 3 ? 0 : Math.abs(signedArea(polygon)),
|
|
220
|
-
box: boundsOf(polygon),
|
|
221
|
-
middle: polygon.length < 3 ? [NaN, NaN] : vertexMean(polygon),
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Whether a smaller destination's centroid lies inside this one (a pavilion, a hall of stands).
|
|
226
|
-
* Such a destination is a zone people walk in, not an obstacle.
|
|
227
|
-
* @param d The destination.
|
|
228
|
-
* @param all Every destination on the layer.
|
|
229
|
-
* @returns True when `d` holds another destination.
|
|
230
|
-
*/
|
|
231
|
-
function holdsAnother(d, all) {
|
|
232
|
-
if (d.polygon.length < 3)
|
|
233
|
-
return false;
|
|
234
|
-
const [x0, y0, x1, y1] = d.box;
|
|
235
|
-
return all.some((other) => {
|
|
236
|
-
if (other === d || other.polygon.length < 3 || other.area >= d.area)
|
|
237
|
-
return false;
|
|
238
|
-
const [x, y] = other.middle;
|
|
239
|
-
return x >= x0 && x <= x1 && y >= y0 && y <= y1 && pointInRing(d.polygon, other.middle);
|
|
240
|
-
});
|
|
241
|
-
}
|