@expofp/wayfinding-compiler 0.0.0-bootstrap.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +446 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +3 -0
- package/dist/lib/attach.d.ts +43 -0
- package/dist/lib/attach.js +199 -0
- package/dist/lib/axes.d.ts +76 -0
- package/dist/lib/axes.js +103 -0
- package/dist/lib/blockers.d.ts +134 -0
- package/dist/lib/blockers.js +289 -0
- package/dist/lib/cell-array.d.ts +14 -0
- package/dist/lib/cell-array.js +25 -0
- package/dist/lib/centering.d.ts +14 -0
- package/dist/lib/centering.js +88 -0
- package/dist/lib/compile-layer.d.ts +41 -0
- package/dist/lib/compile-layer.js +241 -0
- package/dist/lib/compile-wayfinding.d.ts +90 -0
- package/dist/lib/compile-wayfinding.js +203 -0
- package/dist/lib/connect.d.ts +35 -0
- package/dist/lib/connect.js +206 -0
- package/dist/lib/diagnostics.d.ts +190 -0
- package/dist/lib/diagnostics.js +634 -0
- package/dist/lib/doorways.d.ts +78 -0
- package/dist/lib/doorways.js +331 -0
- package/dist/lib/emit-network.d.ts +47 -0
- package/dist/lib/emit-network.js +105 -0
- package/dist/lib/emit-v1.d.ts +31 -0
- package/dist/lib/emit-v1.js +56 -0
- package/dist/lib/entries.d.ts +123 -0
- package/dist/lib/entries.js +448 -0
- package/dist/lib/floor-grids.d.ts +48 -0
- package/dist/lib/floor-grids.js +185 -0
- package/dist/lib/framing.d.ts +20 -0
- package/dist/lib/framing.js +187 -0
- package/dist/lib/geom/boolean-ops.d.ts +21 -0
- package/dist/lib/geom/boolean-ops.js +1 -0
- package/dist/lib/geom/clipper2-ops.d.ts +7 -0
- package/dist/lib/geom/clipper2-ops.js +50 -0
- package/dist/lib/geom/footprint.d.ts +26 -0
- package/dist/lib/geom/footprint.js +203 -0
- package/dist/lib/geom/frames.d.ts +116 -0
- package/dist/lib/geom/frames.js +233 -0
- package/dist/lib/geom/mesh-outline.d.ts +13 -0
- package/dist/lib/geom/mesh-outline.js +90 -0
- package/dist/lib/geom/normalize.d.ts +28 -0
- package/dist/lib/geom/normalize.js +118 -0
- package/dist/lib/geom/point-key.d.ts +16 -0
- package/dist/lib/geom/point-key.js +15 -0
- package/dist/lib/geom/primitives.d.ts +105 -0
- package/dist/lib/geom/primitives.js +184 -0
- package/dist/lib/geom/split-crossings.d.ts +14 -0
- package/dist/lib/geom/split-crossings.js +171 -0
- package/dist/lib/geom/types.d.ts +9 -0
- package/dist/lib/geom/types.js +1 -0
- package/dist/lib/geom/walls.d.ts +31 -0
- package/dist/lib/geom/walls.js +71 -0
- package/dist/lib/graph-invariants.d.ts +49 -0
- package/dist/lib/graph-invariants.js +129 -0
- package/dist/lib/graph-types.d.ts +143 -0
- package/dist/lib/graph-types.js +7 -0
- package/dist/lib/grid-network.d.ts +20 -0
- package/dist/lib/grid-network.js +349 -0
- package/dist/lib/lattice-map.d.ts +19 -0
- package/dist/lib/lattice-map.js +92 -0
- package/dist/lib/lattice-network.d.ts +32 -0
- package/dist/lib/lattice-network.js +289 -0
- package/dist/lib/mask.d.ts +54 -0
- package/dist/lib/mask.js +210 -0
- package/dist/lib/min-heap.d.ts +24 -0
- package/dist/lib/min-heap.js +73 -0
- package/dist/lib/network-build.d.ts +21 -0
- package/dist/lib/network-build.js +4 -0
- package/dist/lib/network-graph.d.ts +80 -0
- package/dist/lib/network-graph.js +467 -0
- package/dist/lib/network-setup.d.ts +50 -0
- package/dist/lib/network-setup.js +76 -0
- package/dist/lib/network-sizes.d.ts +29 -0
- package/dist/lib/network-sizes.js +22 -0
- package/dist/lib/network-store.d.ts +173 -0
- package/dist/lib/network-store.js +430 -0
- package/dist/lib/openings.d.ts +54 -0
- package/dist/lib/openings.js +387 -0
- package/dist/lib/place-path.d.ts +47 -0
- package/dist/lib/place-path.js +346 -0
- package/dist/lib/post-passes.d.ts +31 -0
- package/dist/lib/post-passes.js +305 -0
- package/dist/lib/reachability.d.ts +58 -0
- package/dist/lib/reachability.js +220 -0
- package/dist/lib/report.d.ts +49 -0
- package/dist/lib/report.js +374 -0
- package/dist/lib/router.d.ts +111 -0
- package/dist/lib/router.js +388 -0
- package/dist/lib/runs.d.ts +92 -0
- package/dist/lib/runs.js +232 -0
- package/dist/lib/scale.d.ts +58 -0
- package/dist/lib/scale.js +185 -0
- package/dist/lib/steiner-network.d.ts +19 -0
- package/dist/lib/steiner-network.js +183 -0
- package/dist/lib/strip.d.ts +43 -0
- package/dist/lib/strip.js +200 -0
- package/dist/lib/stubs.d.ts +84 -0
- package/dist/lib/stubs.js +316 -0
- package/dist/lib/transitions.d.ts +72 -0
- package/dist/lib/transitions.js +191 -0
- package/dist/lib/types.d.ts +126 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/validate-source.d.ts +41 -0
- package/dist/lib/validate-source.js +133 -0
- package/dist/lib/walkable.d.ts +104 -0
- package/dist/lib/walkable.js +140 -0
- package/package.json +37 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Doorway } from './doorways.js';
|
|
2
|
+
import type { Ring, Segment, Vec2 } from './geom/types.js';
|
|
3
|
+
import type { PreparedWalls } from './geom/walls.js';
|
|
4
|
+
import type { RouteNetworkDestination, Unreached } from './graph-types.js';
|
|
5
|
+
/** How far from a wall an entrance outside the building still opens it, in meters. */
|
|
6
|
+
export declare const ENTRANCE_REACH = 10;
|
|
7
|
+
/** One opening: where an entrance's dock goes through a wall, and the way in it makes. */
|
|
8
|
+
export interface Opening {
|
|
9
|
+
/** The entrance's id. */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Where the dock crosses the wall: the middle of the span, on the wall's line. */
|
|
12
|
+
readonly at: Vec2;
|
|
13
|
+
/** The wall span the dock may cross, jamb to jamb. */
|
|
14
|
+
readonly span: Segment;
|
|
15
|
+
/**
|
|
16
|
+
* The way in as the network sees it: a doorway standing on the floor just past the wall, its
|
|
17
|
+
* normal out of the entrance onto the floor.
|
|
18
|
+
*/
|
|
19
|
+
readonly door: Doorway;
|
|
20
|
+
/**
|
|
21
|
+
* Inside the entrance's bounds; or, for an entrance outside the building, a doorway the walls leave
|
|
22
|
+
* in reach, else the nearest wall.
|
|
23
|
+
*/
|
|
24
|
+
readonly by: 'crossing' | 'doorway' | 'nearby';
|
|
25
|
+
/** `doorway` and `nearby` only: how far the entrance stands from it, in meters. */
|
|
26
|
+
readonly distance?: number;
|
|
27
|
+
}
|
|
28
|
+
/** The entrances' openings, and the entrances outside the building that got none. */
|
|
29
|
+
interface EntranceOpenings {
|
|
30
|
+
/** The openings made, in destination order; one per side of a wall with floor on both. */
|
|
31
|
+
readonly openings: Opening[];
|
|
32
|
+
/** Entrances outside the building that got no opening, with what was found. */
|
|
33
|
+
readonly unreached: Unreached[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The openings the entrances make. The walls stay whole; an opening says where an entrance's dock
|
|
37
|
+
* may go through one.
|
|
38
|
+
*
|
|
39
|
+
* An entrance across a wall opens every wall piece inside its bounds, through its middle: pieces on
|
|
40
|
+
* one line are one opening, and it opens onto each side with floor behind the wall. An entrance
|
|
41
|
+
* outside the building with no such piece goes in through the doorway the walls leave nearest its
|
|
42
|
+
* middle within {@link ENTRANCE_REACH}, with floor behind it and no wall between; with none, it opens
|
|
43
|
+
* the nearest wall in reach — nearest to its middle, with floor and a person's clearance behind it,
|
|
44
|
+
* and no other wall between — where its bounds project onto the wall. An opening narrower than a
|
|
45
|
+
* person is not made.
|
|
46
|
+
* @param prepared The layer's walls, read once.
|
|
47
|
+
* @param outline The building's outline.
|
|
48
|
+
* @param destinations The layer's destinations, rings as given.
|
|
49
|
+
* @param outside Whether a destination stands outside the building.
|
|
50
|
+
* @returns The openings, and the entrances outside that got none.
|
|
51
|
+
*/
|
|
52
|
+
export declare function entranceOpenings(prepared: PreparedWalls, outline: readonly Ring[], destinations: readonly RouteNetworkDestination[], outside: (d: RouteNetworkDestination) => boolean): EntranceOpenings;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=openings.d.ts.map
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
// Entrance openings: where an entrance's dock goes through a wall — the wall pieces inside its bounds,
|
|
2
|
+
// or the nearest wall with floor behind it when the entrance stands outside the building. The walls
|
|
3
|
+
// stay whole: each opening is a doorway the entrance owns, standing on the floor just past the wall,
|
|
4
|
+
// and the dock from it crosses the wall only inside the opening's span.
|
|
5
|
+
import { ON_WALL_LINE } from './doorways.js';
|
|
6
|
+
import { cleanRing } from './geom/normalize.js';
|
|
7
|
+
import { pointInRing, pointSegmentDistance, rayVsSegment, ringEdges, segmentsCross, vertexMean, } from './geom/primitives.js';
|
|
8
|
+
import { MIN_PASS, NETWORK_SIZES } from './network-sizes.js';
|
|
9
|
+
import { inBuilding, WALL_HALF_WIDTH } from './walkable.js';
|
|
10
|
+
/** How far from a wall an entrance outside the building still opens it, in meters. */
|
|
11
|
+
export const ENTRANCE_REACH = 10;
|
|
12
|
+
/** How far behind a wall the floor is looked for: past the wall's thickness, half a person in. */
|
|
13
|
+
const BEHIND = WALL_HALF_WIDTH + MIN_PASS;
|
|
14
|
+
/**
|
|
15
|
+
* The shares of a segment that lie inside a ring: cut at every edge crossing, a share is inside when
|
|
16
|
+
* its middle is.
|
|
17
|
+
* @param a Segment start.
|
|
18
|
+
* @param b Segment end.
|
|
19
|
+
* @param ring The ring.
|
|
20
|
+
* @returns The inside shares as `[t0, t1]` along the segment.
|
|
21
|
+
*/
|
|
22
|
+
function insideShares(a, b, ring) {
|
|
23
|
+
const dx = b[0] - a[0];
|
|
24
|
+
const dy = b[1] - a[1];
|
|
25
|
+
const cuts = [0, 1];
|
|
26
|
+
const hit = [0, 0];
|
|
27
|
+
for (const [x0, y0, x1, y1] of ringEdges(ring)) {
|
|
28
|
+
if (!rayVsSegment(hit, a[0], a[1], dx, dy, x0, y0, x1, y1))
|
|
29
|
+
continue;
|
|
30
|
+
const [t, u] = hit;
|
|
31
|
+
if (t > 0 && t < 1 && u >= 0 && u <= 1)
|
|
32
|
+
cuts.push(t);
|
|
33
|
+
}
|
|
34
|
+
cuts.sort((p, q) => p - q);
|
|
35
|
+
const shares = [];
|
|
36
|
+
for (let i = 0; i + 1 < cuts.length; i++) {
|
|
37
|
+
const [t0, t1] = [cuts[i], cuts[i + 1]];
|
|
38
|
+
if (t1 - t0 < 1e-9)
|
|
39
|
+
continue;
|
|
40
|
+
const t = (t0 + t1) / 2;
|
|
41
|
+
if (pointInRing(ring, [a[0] + dx * t, a[1] + dy * t]))
|
|
42
|
+
shares.push([t0, t1]);
|
|
43
|
+
}
|
|
44
|
+
return shares;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The pieces grouped by the line they lie on: a wall drawn with many vertices, or twice, is one line.
|
|
48
|
+
* @param pieces The pieces inside one entrance's bounds.
|
|
49
|
+
* @returns The groups, each led by its longest piece.
|
|
50
|
+
*/
|
|
51
|
+
function byLine(pieces) {
|
|
52
|
+
const groups = [];
|
|
53
|
+
const onLine = (lead, piece) => [piece.a, piece.b].every(([x, y]) => distanceToLine(x, y, lead.a, lead.b) <= ON_WALL_LINE);
|
|
54
|
+
for (const piece of [...pieces].sort((p, q) => length(q) - length(p))) {
|
|
55
|
+
const group = groups.find((g) => onLine(g[0], piece));
|
|
56
|
+
if (group)
|
|
57
|
+
group.push(piece);
|
|
58
|
+
else
|
|
59
|
+
groups.push([piece]);
|
|
60
|
+
}
|
|
61
|
+
return groups;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* How far the wall a segment is part of runs along the segment's line, both ways: the pieces on that
|
|
65
|
+
* line meeting end to end are one wall, however many vertices draw it.
|
|
66
|
+
* @param line The segment's line, from its first end.
|
|
67
|
+
* @param len The segment's length.
|
|
68
|
+
* @param segments Every wall segment of the floor.
|
|
69
|
+
* @returns The wall's ends, as distances along the line.
|
|
70
|
+
*/
|
|
71
|
+
function spanAlong(line, len, segments) {
|
|
72
|
+
const [ox, oy] = line.origin;
|
|
73
|
+
const onLine = ([x, y]) => Math.abs(line.ux * (y - oy) - line.uy * (x - ox)) <= ON_WALL_LINE;
|
|
74
|
+
const along = ([x, y]) => (x - ox) * line.ux + (y - oy) * line.uy;
|
|
75
|
+
const pieces = [];
|
|
76
|
+
for (const [x0, y0, x1, y1] of segments) {
|
|
77
|
+
if (!onLine([x0, y0]) || !onLine([x1, y1]))
|
|
78
|
+
continue;
|
|
79
|
+
const a = along([x0, y0]);
|
|
80
|
+
const b = along([x1, y1]);
|
|
81
|
+
pieces.push([Math.min(a, b), Math.max(a, b)]);
|
|
82
|
+
}
|
|
83
|
+
let [lo, hi] = [0, len];
|
|
84
|
+
for (let grown = true; grown;) {
|
|
85
|
+
grown = false;
|
|
86
|
+
for (const [a, b] of pieces) {
|
|
87
|
+
if (a > hi + ON_WALL_LINE || b < lo - ON_WALL_LINE || (a >= lo && b <= hi))
|
|
88
|
+
continue;
|
|
89
|
+
lo = Math.min(lo, a);
|
|
90
|
+
hi = Math.max(hi, b);
|
|
91
|
+
grown = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return [lo, hi];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Distance from a point to the line through two points.
|
|
98
|
+
* @param x Point x.
|
|
99
|
+
* @param y Point y.
|
|
100
|
+
* @param a One point on the line.
|
|
101
|
+
* @param b Another.
|
|
102
|
+
* @returns The distance.
|
|
103
|
+
*/
|
|
104
|
+
function distanceToLine(x, y, a, b) {
|
|
105
|
+
const len = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1;
|
|
106
|
+
return Math.abs(((b[0] - a[0]) * (y - a[1]) - (b[1] - a[1]) * (x - a[0])) / len);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A piece's length.
|
|
110
|
+
* @param piece The piece.
|
|
111
|
+
* @returns Its length.
|
|
112
|
+
*/
|
|
113
|
+
function length(piece) {
|
|
114
|
+
return Math.hypot(piece.b[0] - piece.a[0], piece.b[1] - piece.a[1]);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* An opening on a wall's line, from `lo` to `hi` along it, onto the side the sign names.
|
|
118
|
+
* @param id The entrance's id.
|
|
119
|
+
* @param line The wall's line.
|
|
120
|
+
* @param lo Where the span starts along the line.
|
|
121
|
+
* @param hi Where it ends.
|
|
122
|
+
* @param side Which side of the line the floor is on: the sign of the line's left normal.
|
|
123
|
+
* @param by How the wall was found.
|
|
124
|
+
* @param distance `nearby` only: how far the entrance stands from the wall.
|
|
125
|
+
* @returns The opening.
|
|
126
|
+
*/
|
|
127
|
+
function opening(id, line, lo, hi, side, by, distance) {
|
|
128
|
+
const { origin, ux, uy } = line;
|
|
129
|
+
const mid = (lo + hi) / 2;
|
|
130
|
+
const at = (t) => [origin[0] + ux * t, origin[1] + uy * t];
|
|
131
|
+
const nx = -uy * side;
|
|
132
|
+
const ny = ux * side;
|
|
133
|
+
const shift = ([x, y]) => [
|
|
134
|
+
+(x + nx * BEHIND).toFixed(2),
|
|
135
|
+
+(y + ny * BEHIND).toFixed(2),
|
|
136
|
+
];
|
|
137
|
+
const [a, b, m] = [at(lo), at(hi), at(mid)];
|
|
138
|
+
const [ga, gb] = [shift(a), shift(b)];
|
|
139
|
+
return {
|
|
140
|
+
id,
|
|
141
|
+
at: [+m[0].toFixed(2), +m[1].toFixed(2)],
|
|
142
|
+
span: [a[0], a[1], b[0], b[1]],
|
|
143
|
+
door: { gap: [ga[0], ga[1], gb[0], gb[1]], mid: shift(m), nx, ny },
|
|
144
|
+
by,
|
|
145
|
+
...(distance !== undefined && { distance }),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Distance between a ring and a segment that do not cross: from the nearer of the ring's vertices to
|
|
150
|
+
* the segment, or of the segment's ends to the ring's edges.
|
|
151
|
+
* @param ring The ring.
|
|
152
|
+
* @param seg The segment.
|
|
153
|
+
* @returns The distance.
|
|
154
|
+
*/
|
|
155
|
+
function ringSegmentDistance(ring, seg) {
|
|
156
|
+
let best = Infinity;
|
|
157
|
+
for (const [x, y] of ring)
|
|
158
|
+
best = Math.min(best, pointSegmentDistance(x, y, ...seg));
|
|
159
|
+
for (const edge of ringEdges(ring))
|
|
160
|
+
for (const [x, y] of [
|
|
161
|
+
[seg[0], seg[1]],
|
|
162
|
+
[seg[2], seg[3]],
|
|
163
|
+
])
|
|
164
|
+
best = Math.min(best, pointSegmentDistance(x, y, ...edge));
|
|
165
|
+
return best;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Whether two segments are the same piece of wall, by their ends.
|
|
169
|
+
* @param a One segment.
|
|
170
|
+
* @param b The other.
|
|
171
|
+
* @returns True when the ends coincide.
|
|
172
|
+
*/
|
|
173
|
+
function sameSegment(a, b) {
|
|
174
|
+
return a.every((v, i) => v === b[i]);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* How far a ray from a point on a wall runs before it meets another wall: the wall it leaves, and
|
|
178
|
+
* anything within the wall's own thickness, does not count.
|
|
179
|
+
* @param from Where the ray starts.
|
|
180
|
+
* @param dx Ray direction x.
|
|
181
|
+
* @param dy Ray direction y.
|
|
182
|
+
* @param segments What it may meet.
|
|
183
|
+
* @param skip The wall the ray leaves.
|
|
184
|
+
* @returns The distance, or `Infinity`.
|
|
185
|
+
*/
|
|
186
|
+
function freeAhead(from, dx, dy, segments, skip) {
|
|
187
|
+
const hit = [0, 0];
|
|
188
|
+
let best = Infinity;
|
|
189
|
+
for (const seg of segments) {
|
|
190
|
+
if (sameSegment(seg, skip) || !rayVsSegment(hit, from[0], from[1], dx, dy, ...seg))
|
|
191
|
+
continue;
|
|
192
|
+
const [t, u] = hit;
|
|
193
|
+
if (u >= 0 && u <= 1 && t > WALL_HALF_WIDTH && t < best)
|
|
194
|
+
best = t;
|
|
195
|
+
}
|
|
196
|
+
return best;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The doorways the walls leave in the building outlines: the stretches of an outline's edges no wall
|
|
200
|
+
* runs along, at least a person wide.
|
|
201
|
+
* @param outline The building's outline.
|
|
202
|
+
* @param segments The walls.
|
|
203
|
+
* @returns The gaps, edge by edge.
|
|
204
|
+
*/
|
|
205
|
+
function outlineGaps(outline, segments) {
|
|
206
|
+
const gaps = [];
|
|
207
|
+
for (const ring of outline) {
|
|
208
|
+
for (const edge of ringEdges(ring)) {
|
|
209
|
+
const len = Math.hypot(edge[2] - edge[0], edge[3] - edge[1]);
|
|
210
|
+
if (len < 2 * MIN_PASS)
|
|
211
|
+
continue;
|
|
212
|
+
const line = {
|
|
213
|
+
origin: [edge[0], edge[1]],
|
|
214
|
+
ux: (edge[2] - edge[0]) / len,
|
|
215
|
+
uy: (edge[3] - edge[1]) / len,
|
|
216
|
+
};
|
|
217
|
+
const along = (x, y) => (x - line.origin[0]) * line.ux + (y - line.origin[1]) * line.uy;
|
|
218
|
+
const off = (x, y) => Math.abs((x - line.origin[0]) * line.uy - (y - line.origin[1]) * line.ux);
|
|
219
|
+
const at = (t) => [line.origin[0] + line.ux * t, line.origin[1] + line.uy * t];
|
|
220
|
+
const covered = [];
|
|
221
|
+
for (const [x0, y0, x1, y1] of segments) {
|
|
222
|
+
if (off(x0, y0) > ON_WALL_LINE || off(x1, y1) > ON_WALL_LINE)
|
|
223
|
+
continue;
|
|
224
|
+
const [a, b] = [along(x0, y0), along(x1, y1)].sort((p, q) => p - q);
|
|
225
|
+
if (b > 0 && a < len)
|
|
226
|
+
covered.push([Math.max(0, a), Math.min(len, b)]);
|
|
227
|
+
}
|
|
228
|
+
covered.sort((p, q) => p[0] - q[0] || p[1] - q[1]);
|
|
229
|
+
let from = 0;
|
|
230
|
+
for (const [start, end] of [...covered, [len, len]]) {
|
|
231
|
+
if (start - from >= 2 * MIN_PASS) {
|
|
232
|
+
const [a, b] = [at(from), at(start)];
|
|
233
|
+
gaps.push({ line, lo: from, hi: start, segment: [a[0], a[1], b[0], b[1]] });
|
|
234
|
+
}
|
|
235
|
+
from = Math.max(from, end);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return gaps;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The openings the entrances make. The walls stay whole; an opening says where an entrance's dock
|
|
243
|
+
* may go through one.
|
|
244
|
+
*
|
|
245
|
+
* An entrance across a wall opens every wall piece inside its bounds, through its middle: pieces on
|
|
246
|
+
* one line are one opening, and it opens onto each side with floor behind the wall. An entrance
|
|
247
|
+
* outside the building with no such piece goes in through the doorway the walls leave nearest its
|
|
248
|
+
* middle within {@link ENTRANCE_REACH}, with floor behind it and no wall between; with none, it opens
|
|
249
|
+
* the nearest wall in reach — nearest to its middle, with floor and a person's clearance behind it,
|
|
250
|
+
* and no other wall between — where its bounds project onto the wall. An opening narrower than a
|
|
251
|
+
* person is not made.
|
|
252
|
+
* @param prepared The layer's walls, read once.
|
|
253
|
+
* @param outline The building's outline.
|
|
254
|
+
* @param destinations The layer's destinations, rings as given.
|
|
255
|
+
* @param outside Whether a destination stands outside the building.
|
|
256
|
+
* @returns The openings, and the entrances outside that got none.
|
|
257
|
+
*/
|
|
258
|
+
export function entranceOpenings(prepared, outline, destinations, outside) {
|
|
259
|
+
const openings = [];
|
|
260
|
+
const unreached = [];
|
|
261
|
+
const floorAt = ({ door }) => inBuilding(outline, door.mid);
|
|
262
|
+
let gaps = null;
|
|
263
|
+
for (const d of destinations) {
|
|
264
|
+
if (!d.entrance)
|
|
265
|
+
continue;
|
|
266
|
+
const ring = cleanRing(d.dock ?? d.polygon);
|
|
267
|
+
if (ring.length < 3)
|
|
268
|
+
continue;
|
|
269
|
+
const pieces = [];
|
|
270
|
+
for (const [x0, y0, x1, y1] of prepared.segments)
|
|
271
|
+
for (const [t0, t1] of insideShares([x0, y0], [x1, y1], ring))
|
|
272
|
+
pieces.push({
|
|
273
|
+
a: [x0 + (x1 - x0) * t0, y0 + (y1 - y0) * t0],
|
|
274
|
+
b: [x0 + (x1 - x0) * t1, y0 + (y1 - y0) * t1],
|
|
275
|
+
});
|
|
276
|
+
let made = 0;
|
|
277
|
+
for (const group of byLine(pieces)) {
|
|
278
|
+
const lead = group[0];
|
|
279
|
+
const len = length(lead);
|
|
280
|
+
const line = {
|
|
281
|
+
origin: lead.a,
|
|
282
|
+
ux: (lead.b[0] - lead.a[0]) / len,
|
|
283
|
+
uy: (lead.b[1] - lead.a[1]) / len,
|
|
284
|
+
};
|
|
285
|
+
const along = group.flatMap((p) => [p.a, p.b].map(([x, y]) => (x - lead.a[0]) * line.ux + (y - lead.a[1]) * line.uy));
|
|
286
|
+
const lo = Math.min(...along);
|
|
287
|
+
const hi = Math.max(...along);
|
|
288
|
+
if (hi - lo < 2 * MIN_PASS)
|
|
289
|
+
continue;
|
|
290
|
+
for (const side of [1, -1]) {
|
|
291
|
+
const onto = opening(d.id, line, lo, hi, side, 'crossing');
|
|
292
|
+
if (!floorAt(onto))
|
|
293
|
+
continue;
|
|
294
|
+
openings.push(onto);
|
|
295
|
+
made++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (made || !outside(d))
|
|
299
|
+
continue;
|
|
300
|
+
const center = vertexMean(ring);
|
|
301
|
+
// Outside the building and across no wall: through the doorway the walls leave first, so a route
|
|
302
|
+
// goes through a wall only where there is no way past it.
|
|
303
|
+
let doorway;
|
|
304
|
+
gaps ??= outlineGaps(outline, prepared.segments);
|
|
305
|
+
for (const { line, lo, hi, segment } of gaps) {
|
|
306
|
+
const distance = ringSegmentDistance(ring, segment);
|
|
307
|
+
if (distance > ENTRANCE_REACH)
|
|
308
|
+
continue;
|
|
309
|
+
const fromMiddle = pointSegmentDistance(center[0], center[1], ...segment);
|
|
310
|
+
const side = -line.uy * (center[0] - line.origin[0]) + line.ux * (center[1] - line.origin[1]) > 0
|
|
311
|
+
? -1
|
|
312
|
+
: 1;
|
|
313
|
+
const candidate = opening(d.id, line, lo, hi, side, 'doorway', +distance.toFixed(2));
|
|
314
|
+
const sight = [candidate.at[0], candidate.at[1], center[0], center[1]];
|
|
315
|
+
if ((doorway && fromMiddle >= doorway.fromMiddle) ||
|
|
316
|
+
!floorAt(candidate) ||
|
|
317
|
+
prepared.segments.some((wall) => segmentsCross(sight, wall)))
|
|
318
|
+
continue;
|
|
319
|
+
doorway = { opening: candidate, fromMiddle };
|
|
320
|
+
}
|
|
321
|
+
if (doorway) {
|
|
322
|
+
openings.push(doorway.opening);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
// With no doorway in reach: the nearest wall with floor behind it.
|
|
326
|
+
const candidates = [];
|
|
327
|
+
let nearest = Infinity;
|
|
328
|
+
for (const seg of prepared.segments) {
|
|
329
|
+
const distance = ringSegmentDistance(ring, seg);
|
|
330
|
+
nearest = Math.min(nearest, distance);
|
|
331
|
+
if (distance <= ENTRANCE_REACH)
|
|
332
|
+
candidates.push({
|
|
333
|
+
seg,
|
|
334
|
+
distance,
|
|
335
|
+
fromMiddle: pointSegmentDistance(center[0], center[1], ...seg),
|
|
336
|
+
index: candidates.length,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
// The wall under the entrance's middle first: a wide entrance may touch a side wall with a corner.
|
|
340
|
+
// Ties keep the walls' order, which is canonical.
|
|
341
|
+
candidates.sort((p, q) => p.fromMiddle - q.fromMiddle || p.index - q.index);
|
|
342
|
+
let found;
|
|
343
|
+
for (const { seg, distance } of candidates) {
|
|
344
|
+
const origin = [seg[0], seg[1]];
|
|
345
|
+
const len = Math.hypot(seg[2] - seg[0], seg[3] - seg[1]);
|
|
346
|
+
if (!len)
|
|
347
|
+
continue;
|
|
348
|
+
const line = { origin, ux: (seg[2] - seg[0]) / len, uy: (seg[3] - seg[1]) / len };
|
|
349
|
+
const project = ([x, y]) => (x - origin[0]) * line.ux + (y - origin[1]) * line.uy;
|
|
350
|
+
// The whole wall the segment is part of: a wall drawn with many vertices is one.
|
|
351
|
+
const [first, last] = spanAlong(line, len, prepared.segments);
|
|
352
|
+
const clamp = (v) => Math.max(first, Math.min(last, v));
|
|
353
|
+
const c = clamp(project(center));
|
|
354
|
+
const along = ring.map(project);
|
|
355
|
+
// The bounds' shadow on the wall, at least a person wide around the entrance's middle.
|
|
356
|
+
const lo = clamp(Math.min(...along, c - BEHIND));
|
|
357
|
+
const hi = clamp(Math.max(...along, c + BEHIND));
|
|
358
|
+
if (hi - lo < 2 * MIN_PASS)
|
|
359
|
+
continue;
|
|
360
|
+
// The side away from the entrance: the left normal's sign that points from the entrance over
|
|
361
|
+
// the wall.
|
|
362
|
+
const side = -line.uy * (center[0] - origin[0]) + line.ux * (center[1] - origin[1]) > 0 ? -1 : 1;
|
|
363
|
+
const candidate = opening(d.id, line, lo, hi, side, 'nearby', +distance.toFixed(2));
|
|
364
|
+
const { at, door } = candidate;
|
|
365
|
+
// Floor with room for a person behind the wall, and no other wall between the entrance and the
|
|
366
|
+
// opening.
|
|
367
|
+
const sight = [at[0], at[1], center[0], center[1]];
|
|
368
|
+
if (!floorAt(candidate) ||
|
|
369
|
+
freeAhead(at, door.nx, door.ny, prepared.segments, seg) < NETWORK_SIZES.clear ||
|
|
370
|
+
prepared.segments.some((other) => !sameSegment(other, seg) && segmentsCross(sight, other)))
|
|
371
|
+
continue;
|
|
372
|
+
found = candidate;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
if (found)
|
|
376
|
+
openings.push(found);
|
|
377
|
+
else
|
|
378
|
+
unreached.push({
|
|
379
|
+
id: d.id,
|
|
380
|
+
reason: 'no-floor',
|
|
381
|
+
outside: true,
|
|
382
|
+
...(isFinite(nearest) && { nearestWall: +nearest.toFixed(2) }),
|
|
383
|
+
...(candidates.length > 0 && { floorBehind: false }),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return { openings, unreached };
|
|
387
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Frame } from './geom/frames.js';
|
|
2
|
+
import type { Segment, Vec2 } from './geom/types.js';
|
|
3
|
+
import type { NetworkSizes } from './network-sizes.js';
|
|
4
|
+
import type { NetworkStore } from './network-store.js';
|
|
5
|
+
import type { Router } from './router.js';
|
|
6
|
+
import { type Placement } from './runs.js';
|
|
7
|
+
/** A stub tip a path ends at: the point and the stub's direction. */
|
|
8
|
+
export interface TipTarget {
|
|
9
|
+
/** The tip. */
|
|
10
|
+
readonly at: Vec2;
|
|
11
|
+
/** Unit direction of the stub, out of its destination. */
|
|
12
|
+
readonly normal: Vec2;
|
|
13
|
+
/** The direction the stub's destination is drawn in. */
|
|
14
|
+
readonly frame: Frame;
|
|
15
|
+
}
|
|
16
|
+
/** What laying a path shares with the growth: the network, the raster, and where the tips stand. */
|
|
17
|
+
interface PathLaying {
|
|
18
|
+
/** The network; extended in place. */
|
|
19
|
+
readonly store: NetworkStore;
|
|
20
|
+
/** The raster router the paths were walked on. */
|
|
21
|
+
readonly router: Router;
|
|
22
|
+
/** Network sizes. */
|
|
23
|
+
readonly cfg: NetworkSizes;
|
|
24
|
+
/** What the runs are laid on. */
|
|
25
|
+
readonly place: Placement;
|
|
26
|
+
/** The tip standing at a cell a path may end at. */
|
|
27
|
+
readonly tipAt: ReadonlyMap<number, TipTarget>;
|
|
28
|
+
/** 1 for each router cell a network line passes: where a raster path may end. */
|
|
29
|
+
readonly onNetwork: Uint8Array;
|
|
30
|
+
/** Adds a segment to the network and claims the cells it passes. */
|
|
31
|
+
readonly add: (seg: Segment) => void;
|
|
32
|
+
/** Claims cells for a segment, so a path ending in one of them ties to it; its own cells by default. */
|
|
33
|
+
readonly claim: (seg: Segment, cells?: Iterable<number>) => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Lays a path of cells, `head` first, its ends tied to what stands at them: the tip given, or the
|
|
37
|
+
* network line claiming the cell. False when an end could not be tied; nothing is laid then.
|
|
38
|
+
*/
|
|
39
|
+
export type LayPath = (cells: readonly number[], head: TipTarget | null, free?: (seg: Segment) => boolean) => boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Builds the function that lays one path.
|
|
42
|
+
* @param shared What it shares with the growth.
|
|
43
|
+
* @returns The function.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createPathPlacer(shared: PathLaying): LayPath;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=place-path.d.ts.map
|