@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/geom/walls.js
DELETED
|
@@ -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
|
package/dist/lib/graph-types.js
DELETED
|
@@ -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;
|
package/dist/lib/mask.d.ts
DELETED
|
@@ -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
|
-
}
|