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