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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +2 -445
  2. package/package.json +3 -34
  3. package/dist/index.d.ts +0 -14
  4. package/dist/index.js +0 -3
  5. package/dist/lib/attach.d.ts +0 -43
  6. package/dist/lib/attach.js +0 -199
  7. package/dist/lib/axes.d.ts +0 -76
  8. package/dist/lib/axes.js +0 -103
  9. package/dist/lib/blockers.d.ts +0 -134
  10. package/dist/lib/blockers.js +0 -289
  11. package/dist/lib/cell-array.d.ts +0 -14
  12. package/dist/lib/cell-array.js +0 -25
  13. package/dist/lib/compile-layer.d.ts +0 -41
  14. package/dist/lib/compile-layer.js +0 -241
  15. package/dist/lib/compile-wayfinding.d.ts +0 -90
  16. package/dist/lib/compile-wayfinding.js +0 -203
  17. package/dist/lib/connect.d.ts +0 -35
  18. package/dist/lib/connect.js +0 -206
  19. package/dist/lib/diagnostics.d.ts +0 -190
  20. package/dist/lib/diagnostics.js +0 -634
  21. package/dist/lib/doorways.d.ts +0 -78
  22. package/dist/lib/doorways.js +0 -331
  23. package/dist/lib/emit-network.d.ts +0 -47
  24. package/dist/lib/emit-network.js +0 -105
  25. package/dist/lib/emit-v1.d.ts +0 -31
  26. package/dist/lib/emit-v1.js +0 -56
  27. package/dist/lib/entries.d.ts +0 -123
  28. package/dist/lib/entries.js +0 -448
  29. package/dist/lib/floor-grids.d.ts +0 -48
  30. package/dist/lib/floor-grids.js +0 -185
  31. package/dist/lib/framing.d.ts +0 -20
  32. package/dist/lib/framing.js +0 -187
  33. package/dist/lib/geom/boolean-ops.d.ts +0 -21
  34. package/dist/lib/geom/boolean-ops.js +0 -1
  35. package/dist/lib/geom/clipper2-ops.d.ts +0 -7
  36. package/dist/lib/geom/clipper2-ops.js +0 -50
  37. package/dist/lib/geom/footprint.d.ts +0 -26
  38. package/dist/lib/geom/footprint.js +0 -203
  39. package/dist/lib/geom/frames.d.ts +0 -116
  40. package/dist/lib/geom/frames.js +0 -233
  41. package/dist/lib/geom/mesh-outline.d.ts +0 -13
  42. package/dist/lib/geom/mesh-outline.js +0 -90
  43. package/dist/lib/geom/normalize.d.ts +0 -28
  44. package/dist/lib/geom/normalize.js +0 -118
  45. package/dist/lib/geom/point-key.d.ts +0 -16
  46. package/dist/lib/geom/point-key.js +0 -15
  47. package/dist/lib/geom/primitives.d.ts +0 -105
  48. package/dist/lib/geom/primitives.js +0 -184
  49. package/dist/lib/geom/split-crossings.d.ts +0 -14
  50. package/dist/lib/geom/split-crossings.js +0 -171
  51. package/dist/lib/geom/types.d.ts +0 -9
  52. package/dist/lib/geom/types.js +0 -1
  53. package/dist/lib/geom/walls.d.ts +0 -31
  54. package/dist/lib/geom/walls.js +0 -71
  55. package/dist/lib/graph-invariants.d.ts +0 -49
  56. package/dist/lib/graph-invariants.js +0 -129
  57. package/dist/lib/graph-types.d.ts +0 -143
  58. package/dist/lib/graph-types.js +0 -7
  59. package/dist/lib/mask.d.ts +0 -54
  60. package/dist/lib/mask.js +0 -210
  61. package/dist/lib/min-heap.d.ts +0 -24
  62. package/dist/lib/min-heap.js +0 -73
  63. package/dist/lib/network-build.d.ts +0 -21
  64. package/dist/lib/network-build.js +0 -4
  65. package/dist/lib/network-graph.d.ts +0 -80
  66. package/dist/lib/network-graph.js +0 -467
  67. package/dist/lib/network-sizes.d.ts +0 -29
  68. package/dist/lib/network-sizes.js +0 -22
  69. package/dist/lib/network-store.d.ts +0 -173
  70. package/dist/lib/network-store.js +0 -430
  71. package/dist/lib/openings.d.ts +0 -54
  72. package/dist/lib/openings.js +0 -387
  73. package/dist/lib/place-path.d.ts +0 -47
  74. package/dist/lib/place-path.js +0 -346
  75. package/dist/lib/post-passes.d.ts +0 -31
  76. package/dist/lib/post-passes.js +0 -305
  77. package/dist/lib/reachability.d.ts +0 -58
  78. package/dist/lib/reachability.js +0 -220
  79. package/dist/lib/report.d.ts +0 -49
  80. package/dist/lib/report.js +0 -374
  81. package/dist/lib/router.d.ts +0 -111
  82. package/dist/lib/router.js +0 -388
  83. package/dist/lib/runs.d.ts +0 -92
  84. package/dist/lib/runs.js +0 -232
  85. package/dist/lib/scale.d.ts +0 -58
  86. package/dist/lib/scale.js +0 -185
  87. package/dist/lib/steiner-network.d.ts +0 -19
  88. package/dist/lib/steiner-network.js +0 -183
  89. package/dist/lib/strip.d.ts +0 -43
  90. package/dist/lib/strip.js +0 -200
  91. package/dist/lib/stubs.d.ts +0 -84
  92. package/dist/lib/stubs.js +0 -316
  93. package/dist/lib/transitions.d.ts +0 -72
  94. package/dist/lib/transitions.js +0 -191
  95. package/dist/lib/types.d.ts +0 -126
  96. package/dist/lib/types.js +0 -1
  97. package/dist/lib/validate-source.d.ts +0 -41
  98. package/dist/lib/validate-source.js +0 -133
  99. package/dist/lib/walkable.d.ts +0 -104
  100. package/dist/lib/walkable.js +0 -140
