@expofp/wayfinding-compiler 0.0.0-bootstrap.0 → 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/centering.d.ts +0 -14
- package/dist/lib/centering.js +0 -88
- 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/grid-network.d.ts +0 -20
- package/dist/lib/grid-network.js +0 -349
- package/dist/lib/lattice-map.d.ts +0 -19
- package/dist/lib/lattice-map.js +0 -92
- package/dist/lib/lattice-network.d.ts +0 -32
- package/dist/lib/lattice-network.js +0 -289
- 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-setup.d.ts +0 -50
- package/dist/lib/network-setup.js +0 -76
- 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
|
@@ -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
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { type Diagnostic } from './diagnostics.js';
|
|
2
|
-
import type { RouteNetwork } from './graph-types.js';
|
|
3
|
-
import { type Reachability } from './reachability.js';
|
|
4
|
-
import { type CompileReport } from './report.js';
|
|
5
|
-
import type { SourceDestination, WayfindingSource, WfV1Graph } from './types.js';
|
|
6
|
-
import type { WalkableArea } from './walkable.js';
|
|
7
|
-
/** Progress of a compile, reported as each stage finishes. */
|
|
8
|
-
export interface CompileProgress {
|
|
9
|
-
/** The stage just finished. */
|
|
10
|
-
readonly stage: 'walkable' | 'network' | 'transitions';
|
|
11
|
-
/** The layer it belongs to; empty for whole-plan stages. */
|
|
12
|
-
readonly layer: string;
|
|
13
|
-
/** Stages completed so far, out of {@link total}. */
|
|
14
|
-
readonly done: number;
|
|
15
|
-
/** Stages this compile will run in all. */
|
|
16
|
-
readonly total: number;
|
|
17
|
-
/** True when the caller handed the stage its artifact and it did not run. */
|
|
18
|
-
readonly cached: boolean;
|
|
19
|
-
}
|
|
20
|
-
/** What a compile may be given besides the plan. */
|
|
21
|
-
export interface CompileOptions {
|
|
22
|
-
/** Called as each stage finishes. */
|
|
23
|
-
readonly onProgress?: (progress: CompileProgress) => void;
|
|
24
|
-
/**
|
|
25
|
-
* Walkable areas to reuse, by layer id, from an earlier compile of the same geometry: only the
|
|
26
|
-
* route network is then built. A layer with no entry builds its own.
|
|
27
|
-
*/
|
|
28
|
-
readonly walkable?: Readonly<Record<string, WalkableArea>>;
|
|
29
|
-
}
|
|
30
|
-
/** One layer's stage artifacts and raw inputs, as plain serializable data. */
|
|
31
|
-
export interface LayerDebug {
|
|
32
|
-
/** Globally unique layer id. */
|
|
33
|
-
readonly layer: string;
|
|
34
|
-
/** The wall polylines as given (flat `[x0,y0,…]`). */
|
|
35
|
-
readonly rawWalls: readonly (readonly number[])[];
|
|
36
|
-
/** The destinations as the stages saw them, carved. */
|
|
37
|
-
readonly destinations: readonly SourceDestination[];
|
|
38
|
-
/** Walkable area. */
|
|
39
|
-
readonly walkable: WalkableArea;
|
|
40
|
-
/** Route network. */
|
|
41
|
-
readonly network: RouteNetwork;
|
|
42
|
-
}
|
|
43
|
-
/** What a compile returns: the graph and the report on it. Plain serializable data. */
|
|
44
|
-
export interface CompileResult {
|
|
45
|
-
/** The emitted graph, lines not cut where they meet; empty, never null, when nothing was built. */
|
|
46
|
-
readonly graph: WfV1Graph;
|
|
47
|
-
/** Outcome, counts, and which POI are not connected and why. */
|
|
48
|
-
readonly report: CompileReport;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* `compileWayfinding(source, { debug: true })`: the result plus stage artifacts; JSON-serializable.
|
|
52
|
-
*/
|
|
53
|
-
export interface WayfindingDebugResult extends CompileResult {
|
|
54
|
-
/** Per-layer stage artifacts. */
|
|
55
|
-
readonly layers: LayerDebug[];
|
|
56
|
-
/** What the emitted graph connects: its parts, and the part every POI is bound to. */
|
|
57
|
-
readonly reachability: Reachability;
|
|
58
|
-
/** Every technical fact of the compile, stage by stage. */
|
|
59
|
-
readonly diagnostics: readonly Diagnostic[];
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Compile plan geometry into a routing graph in the shape of the v1 format, with a report on the
|
|
63
|
-
* result. Its lines are not cut where they meet: the converter that builds wf.data.js cuts them.
|
|
64
|
-
*
|
|
65
|
-
* Pure and deterministic. Per layer: walkable area, route network, v1 emit. A plan it cannot read
|
|
66
|
-
* comes back `not-built` with the reason in the report; it never throws over the plan.
|
|
67
|
-
* @param source Per-layer walls and destinations.
|
|
68
|
-
* @param options What the compile may be given besides the plan.
|
|
69
|
-
* @param options.onProgress Called as each stage finishes.
|
|
70
|
-
* @param options.walkable Walkable areas by layer, to reuse instead of rebuilding.
|
|
71
|
-
* @returns The compiled graph and its report.
|
|
72
|
-
*/
|
|
73
|
-
export declare function compileWayfinding(source: WayfindingSource, options?: CompileOptions): CompileResult;
|
|
74
|
-
/**
|
|
75
|
-
* Debug mode: the same pass also returns {@link WayfindingDebugResult}.
|
|
76
|
-
* @param source Per-layer walls and destinations.
|
|
77
|
-
* @param options `debug: true` and the usual options.
|
|
78
|
-
* @param options.debug Return the stage artifacts. `{ invariants: true }` also checks the emitted
|
|
79
|
-
* graph against the geometric invariants (`graph-invariants.ts`) — a pairwise sweep that costs a
|
|
80
|
-
* sizable share of the compile on a large plan, so it is opted into, not implied by `debug`.
|
|
81
|
-
* @param options.onProgress Called as each stage finishes.
|
|
82
|
-
* @param options.walkable Walkable areas by layer, to reuse instead of rebuilding.
|
|
83
|
-
* @returns The compiled graph and its report, plus the stage artifacts and facts.
|
|
84
|
-
*/
|
|
85
|
-
export declare function compileWayfinding(source: WayfindingSource, options: CompileOptions & {
|
|
86
|
-
debug: true | {
|
|
87
|
-
invariants?: boolean;
|
|
88
|
-
};
|
|
89
|
-
}): WayfindingDebugResult;
|
|
90
|
-
//# sourceMappingURL=compile-wayfinding.d.ts.map
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { CellsRefusedError } from './cell-array.js';
|
|
2
|
-
import { compileLayer, inCanonicalOrder } from './compile-layer.js';
|
|
3
|
-
import { fromEmit, fromGraphInvariants, fromNetwork, fromNormalize, fromOversize, fromReachability, fromScale, fromSourceIssues, fromStops, fromTransitions, fromWalkable, } from './diagnostics.js';
|
|
4
|
-
import { emitLayerV1 } from './emit-v1.js';
|
|
5
|
-
import { createClipper2Ops } from './geom/clipper2-ops.js';
|
|
6
|
-
import { boundsOf } from './geom/primitives.js';
|
|
7
|
-
import { checkGraphInvariants } from './graph-invariants.js';
|
|
8
|
-
import { NETWORK_SIZES } from './network-sizes.js';
|
|
9
|
-
import { readReachability } from './reachability.js';
|
|
10
|
-
import { buildReport } from './report.js';
|
|
11
|
-
import { destinationsInPlanUnits, graphInPlanUnits, networkInPlanUnits, orphansInPlanUnits, sourceInMeters, walkableScaled, } from './scale.js';
|
|
12
|
-
import { emitTransitionEdges, planTransitions } from './transitions.js';
|
|
13
|
-
import { destinationFinite, validateWayfindingSource, wallsFinite } from './validate-source.js';
|
|
14
|
-
/**
|
|
15
|
-
* Most raster cells a floor's extent may take: six times the largest plan known, and well short of
|
|
16
|
-
* what a browser tab holds. A larger floor is drawn in the wrong units or holds a stray drawing.
|
|
17
|
-
*/
|
|
18
|
-
const MAX_FLOOR_CELLS = 40e6;
|
|
19
|
-
/**
|
|
20
|
-
* The box a layer's drawing spans: walls and every destination ring.
|
|
21
|
-
* @param layer One layer, in meters.
|
|
22
|
-
* @returns Its bounds; infinite when nothing is drawn.
|
|
23
|
-
*/
|
|
24
|
-
function extentOf(layer) {
|
|
25
|
-
const points = [];
|
|
26
|
-
for (const run of layer.walls)
|
|
27
|
-
for (let i = 0; i + 1 < run.length; i += 2)
|
|
28
|
-
points.push([run[i], run[i + 1]]);
|
|
29
|
-
for (const d of layer.destinations)
|
|
30
|
-
points.push(...d.polygon, ...(d.parts ?? []).flat(), ...(d.dock ?? []));
|
|
31
|
-
return boundsOf(points);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* A layer's destinations in canonical order, each under an id no other one on the layer shares: a blank
|
|
35
|
-
* or repeated id gets its rank among its namesakes, so every destination docks by its own geometry.
|
|
36
|
-
* @param destinations The layer's destinations, in source order.
|
|
37
|
-
* @returns The destinations in canonical order, and the id each source position compiles under.
|
|
38
|
-
*/
|
|
39
|
-
function uniquelyNamed(destinations) {
|
|
40
|
-
const sorted = inCanonicalOrder(destinations);
|
|
41
|
-
const count = new Map();
|
|
42
|
-
const rank = new Map();
|
|
43
|
-
const idOf = new Map();
|
|
44
|
-
for (const d of destinations)
|
|
45
|
-
count.set(d.id, (count.get(d.id) ?? 0) + 1);
|
|
46
|
-
for (const d of sorted) {
|
|
47
|
-
const n = rank.get(d.id) ?? 0;
|
|
48
|
-
rank.set(d.id, n + 1);
|
|
49
|
-
idOf.set(d, d.id && count.get(d.id) === 1 ? d.id : `${d.id}\u0000${n}`);
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
destinations: sorted.map((d) => ({ ...d, id: idOf.get(d) })),
|
|
53
|
-
ids: destinations.map((d) => idOf.get(d)),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Shared implementation of both overloads.
|
|
58
|
-
* @param source Per-layer walls and destinations.
|
|
59
|
-
* @param options Compile options.
|
|
60
|
-
* @param options.debug Return the stage artifacts; `{ invariants: true }` also checks the graph.
|
|
61
|
-
* @param options.onProgress Called as each stage finishes.
|
|
62
|
-
* @param options.walkable Walkable areas by layer, to reuse instead of rebuilding.
|
|
63
|
-
* @returns The compiled graph and its report, plus the stage artifacts and facts in debug mode.
|
|
64
|
-
*/
|
|
65
|
-
export function compileWayfinding(source, options) {
|
|
66
|
-
const issues = validateWayfindingSource(source);
|
|
67
|
-
const input = [...fromSourceIssues(issues), ...fromScale(source.unitsPerMeter)];
|
|
68
|
-
// An unreadable plan is reported, not thrown. An error naming a layer stops that floor alone.
|
|
69
|
-
if (issues.some(({ severity, layer }) => severity === 'error' && layer === undefined)) {
|
|
70
|
-
const graph = { lines: [], lineEnds: [] };
|
|
71
|
-
const reachability = { parts: [], poi: [], unownedEnds: [], sharedEnds: [] };
|
|
72
|
-
const report = buildReport(input, reachability, source);
|
|
73
|
-
return options?.debug
|
|
74
|
-
? { graph, report, layers: [], reachability, diagnostics: input }
|
|
75
|
-
: { graph, report };
|
|
76
|
-
}
|
|
77
|
-
// What the input stage proved unreadable is left out and stays in the report as the input fact:
|
|
78
|
-
// a broken wall skips its floor, a broken destination or transition is dropped alone. Keyed by
|
|
79
|
-
// position, not id — two layers may share an id, and only the broken one is touched.
|
|
80
|
-
const layerBroken = source.layers.map((layer) => !wallsFinite(layer.walls));
|
|
81
|
-
const poiBroken = source.layers.map((layer) => layer.destinations.flatMap((d, index) => (destinationFinite(d) ? [] : [index])));
|
|
82
|
-
const brokenTransitions = new Set(issues.flatMap(({ code, index }) => code === 'transition-coordinate-not-finite' && index !== undefined ? [index] : []));
|
|
83
|
-
const ops = createClipper2Ops();
|
|
84
|
-
const graph = { lines: [], lineEnds: [] };
|
|
85
|
-
const layers = [];
|
|
86
|
-
// Stages work in meters: one conversion in here, one back to plan units on the way out.
|
|
87
|
-
const unit = source.unitsPerMeter ?? 1;
|
|
88
|
-
// Canonical order and unique ids before transition stops resolve: a stop takes the smallest
|
|
89
|
-
// destination it stands in, the first of equals.
|
|
90
|
-
const scaled = sourceInMeters(source, unit);
|
|
91
|
-
const named = scaled.layers.map((layer) => uniquelyNamed(layer.destinations));
|
|
92
|
-
const metric = {
|
|
93
|
-
...scaled,
|
|
94
|
-
...(scaled.transitions && {
|
|
95
|
-
transitions: scaled.transitions.filter((_, index) => !brokenTransitions.has(index)),
|
|
96
|
-
}),
|
|
97
|
-
layers: scaled.layers.map((layer, index) => {
|
|
98
|
-
const broken = poiBroken[index];
|
|
99
|
-
if (!broken.length)
|
|
100
|
-
return { ...layer, destinations: named[index].destinations };
|
|
101
|
-
const left = new Set(broken.map((at) => named[index].ids[at]));
|
|
102
|
-
return {
|
|
103
|
-
...layer,
|
|
104
|
-
destinations: named[index].destinations.filter(({ id }) => !left.has(id)),
|
|
105
|
-
};
|
|
106
|
-
}),
|
|
107
|
-
};
|
|
108
|
-
// Stops resolve to footprints before the layers compile: a stop in the open gets a pad to dock
|
|
109
|
-
// from.
|
|
110
|
-
const plan = planTransitions(metric);
|
|
111
|
-
const networks = new Map();
|
|
112
|
-
const stages = [];
|
|
113
|
-
// Two stages per layer plus the transition pass.
|
|
114
|
-
const total = plan.layers.length * 2 + 1;
|
|
115
|
-
let done = 0;
|
|
116
|
-
const progress = (stage, layer, cached) => {
|
|
117
|
-
done++;
|
|
118
|
-
options?.onProgress?.({ stage, layer, done, total, cached });
|
|
119
|
-
};
|
|
120
|
-
for (const [index, layerSource] of plan.layers.entries()) {
|
|
121
|
-
// Counted against the plan's own POI, without transition pads; the plan's layers come in order.
|
|
122
|
-
const own = source.layers[index];
|
|
123
|
-
// A wall coordinate that is not a number: the input fact carries the reason, the floor is skipped.
|
|
124
|
-
if (layerBroken[index]) {
|
|
125
|
-
progress('walkable', layerSource.layer, false);
|
|
126
|
-
progress('network', layerSource.layer, false);
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
const [x0, y0, x1, y1] = extentOf(layerSource);
|
|
130
|
-
const cells = ((x1 - x0 + 2) / NETWORK_SIZES.cs) * ((y1 - y0 + 2) / NETWORK_SIZES.cs);
|
|
131
|
-
// Too large to rasterize: reported and skipped, as a floor with no floor is.
|
|
132
|
-
if (Number.isFinite(cells) && cells > MAX_FLOOR_CELLS) {
|
|
133
|
-
stages.push(...fromOversize(layerSource.layer, x1 - x0, y1 - y0, own.destinations.length));
|
|
134
|
-
progress('walkable', layerSource.layer, false);
|
|
135
|
-
progress('network', layerSource.layer, false);
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
// A caller's walkable area is in plan units.
|
|
139
|
-
const cached = options?.walkable?.[layerSource.layer];
|
|
140
|
-
const reported = new Set();
|
|
141
|
-
let compiled;
|
|
142
|
-
try {
|
|
143
|
-
compiled = compileLayer(layerSource, ops, {
|
|
144
|
-
...(cached && { walkable: walkableScaled(cached, 1 / unit, false) }),
|
|
145
|
-
onStage: (stage, ready) => {
|
|
146
|
-
reported.add(stage);
|
|
147
|
-
progress(stage, layerSource.layer, ready);
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
if (!(error instanceof CellsRefusedError))
|
|
153
|
-
throw error;
|
|
154
|
-
// Under the cell limit, yet more than this device holds: reported like any floor too large.
|
|
155
|
-
stages.push(...fromOversize(layerSource.layer, x1 - x0, y1 - y0, own.destinations.length));
|
|
156
|
-
for (const stage of ['walkable', 'network'])
|
|
157
|
-
if (!reported.has(stage))
|
|
158
|
-
progress(stage, layerSource.layer, false);
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
const collapsed = emitLayerV1(compiled.network, compiled.layer, graph);
|
|
162
|
-
networks.set(compiled.layer, compiled.network);
|
|
163
|
-
stages.push(...fromNormalize(own, compiled.facts, named[index].ids, unit), ...fromWalkable(compiled.layer, compiled.walkable.stats, own.destinations.length), ...fromNetwork(own, compiled.network.stats, unit, named[index].ids), ...fromEmit(own, collapsed, named[index].ids));
|
|
164
|
-
if (options?.debug) {
|
|
165
|
-
layers.push({
|
|
166
|
-
layer: compiled.layer,
|
|
167
|
-
rawWalls: own.walls,
|
|
168
|
-
destinations: destinationsInPlanUnits(compiled.destinations, unit),
|
|
169
|
-
walkable: walkableScaled(compiled.walkable, unit, true),
|
|
170
|
-
network: networkInPlanUnits(compiled.network, unit),
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const shafts = emitTransitionEdges(plan, networks, graph);
|
|
175
|
-
// Orphans in plan units, like everything emitted.
|
|
176
|
-
const transitionOrphans = orphansInPlanUnits([...plan.orphans, ...shafts.orphans], unit);
|
|
177
|
-
progress('transitions', '', false);
|
|
178
|
-
const emitted = graphInPlanUnits(graph, unit);
|
|
179
|
-
// Read off the emitted graph and the plan as given, in plan units, as they are handed out.
|
|
180
|
-
const reachability = readReachability(emitted, source);
|
|
181
|
-
const diagnostics = [
|
|
182
|
-
...input,
|
|
183
|
-
...stages,
|
|
184
|
-
...fromStops(plan, unit),
|
|
185
|
-
...fromTransitions(transitionOrphans, shafts.singleFloor, unit),
|
|
186
|
-
...fromReachability(reachability, [...input, ...stages]),
|
|
187
|
-
];
|
|
188
|
-
// The report folds the compile's facts; the graph invariants are debug-only and stay out of it.
|
|
189
|
-
const report = buildReport(diagnostics, reachability, source);
|
|
190
|
-
if (!options?.debug)
|
|
191
|
-
return { graph: emitted, report };
|
|
192
|
-
// The pairwise sweep costs a sizable share of the compile on a large plan: opted into alone.
|
|
193
|
-
const invariants = typeof options.debug === 'object' && options.debug.invariants
|
|
194
|
-
? fromGraphInvariants(checkGraphInvariants(emitted, layers))
|
|
195
|
-
: [];
|
|
196
|
-
return {
|
|
197
|
-
graph: emitted,
|
|
198
|
-
report,
|
|
199
|
-
layers,
|
|
200
|
-
reachability,
|
|
201
|
-
diagnostics: [...diagnostics, ...invariants],
|
|
202
|
-
};
|
|
203
|
-
}
|
package/dist/lib/connect.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { Segment } from './geom/types.js';
|
|
2
|
-
import type { NetworkSizes } from './network-sizes.js';
|
|
3
|
-
import type { NetworkStore } from './network-store.js';
|
|
4
|
-
import { type LayPath, type TipTarget } from './place-path.js';
|
|
5
|
-
import type { Router } from './router.js';
|
|
6
|
-
import type { Placement } from './runs.js';
|
|
7
|
-
/** Lays raster paths into the network and grows the pieces together. */
|
|
8
|
-
export interface Connector {
|
|
9
|
-
/** Adds a segment and claims the cells it passes. */
|
|
10
|
-
add(seg: Segment): void;
|
|
11
|
-
/** 1 for each router cell a network line passes: where a raster path may end. */
|
|
12
|
-
readonly onNetwork: Uint8Array;
|
|
13
|
-
/** Lays a path of cells, `head` first, its ends tied to what stands at them. */
|
|
14
|
-
readonly layPath: LayPath;
|
|
15
|
-
/**
|
|
16
|
-
* One growth from the largest piece to every other piece and every lone tip the floor connects
|
|
17
|
-
* to it; what the largest piece cannot reach grows a tree of its own.
|
|
18
|
-
* @param tips Tips no line stands at yet.
|
|
19
|
-
* @returns How many pieces the growth and the ties could not join: the pieces left beyond the
|
|
20
|
-
* first, 0 when all are one.
|
|
21
|
-
*/
|
|
22
|
-
connect(tips: readonly TipTarget[]): {
|
|
23
|
-
untied: number;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Builds the connector.
|
|
28
|
-
* @param store The network.
|
|
29
|
-
* @param router The raster router.
|
|
30
|
-
* @param cfg Network sizes.
|
|
31
|
-
* @param place What the runs are laid on.
|
|
32
|
-
* @returns The connector.
|
|
33
|
-
*/
|
|
34
|
-
export declare function createConnector(store: NetworkStore, router: Router, cfg: NetworkSizes, place: Placement): Connector;
|
|
35
|
-
//# sourceMappingURL=connect.d.ts.map
|