@@ -1,203 +0,0 @@
1
- import { meshOutline } from './mesh-outline.js';
2
- import { cleanRing } from './normalize.js';
3
- import { boundsOf, outerRingsOf, signedArea, wound } from './primitives.js';
4
- /** Most vertices an axis-aligned traced outline may have to be carved as itself (an L or a T fits). */
5
- const MAX_OUTLINE_VERTICES = 12;
6
- /** How far an edge may lean off an axis and still count as axis-aligned, in meters. */
7
- const AXIS_EPS = 0.05;
8
- /**
9
- * Most vertices a tilted hull may have to be carved as itself: a rotated stand or a chamfered room fits,
10
- * a traced circle does not.
11
- */
12
- const MAX_TILTED_VERTICES = 6;
13
- /**
14
- * Share of its convex hull the pieces must fill to be carved as that hull; an L or a crescent leaves
15
- * aisle the hull would swallow.
16
- */
17
- const HULL_FILL = 0.95;
18
- /**
19
- * Most pieces a mesh may have to be traced; a finer mesh is a drawing (lettering, a logo) and is boxed,
20
- * since tracing it can take minutes. An outline of V corners triangulates into V - 2 pieces.
21
- */
22
- const MAX_MESH_PARTS = 64;
23
- /**
24
- * The outer rings of a boolean result; holes are dropped, so a footprint is carved solid.
25
- * @param rings Outers and holes mixed.
26
- * @returns The outer rings, largest first.
27
- */
28
- function outerRings(rings) {
29
- return outerRingsOf(rings).sort((a, b) => Math.abs(signedArea(b)) - Math.abs(signedArea(a)));
30
- }
31
- /**
32
- * Whether an outline is a few rectangles joined (an L-shaped lounge): short and axis-aligned.
33
- * @param ring The normalized outline.
34
- * @returns True for a short, axis-aligned loop.
35
- */
36
- function isRectilinear(ring) {
37
- if (ring.length < 4 || ring.length > MAX_OUTLINE_VERTICES)
38
- return false;
39
- for (let i = 0; i < ring.length; i++) {
40
- const [ax, ay] = ring[i];
41
- const [bx, by] = ring[(i + 1) % ring.length];
42
- if (Math.abs(bx - ax) > AXIS_EPS && Math.abs(by - ay) > AXIS_EPS)
43
- return false;
44
- }
45
- return true;
46
- }
47
- /**
48
- * The convex hull (Andrew's monotone chain). Read instead of the union, whose mesh seams (zero-area
49
- * spikes, micro-steps) would count as corners.
50
- * @param points The vertices.
51
- * @returns The hull, counter-clockwise.
52
- */
53
- function convexHull(points) {
54
- const sorted = [...points].sort((a, b) => a[0] - b[0] || a[1] - b[1]);
55
- if (sorted.length < 3)
56
- return sorted;
57
- const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
58
- const half = (pts) => {
59
- const out = [];
60
- for (const p of pts) {
61
- while (out.length >= 2 && cross(out[out.length - 2], out[out.length - 1], p) <= 0)
62
- out.pop();
63
- out.push(p);
64
- }
65
- out.pop();
66
- return out;
67
- };
68
- return [...half(sorted), ...half([...sorted].reverse())];
69
- }
70
- /**
71
- * The axis-aligned bounds of all pieces.
72
- * @param parts The pieces.
73
- * @returns A four-vertex ring, or null when the pieces enclose no area.
74
- */
75
- function boundsRing(parts) {
76
- const [minX, minY, maxX, maxY] = boundsOf(parts.flat());
77
- if (!(maxX > minX) || !(maxY > minY))
78
- return null;
79
- return [
80
- [minX, minY],
81
- [maxX, minY],
82
- [maxX, maxY],
83
- [minX, maxY],
84
- ];
85
- }
86
- /**
87
- * The ring a route arrives at — the drawing joined with the dock rectangle — and the other drawn blocks.
88
- *
89
- * A piece identical to the dock is skipped: its shared edges would cancel out of the mesh outline. When
90
- * the drawing falls into blocks, the ring is the block the dock stands in; the rest come back in `drawn`
91
- * for the caller to carve.
92
- * @param parts The raw pieces, positioned, in meters.
93
- * @param ops Boolean backend.
94
- * @param dock The rectangle routes arrive at, if any.
95
- * @returns The ring to carve, how it was read, and the drawn blocks it does not cover (only with a
96
- * dock).
97
- */
98
- export function footprintOf(parts, ops, dock) {
99
- const drawing = (dock ? parts.filter((part) => !sameRing(part, dock)) : parts).filter((part) => part.length >= 3);
100
- const { ring: traced, how, blocks } = tracedRing(drawing, ops);
101
- if (!dock)
102
- return { ring: traced, ...(traced && { how }), drawn: [] };
103
- if (!traced)
104
- return { ring: cleanRing(dock), how: 'dock', drawn: [] };
105
- // The block the dock stands in is the body.
106
- const body = blocks.filter((ring) => shareOf(ops, ring, dock) > 0);
107
- const merged = outerRings(cleanedUnion(ops, [wound(dock), ...(body.length ? body : [traced])]));
108
- // Union pieces are disjoint: the one holding most of the dock is the one it stands in.
109
- const ring = merged.length <= 1
110
- ? (merged[0] ?? traced)
111
- : merged.reduce((best, r) => (shareOf(ops, r, dock) > shareOf(ops, best, dock) ? r : best));
112
- // Blocks outside the ring are still obstacles; blocks inside it would only be carved twice.
113
- return { ring, how, drawn: blocks.filter((block) => !covers(ops, ring, block)) };
114
- }
115
- /**
116
- * Whether a ring covers a block whole.
117
- * @param ops Boolean backend.
118
- * @param ring The ring.
119
- * @param block The block.
120
- * @returns True when nothing of the block lies outside.
121
- */
122
- function covers(ops, ring, block) {
123
- const outside = ops
124
- .difference([wound(block)], [wound(ring)])
125
- .map((r) => cleanRing(r))
126
- .filter((r) => r.length >= 3);
127
- return outside.length === 0;
128
- }
129
- /**
130
- * How much of the dock a ring covers.
131
- * @param ops Boolean backend.
132
- * @param ring The ring.
133
- * @param dock The rectangle.
134
- * @returns The shared area.
135
- */
136
- function shareOf(ops, ring, dock) {
137
- return ops.area(ops.intersect([wound(ring)], [wound(dock)]));
138
- }
139
- /**
140
- * A union with the seam vertices it leaves where rings met cleaned away.
141
- * @param ops Boolean backend.
142
- * @param rings The rings to union.
143
- * @returns Outers and holes mixed.
144
- */
145
- function cleanedUnion(ops, rings) {
146
- return ops
147
- .union(rings)
148
- .map((ring) => cleanRing(ring))
149
- .filter((ring) => ring.length >= 3);
150
- }
151
- /**
152
- * Same vertices in the same order. Exact on purpose: the host scales one array into `dock` and `parts`,
153
- * so copies match to the bit, and a tolerance would drop pieces that merely resemble the dock.
154
- * @param a One ring.
155
- * @param b The other.
156
- * @returns Whether they are identical.
157
- */
158
- function sameRing(a, b) {
159
- return a.length === b.length && a.every((p, i) => p[0] === b[i][0] && p[1] === b[i][1]);
160
- }
161
- /**
162
- * The shape the pieces cover: the mesh outline as is, however many corners. A mesh whose outline does
163
- * not close falls back to its hull when it fills it, a short axis-aligned trace, or its box.
164
- * @param usable The pieces, at least three vertices each.
165
- * @param ops Boolean backend.
166
- * @returns The ring (null when the pieces enclose no area), how it was read, and the blocks the
167
- * mesh outline falls into, largest first.
168
- */
169
- function tracedRing(usable, ops) {
170
- if (usable.length === 0)
171
- return { ring: null, how: 'traced', blocks: [] };
172
- // Carved exactly: a larger shape would take the aisle beside it.
173
- const boundary = meshOutline(usable);
174
- const blocks = boundary ? outerRings(cleanedUnion(ops, boundary)) : [];
175
- // Several pieces keep the largest: one destination is one place to walk to.
176
- if (blocks.length)
177
- return { ring: blocks[0], how: 'traced', blocks };
178
- return { ...fallbackRing(usable, ops), blocks };
179
- }
180
- /**
181
- * The shape of a mesh whose outline does not close: its hull when it fills it, a short axis-aligned
182
- * trace, or its box.
183
- * @param usable The pieces, at least three vertices each.
184
- * @param ops Boolean backend.
185
- * @returns The ring (null when the pieces enclose no area) and how it was read.
186
- */
187
- function fallbackRing(usable, ops) {
188
- // Pieces' area against the hull's settles convex shapes without a boolean; a simple full hull is carved.
189
- const hull = convexHull(usable.flat());
190
- const hullArea = Math.abs(signedArea(hull));
191
- const covered = usable.reduce((sum, part) => sum + Math.abs(signedArea(part)), 0);
192
- if (hullArea > 0 && covered >= HULL_FILL * hullArea)
193
- return hull.length >= 3 && hull.length <= MAX_TILTED_VERTICES
194
- ? { ring: hull, how: 'hull' }
195
- : { ring: boundsRing(usable), how: 'bounds' };
196
- // Not hull-filling: an L, a T, gapped pieces or a drawing. Traced only while the mesh is small.
197
- if (usable.length > MAX_MESH_PARTS)
198
- return { ring: boundsRing(usable), how: 'bounds-oversized' };
199
- const outers = outerRings(cleanedUnion(ops, usable));
200
- if (outers.length === 1 && isRectilinear(outers[0]))
201
- return { ring: outers[0], how: 'rectilinear' };
202
- return { ring: boundsRing(usable), how: 'bounds' };
203
- }
@@ -1,116 +0,0 @@
1
- import type { Ring, Segment, Vec2 } from './types.js';
2
- /** A pair of axes: the first as a unit vector, the second a quarter turn on. */
3
- export interface Frame {
4
- /** The first axis's x. */
5
- readonly cos: number;
6
- /** The first axis's y. */
7
- readonly sin: number;
8
- }
9
- /** The plan's own axes. */
10
- export declare const PLAN: Frame;
11
- /**
12
- * A point in a frame's coordinates.
13
- * @param frame The frame.
14
- * @param x Plan x.
15
- * @param y Plan y.
16
- * @returns The point as `[u, v]` along the frame's axes.
17
- */
18
- export declare function toFrame(frame: Frame, x: number, y: number): Vec2;
19
- /**
20
- * A point of a frame back on the plan.
21
- * @param frame The frame.
22
- * @param u Coordinate along the first axis.
23
- * @param v Coordinate along the second.
24
- * @returns The point in plan coordinates.
25
- */
26
- export declare function fromFrame(frame: Frame, u: number, v: number): Vec2;
27
- /**
28
- * A point of a frame in plan coordinates, snapped to the centimeter grid like every network point.
29
- * @param frame The frame.
30
- * @param u Coordinate along the first axis.
31
- * @param v Coordinate along the second.
32
- * @returns The point, on the centimeter grid.
33
- */
34
- export declare function placeIn(frame: Frame, u: number, v: number): Vec2;
35
- /**
36
- * Rings turned into a frame's coordinates.
37
- * @param rings Rings in plan coordinates.
38
- * @param frame The frame.
39
- * @returns The rings in frame coordinates.
40
- */
41
- export declare function turnedInto(rings: readonly Ring[], frame: Frame): Ring[];
42
- /**
43
- * The frame with an axis nearest to a direction.
44
- * @param frames The frames to choose from.
45
- * @param dx Direction x.
46
- * @param dy Direction y.
47
- * @returns The frame's index.
48
- */
49
- export declare function nearestFrame(frames: readonly Frame[], dx: number, dy: number): number;
50
- /**
51
- * The nearest frame, if the direction is within what the raster can tell over `reach`; a stand set at
52
- * its own angle runs along none.
53
- * @param frames The frames to choose from.
54
- * @param dx Direction x.
55
- * @param dy Direction y.
56
- * @param resolution The raster cell.
57
- * @param reach The length the direction is read over.
58
- * @returns The index of that frame, or -1.
59
- */
60
- export declare function frameRunning(frames: readonly Frame[], dx: number, dy: number, resolution: number, reach: number): number;
61
- /**
62
- * The right-angled way between two points in a frame: one leg when they share an axis within
63
- * `NODE_SNAP`, else two legs through whichever corner is free. No diagonals: they read as mistakes.
64
- * @param frame The frame the legs run along.
65
- * @param a Start.
66
- * @param b End.
67
- * @param free Whether a leg may be laid.
68
- * @returns The legs, or null when neither corner is free.
69
- */
70
- export declare function legsBetween(frame: Frame, a: Vec2, b: Vec2, free: (leg: Segment) => boolean): Segment[] | null;
71
- /**
72
- * The way from a point onto a network line: one straight leg along a frame axis when it lands within
73
- * `NODE_SNAP` of the nearest point, else the right-angled way to the nearest point.
74
- * @param frame The frame the legs run along.
75
- * @param from Start.
76
- * @param line The segment to join.
77
- * @param nearest The point of `line` nearest to `from`.
78
- * @param free Whether a leg may be laid.
79
- * @returns The legs and where they meet the line, or null when no way is free.
80
- */
81
- export declare function linkOnto(frame: Frame, from: Vec2, line: Segment, nearest: Vec2, free: (leg: Segment) => boolean): {
82
- legs: Segment[];
83
- at: Vec2;
84
- } | null;
85
- /**
86
- * Where two lines meet.
87
- * @param a A point of the first line.
88
- * @param da Its direction.
89
- * @param b A point of the second line.
90
- * @param db Its direction.
91
- * @returns The point and its parameter along each direction, or null when parallel.
92
- */
93
- export declare function linesMeet(a: Vec2, da: Vec2, b: Vec2, db: Vec2): {
94
- at: Vec2;
95
- ta: number;
96
- tb: number;
97
- } | null;
98
- /** One direction a layer is drawn in, and the outlines drawn in it. */
99
- export interface Direction {
100
- /** Its axes. */
101
- readonly frame: Frame;
102
- /** The outlines drawn in it. */
103
- readonly rings: Ring[];
104
- }
105
- /**
106
- * The directions a layer's destinations are drawn in, {@link PLAN} first.
107
- *
108
- * Each destination is read by its longest side, longest sides first. A side joins a direction it is
109
- * within `resolution / length` of (what the raster can tell apart), else opens a new one. A direction
110
- * with a single destination is a stand set at an angle and is dropped.
111
- * @param rings The destinations' outlines.
112
- * @param resolution The raster cell.
113
- * @returns The directions, {@link PLAN} first.
114
- */
115
- export declare function framesOf(rings: readonly Ring[], resolution: number): Direction[];
116
- //# sourceMappingURL=frames.d.ts.map
@@ -1,233 +0,0 @@
1
- // The directions a layer is drawn in: a turned hall gets axes of its own so the network runs along its
2
- // aisles instead of stair-stepping across them.
3
- import { NODE_SNAP } from '../graph-types.js';
4
- import { snapCm } from './point-key.js';
5
- import { rayVsSegment } from './primitives.js';
6
- /** The plan's own axes. */
7
- export const PLAN = { cos: 1, sin: 0 };
8
- const QUARTER = Math.PI / 2;
9
- /**
10
- * A point in a frame's coordinates.
11
- * @param frame The frame.
12
- * @param x Plan x.
13
- * @param y Plan y.
14
- * @returns The point as `[u, v]` along the frame's axes.
15
- */
16
- export function toFrame(frame, x, y) {
17
- return [x * frame.cos + y * frame.sin, y * frame.cos - x * frame.sin];
18
- }
19
- /**
20
- * A point of a frame back on the plan.
21
- * @param frame The frame.
22
- * @param u Coordinate along the first axis.
23
- * @param v Coordinate along the second.
24
- * @returns The point in plan coordinates.
25
- */
26
- export function fromFrame(frame, u, v) {
27
- return [u * frame.cos - v * frame.sin, u * frame.sin + v * frame.cos];
28
- }
29
- /**
30
- * A point of a frame in plan coordinates, snapped to the centimeter grid like every network point.
31
- * @param frame The frame.
32
- * @param u Coordinate along the first axis.
33
- * @param v Coordinate along the second.
34
- * @returns The point, on the centimeter grid.
35
- */
36
- export function placeIn(frame, u, v) {
37
- const [x, y] = fromFrame(frame, u, v);
38
- return [snapCm(x), snapCm(y)];
39
- }
40
- /**
41
- * Rings turned into a frame's coordinates.
42
- * @param rings Rings in plan coordinates.
43
- * @param frame The frame.
44
- * @returns The rings in frame coordinates.
45
- */
46
- export function turnedInto(rings, frame) {
47
- return rings.map((ring) => ring.map((p) => toFrame(frame, p[0], p[1])));
48
- }
49
- /**
50
- * The frame with an axis nearest to a direction.
51
- * @param frames The frames to choose from.
52
- * @param dx Direction x.
53
- * @param dy Direction y.
54
- * @returns The frame's index.
55
- */
56
- export function nearestFrame(frames, dx, dy) {
57
- let best = 0;
58
- let least = Infinity;
59
- frames.forEach((frame, i) => {
60
- const [u, v] = toFrame(frame, dx, dy);
61
- const off = Math.min(Math.abs(u), Math.abs(v));
62
- if (off < least) {
63
- least = off;
64
- best = i;
65
- }
66
- });
67
- return best;
68
- }
69
- /**
70
- * The nearest frame, if the direction is within what the raster can tell over `reach`; a stand set at
71
- * its own angle runs along none.
72
- * @param frames The frames to choose from.
73
- * @param dx Direction x.
74
- * @param dy Direction y.
75
- * @param resolution The raster cell.
76
- * @param reach The length the direction is read over.
77
- * @returns The index of that frame, or -1.
78
- */
79
- export function frameRunning(frames, dx, dy, resolution, reach) {
80
- const i = nearestFrame(frames, dx, dy);
81
- const len = Math.hypot(dx, dy) || 1;
82
- const [u, v] = toFrame(frames[i], dx / len, dy / len);
83
- return Math.min(Math.abs(u), Math.abs(v)) <= resolution / reach ? i : -1;
84
- }
85
- /**
86
- * Whether the way between two points runs along neither axis of a frame.
87
- * @param frame The frame.
88
- * @param a One point.
89
- * @param b The other.
90
- * @param eps Tolerance off an axis.
91
- * @returns True when both coordinates differ by more than `eps`.
92
- */
93
- function turnsIn(frame, a, b, eps) {
94
- const [au, av] = toFrame(frame, a[0], a[1]);
95
- const [bu, bv] = toFrame(frame, b[0], b[1]);
96
- return Math.abs(bu - au) > eps && Math.abs(bv - av) > eps;
97
- }
98
- /**
99
- * The right-angled way between two points in a frame: one leg when they share an axis within
100
- * `NODE_SNAP`, else two legs through whichever corner is free. No diagonals: they read as mistakes.
101
- * @param frame The frame the legs run along.
102
- * @param a Start.
103
- * @param b End.
104
- * @param free Whether a leg may be laid.
105
- * @returns The legs, or null when neither corner is free.
106
- */
107
- export function legsBetween(frame, a, b, free) {
108
- const ways = turnsIn(frame, a, b, NODE_SNAP)
109
- ? [cornerOf(frame, a, b), cornerOf(frame, b, a)].map((c) => [
110
- [a[0], a[1], c[0], c[1]],
111
- [c[0], c[1], b[0], b[1]],
112
- ])
113
- : [[[a[0], a[1], b[0], b[1]]]];
114
- return ways.find((legs) => legs.every(free)) ?? null;
115
- }
116
- /**
117
- * The way from a point onto a network line: one straight leg along a frame axis when it lands within
118
- * `NODE_SNAP` of the nearest point, else the right-angled way to the nearest point.
119
- * @param frame The frame the legs run along.
120
- * @param from Start.
121
- * @param line The segment to join.
122
- * @param nearest The point of `line` nearest to `from`.
123
- * @param free Whether a leg may be laid.
124
- * @returns The legs and where they meet the line, or null when no way is free.
125
- */
126
- export function linkOnto(frame, from, line, nearest, free) {
127
- const away = Math.hypot(nearest[0] - from[0], nearest[1] - from[1]);
128
- const hit = [0, 0];
129
- for (const [dx, dy] of [
130
- [frame.cos, frame.sin],
131
- [-frame.sin, frame.cos],
132
- ]) {
133
- if (!rayVsSegment(hit, from[0], from[1], dx, dy, line[0], line[1], line[2], line[3]))
134
- continue;
135
- const [t, u] = hit;
136
- if (u < 0 || u > 1 || Math.abs(Math.abs(t) - away) > NODE_SNAP)
137
- continue;
138
- const at = [snapCm(from[0] + dx * t), snapCm(from[1] + dy * t)];
139
- const leg = [from[0], from[1], at[0], at[1]];
140
- if (free(leg))
141
- return { legs: [leg], at };
142
- }
143
- const legs = legsBetween(frame, from, nearest, free);
144
- return legs && { legs, at: nearest };
145
- }
146
- /**
147
- * The corner of the way from `a` along the first axis, then the second to `b`.
148
- * @param frame The frame.
149
- * @param a Start.
150
- * @param b End.
151
- * @returns The corner, on the centimeter grid.
152
- */
153
- function cornerOf(frame, a, b) {
154
- const [, va] = toFrame(frame, a[0], a[1]);
155
- const [ub] = toFrame(frame, b[0], b[1]);
156
- return placeIn(frame, ub, va);
157
- }
158
- /**
159
- * Where two lines meet.
160
- * @param a A point of the first line.
161
- * @param da Its direction.
162
- * @param b A point of the second line.
163
- * @param db Its direction.
164
- * @returns The point and its parameter along each direction, or null when parallel.
165
- */
166
- export function linesMeet(a, da, b, db) {
167
- const hit = [0, 0];
168
- if (!rayVsSegment(hit, a[0], a[1], da[0], da[1], b[0], b[1], b[0] + db[0], b[1] + db[1]))
169
- return null;
170
- const [ta, tb] = hit;
171
- return { at: [snapCm(a[0] + da[0] * ta), snapCm(a[1] + da[1] * ta)], ta, tb };
172
- }
173
- /**
174
- * The direction of a side, modulo a quarter turn.
175
- * @param a One end.
176
- * @param b The other.
177
- * @returns Its angle in `[0, π/2)`.
178
- */
179
- function directionOf(a, b) {
180
- return ((Math.atan2(b[1] - a[1], b[0] - a[0]) % QUARTER) + QUARTER) % QUARTER;
181
- }
182
- /**
183
- * The turn between two directions, modulo a quarter turn.
184
- * @param from One angle.
185
- * @param to The other.
186
- * @returns The signed turn `to − from`, in `[−π/4, π/4)`.
187
- */
188
- function turn(from, to) {
189
- return ((((to - from + QUARTER / 2) % QUARTER) + QUARTER) % QUARTER) - QUARTER / 2;
190
- }
191
- /**
192
- * The directions a layer's destinations are drawn in, {@link PLAN} first.
193
- *
194
- * Each destination is read by its longest side, longest sides first. A side joins a direction it is
195
- * within `resolution / length` of (what the raster can tell apart), else opens a new one. A direction
196
- * with a single destination is a stand set at an angle and is dropped.
197
- * @param rings The destinations' outlines.
198
- * @param resolution The raster cell.
199
- * @returns The directions, {@link PLAN} first.
200
- */
201
- export function framesOf(rings, resolution) {
202
- const sides = rings
203
- .map((ring) => {
204
- let best = { ring, length: 0, angle: 0 };
205
- ring.forEach((a, i) => {
206
- const b = ring[(i + 1) % ring.length];
207
- const length = Math.hypot(b[0] - a[0], b[1] - a[1]);
208
- if (length > best.length)
209
- best = { ring, length, angle: directionOf(a, b) };
210
- });
211
- return best;
212
- })
213
- .filter((side) => side.length > 0)
214
- .sort((a, b) => b.length - a.length || a.angle - b.angle);
215
- // Infinite weight: the plan's axes never move.
216
- const found = [{ angle: 0, weight: Infinity, rings: [], plan: true }];
217
- for (const { ring, length, angle } of sides) {
218
- const eps = resolution / length;
219
- const near = found.find((f) => Math.abs(turn(f.angle, angle)) <= eps);
220
- if (!near) {
221
- found.push({ angle, weight: length, rings: [ring], plan: false });
222
- continue;
223
- }
224
- // Length-weighted mean: longer sides are known better.
225
- const mean = near.angle + (turn(near.angle, angle) * length) / (near.weight + length);
226
- near.angle = ((mean % QUARTER) + QUARTER) % QUARTER;
227
- near.weight += length;
228
- near.rings.push(ring);
229
- }
230
- return found
231
- .filter((f) => f.plan || f.rings.length >= 2)
232
- .map((f) => ({ frame: { cos: Math.cos(f.angle), sin: Math.sin(f.angle) }, rings: f.rings }));
233
- }
@@ -1,13 +0,0 @@
1
- import type { Ring } from './types.js';
2
- /**
3
- * The boundary of a mesh: the edges no two pieces share, stitched end to end. One pass instead of a
4
- * boolean, which takes minutes on fine meshes.
5
- *
6
- * The rings are meant to be unioned. A tiling gives its outline plus a ring per hole; overlapping pieces
7
- * share no edges and come back as themselves. The same shape given twice cancels its own edges, which is
8
- * why `footprintOf` leaves the dock out of the pieces.
9
- * @param parts The mesh pieces, wound either way.
10
- * @returns The boundary rings, or null when they enclose nothing.
11
- */
12
- export declare function meshOutline(parts: readonly Ring[]): Ring[] | null;
13
- //# sourceMappingURL=mesh-outline.d.ts.map
@@ -1,90 +0,0 @@
1
- import { cleanRing } from './normalize.js';
2
- import { wound } from './primitives.js';
3
- /**
4
- * Grid vertices snap to before edges are matched, in meters: shared vertices are written once per piece
5
- * and may differ in the last bits.
6
- */
7
- const WELD = 0.001;
8
- /**
9
- * Key of a welded vertex; pieces meeting at a corner get the same one.
10
- * @param x Vertex x.
11
- * @param y Vertex y.
12
- * @returns The key.
13
- */
14
- function weld(x, y) {
15
- return `${Math.round(x / WELD)}:${Math.round(y / WELD)}`;
16
- }
17
- /**
18
- * The boundary of a mesh: the edges no two pieces share, stitched end to end. One pass instead of a
19
- * boolean, which takes minutes on fine meshes.
20
- *
21
- * The rings are meant to be unioned. A tiling gives its outline plus a ring per hole; overlapping pieces
22
- * share no edges and come back as themselves. The same shape given twice cancels its own edges, which is
23
- * why `footprintOf` leaves the dock out of the pieces.
24
- * @param parts The mesh pieces, wound either way.
25
- * @returns The boundary rings, or null when they enclose nothing.
26
- */
27
- export function meshOutline(parts) {
28
- const seen = new Map();
29
- for (const part of parts) {
30
- if (part.length < 3)
31
- continue;
32
- // Same winding for all pieces, so a shared edge is walked both ways.
33
- const ring = wound(part);
34
- for (let i = 0; i < ring.length; i++) {
35
- const from = ring[i];
36
- const to = ring[(i + 1) % ring.length];
37
- const a = weld(from[0], from[1]);
38
- const b = weld(to[0], to[1]);
39
- if (a === b)
40
- continue;
41
- const key = a < b ? `${a}|${b}` : `${b}|${a}`;
42
- const known = seen.get(key);
43
- if (known)
44
- known.count++;
45
- else
46
- seen.set(key, { from, to, count: 1 });
47
- }
48
- }
49
- const edges = [...seen.values()].filter((edge) => edge.count === 1);
50
- if (edges.length < 3)
51
- return null;
52
- // An edge shared by three pieces means overlap: not a tiling, so no outline.
53
- for (const edge of seen.values())
54
- if (edge.count > 2)
55
- return null;
56
- const outgoing = new Map();
57
- for (const edge of edges) {
58
- const key = weld(edge.from[0], edge.from[1]);
59
- outgoing.set(key, [...(outgoing.get(key) ?? []), edge]);
60
- }
61
- const used = new Set();
62
- const rings = [];
63
- for (const start of edges) {
64
- if (used.has(start))
65
- continue;
66
- const ring = [start.from];
67
- let edge = start;
68
- for (;;) {
69
- used.add(edge);
70
- ring.push(edge.to);
71
- const next = (outgoing.get(weld(edge.to[0], edge.to[1])) ?? []).find((e) => !used.has(e));
72
- if (!next)
73
- break;
74
- edge = next;
75
- // Back at the start: the ring is closed.
76
- if (weld(edge.from[0], edge.from[1]) === weld(start.from[0], start.from[1]))
77
- break;
78
- }
79
- // An open chain encloses nothing and is dropped; the closed rings are still the boundary.
80
- const closed = weld(ring[ring.length - 1][0], ring[ring.length - 1][1]) ===
81
- weld(start.from[0], start.from[1]);
82
- if (!closed)
83
- continue;
84
- ring.pop();
85
- const cleaned = cleanRing(ring);
86
- if (cleaned.length >= 3)
87
- rings.push(cleaned);
88
- }
89
- return rings.length ? rings : null;
90
- }
@@ -1,28 +0,0 @@
1
- import type { Vec2 } from './types.js';
2
- /** A cleaned wall centerline: one continuous run of a raw `walls` entry. */
3
- export interface WallLine {
4
- /** Deduplicated vertices; a closed run does not repeat its first vertex. */
5
- readonly points: readonly Vec2[];
6
- /** A closed contour, or an open wall (e.g. with a door gap). */
7
- readonly closed: boolean;
8
- }
9
- /**
10
- * Splits a raw `walls` entry into the continuous runs it describes.
11
- *
12
- * The export stores one segment per vertex pair, joined by repeating the shared vertex; where a pair does
13
- * not touch the next, a new run starts elsewhere. Read as one polyline, each such gap would become a
14
- * phantom wall. The joint parity is read off the entry, since exports double from either vertex.
15
- * @param flat Flat `[x0, y0, x1, y1, …]` coordinates.
16
- * @param eps Merge tolerance for coincident vertices.
17
- * @returns The deduplicated runs; runs of fewer than two vertices are dropped.
18
- */
19
- export declare function cleanPolylineRuns(flat: readonly number[], eps?: number): WallLine[];
20
- /**
21
- * Normalizes a ring: drops coincident vertices (the closing one included) and vertices within `eps` of
22
- * the segment between their neighbors. Every surviving vertex splits a face, and each face grows a stub.
23
- * @param ring The ring, open or explicitly closed.
24
- * @param eps Tolerance for coincidence and collinearity.
25
- * @returns The ring, implicitly closed.
26
- */
27
- export declare function cleanRing(ring: readonly Vec2[], eps?: number): Vec2[];
28
- //# sourceMappingURL=normalize.d.ts.map