@expofp/wayfinding 3.11.11
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 +84 -0
- package/dist/core/createWayfindingEngine.d.ts +16 -0
- package/dist/core/createWayfindingEngine.js +87 -0
- package/dist/core/geometry/pointInPolygon.d.ts +17 -0
- package/dist/core/geometry/pointInPolygon.js +50 -0
- package/dist/core/geometry/projectPointOnSegment.d.ts +27 -0
- package/dist/core/geometry/projectPointOnSegment.js +21 -0
- package/dist/core/graph/buildGraph.d.ts +3 -0
- package/dist/core/graph/buildGraph.js +7 -0
- package/dist/core/graph/buildNGraph.d.ts +13 -0
- package/dist/core/graph/buildNGraph.js +24 -0
- package/dist/core/graph/constants.d.ts +20 -0
- package/dist/core/graph/constants.js +19 -0
- package/dist/core/graph/findShortestPath.d.ts +23 -0
- package/dist/core/graph/findShortestPath.js +65 -0
- package/dist/core/graph/graphCache.d.ts +7 -0
- package/dist/core/graph/graphCache.js +20 -0
- package/dist/core/graph/graphHelpers.d.ts +8 -0
- package/dist/core/graph/graphHelpers.js +52 -0
- package/dist/core/graph/linkCost.d.ts +13 -0
- package/dist/core/graph/linkCost.js +24 -0
- package/dist/core/graph/pathfinder/aStarPathFinder.d.ts +3 -0
- package/dist/core/graph/pathfinder/aStarPathFinder.js +64 -0
- package/dist/core/graph/pathfinder/parseNodeId.d.ts +10 -0
- package/dist/core/graph/pathfinder/parseNodeId.js +20 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +12 -0
- package/dist/core/position/distanceToRoute.d.ts +3 -0
- package/dist/core/position/distanceToRoute.js +29 -0
- package/dist/core/position/gpsThreshold.d.ts +32 -0
- package/dist/core/position/gpsThreshold.js +48 -0
- package/dist/core/position/rerouteController.d.ts +13 -0
- package/dist/core/position/rerouteController.js +22 -0
- package/dist/core/position/snapToRoute.d.ts +9 -0
- package/dist/core/position/snapToRoute.js +50 -0
- package/dist/core/position/splitRouteByPoint.d.ts +6 -0
- package/dist/core/position/splitRouteByPoint.js +56 -0
- package/dist/core/rendering/computeTrailPoints.d.ts +12 -0
- package/dist/core/rendering/computeTrailPoints.js +35 -0
- package/dist/core/rendering/computeTransitionPoints.d.ts +32 -0
- package/dist/core/rendering/computeTransitionPoints.js +114 -0
- package/dist/core/rendering/getVisibleRouteLines.d.ts +10 -0
- package/dist/core/rendering/getVisibleRouteLines.js +18 -0
- package/dist/core/rendering/normalizeRouteDirection.d.ts +19 -0
- package/dist/core/rendering/normalizeRouteDirection.js +82 -0
- package/dist/core/rendering/routeGeometry.d.ts +9 -0
- package/dist/core/rendering/routeGeometry.js +10 -0
- package/dist/core/routing/buildMultiPointRoute.d.ts +13 -0
- package/dist/core/routing/buildMultiPointRoute.js +36 -0
- package/dist/core/routing/buildRoute.d.ts +12 -0
- package/dist/core/routing/buildRoute.js +19 -0
- package/dist/core/routing/getRouteLength.d.ts +3 -0
- package/dist/core/routing/getRouteLength.js +4 -0
- package/dist/core/routing/graphPointResolvers.d.ts +9 -0
- package/dist/core/routing/graphPointResolvers.js +13 -0
- package/dist/core/routing/optimizeWaypointOrder.d.ts +14 -0
- package/dist/core/routing/optimizeWaypointOrder.js +88 -0
- package/dist/core/routing/resolveWaypointCandidates.d.ts +23 -0
- package/dist/core/routing/resolveWaypointCandidates.js +49 -0
- package/dist/core/routing/routeResult.d.ts +4 -0
- package/dist/core/routing/routeResult.js +2 -0
- package/dist/core/types.d.ts +102 -0
- package/dist/core/types.js +1 -0
- package/dist/createWayfinding.d.ts +68 -0
- package/dist/createWayfinding.js +44 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/renderer/createWayfindingRenderer.d.ts +14 -0
- package/dist/renderer/createWayfindingRenderer.js +51 -0
- package/dist/renderer/iconManager.d.ts +39 -0
- package/dist/renderer/iconManager.js +166 -0
- package/dist/renderer/index.d.ts +3 -0
- package/dist/renderer/index.js +1 -0
- package/dist/renderer/layerManager.d.ts +27 -0
- package/dist/renderer/layerManager.js +40 -0
- package/dist/renderer/lineAnimation.d.ts +11 -0
- package/dist/renderer/lineAnimation.js +127 -0
- package/dist/renderer/routeLineManager.d.ts +44 -0
- package/dist/renderer/routeLineManager.js +62 -0
- package/dist/renderer/trailManager.d.ts +32 -0
- package/dist/renderer/trailManager.js +79 -0
- package/dist/renderer/types.d.ts +129 -0
- package/dist/renderer/types.js +1 -0
- package/dist/runtime/createWayfindingRuntime.d.ts +3 -0
- package/dist/runtime/createWayfindingRuntime.js +248 -0
- package/dist/runtime/endpointView.d.ts +20 -0
- package/dist/runtime/endpointView.js +40 -0
- package/dist/runtime/getRouteLines.d.ts +19 -0
- package/dist/runtime/getRouteLines.js +16 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/positionTrailView.d.ts +52 -0
- package/dist/runtime/positionTrailView.js +35 -0
- package/dist/runtime/positionView.d.ts +23 -0
- package/dist/runtime/positionView.js +25 -0
- package/dist/runtime/routeLinesView.d.ts +18 -0
- package/dist/runtime/routeLinesView.js +18 -0
- package/dist/runtime/routeRenderData.d.ts +31 -0
- package/dist/runtime/routeRenderData.js +46 -0
- package/dist/runtime/routeUpdate.d.ts +17 -0
- package/dist/runtime/routeUpdate.js +17 -0
- package/dist/runtime/snapPositionToRoute.d.ts +10 -0
- package/dist/runtime/snapPositionToRoute.js +36 -0
- package/dist/runtime/trailView.d.ts +20 -0
- package/dist/runtime/trailView.js +43 -0
- package/dist/runtime/transitionView.d.ts +17 -0
- package/dist/runtime/transitionView.js +50 -0
- package/dist/runtime/types.d.ts +86 -0
- package/dist/runtime/types.js +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Rect } from '@expofp/geometry';
|
|
2
|
+
import { computeTrailPoints } from '../core/index.js';
|
|
3
|
+
/** Distance between trail dots at scale=1, in the same units as route points. */
|
|
4
|
+
const TRAIL_INTERVAL_AT_BASE_SCALE = 40;
|
|
5
|
+
/**
|
|
6
|
+
* Create a {@link TrailManager} that renders dot trails into layers
|
|
7
|
+
* managed by the given {@link LayerManager}.
|
|
8
|
+
* @param layerManager
|
|
9
|
+
* @param rendererPort
|
|
10
|
+
*/
|
|
11
|
+
export function createTrailManager(layerManager, rendererPort) {
|
|
12
|
+
const trails = new Map();
|
|
13
|
+
function removeDotsFromLayer(record) {
|
|
14
|
+
if (!record.dotDefs.length)
|
|
15
|
+
return;
|
|
16
|
+
const layer = layerManager.touchLayer(record.layerName);
|
|
17
|
+
const oldDots = new Set(record.dotDefs);
|
|
18
|
+
layer.children = layer.children.filter((child) => !oldDots.has(child));
|
|
19
|
+
record.dotDefs = [];
|
|
20
|
+
}
|
|
21
|
+
function redraw(record, scale) {
|
|
22
|
+
const layer = layerManager.touchLayer(record.layerName);
|
|
23
|
+
const interval = TRAIL_INTERVAL_AT_BASE_SCALE * scale;
|
|
24
|
+
const points = computeTrailPoints(record.from, record.to, interval);
|
|
25
|
+
const canvas = record.canvas;
|
|
26
|
+
const newDots = points.map((point) => rendererPort.createImageDef(canvas, new Rect({ x: point.x, y: point.y }, { x: canvas.width * scale, y: canvas.height * scale })));
|
|
27
|
+
removeDotsFromLayer(record);
|
|
28
|
+
layer.children.push(...newDots);
|
|
29
|
+
record.dotDefs = newDots;
|
|
30
|
+
}
|
|
31
|
+
function drop(name) {
|
|
32
|
+
const record = trails.get(name);
|
|
33
|
+
if (!record)
|
|
34
|
+
return;
|
|
35
|
+
removeDotsFromLayer(record);
|
|
36
|
+
trails.delete(name);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
setTrail(name, from, to, options) {
|
|
40
|
+
if (from === null || to === null) {
|
|
41
|
+
drop(name);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!options)
|
|
45
|
+
throw new Error(`renderer: setTrail("${name}") requires options when from/to are non-null`);
|
|
46
|
+
const existing = trails.get(name);
|
|
47
|
+
if (existing) {
|
|
48
|
+
if (existing.canvas !== options.canvas || existing.layerName !== options.layer) {
|
|
49
|
+
throw new Error(`renderer: trail "${name}" cannot change canvas or layer after first call`);
|
|
50
|
+
}
|
|
51
|
+
existing.from = from;
|
|
52
|
+
existing.to = to;
|
|
53
|
+
redraw(existing, rendererPort.getScale());
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const record = {
|
|
57
|
+
from,
|
|
58
|
+
to,
|
|
59
|
+
canvas: options.canvas,
|
|
60
|
+
layerName: options.layer,
|
|
61
|
+
dotDefs: [],
|
|
62
|
+
};
|
|
63
|
+
trails.set(name, record);
|
|
64
|
+
redraw(record, rendererPort.getScale());
|
|
65
|
+
},
|
|
66
|
+
applyScale(scale) {
|
|
67
|
+
const layers = new Set();
|
|
68
|
+
for (const record of trails.values()) {
|
|
69
|
+
redraw(record, scale);
|
|
70
|
+
layers.add(layerManager.touchLayer(record.layerName));
|
|
71
|
+
}
|
|
72
|
+
return [...layers];
|
|
73
|
+
},
|
|
74
|
+
destroy() {
|
|
75
|
+
for (const trailName of [...trails.keys()])
|
|
76
|
+
drop(trailName);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Point2Like, Rect } from '@expofp/geometry';
|
|
2
|
+
import type { ImageDef, ImageSource, LayerDef, LineDef, Rect as RendererRect, RenderableDef } from '@expofp/renderer';
|
|
3
|
+
/**
|
|
4
|
+
* The renderer this layer talks to, injected so it stays free of a concrete
|
|
5
|
+
* renderer. Typed only against `@expofp/renderer` / `@expofp/geometry` so it
|
|
6
|
+
* carries no dependency on `src/renderer`.
|
|
7
|
+
*/
|
|
8
|
+
export interface RendererPort {
|
|
9
|
+
createImageDef(source: ImageSource, bounds: Rect, options?: {
|
|
10
|
+
hidden?: boolean;
|
|
11
|
+
dim?: boolean;
|
|
12
|
+
interactive?: boolean;
|
|
13
|
+
origin?: [number, number];
|
|
14
|
+
}): ImageDef;
|
|
15
|
+
createLineDef(line: {
|
|
16
|
+
p0: Point2Like;
|
|
17
|
+
p1: Point2Like;
|
|
18
|
+
}, color?: string, width?: number): LineDef;
|
|
19
|
+
getRotation(newAngle: number, currentRotation: number): number | undefined;
|
|
20
|
+
/** Convert a geometry rect to the renderer's `Rect`. */
|
|
21
|
+
rectToRenderer(rect: Rect): RendererRect;
|
|
22
|
+
/** Current camera point-scale. */
|
|
23
|
+
getScale(): number;
|
|
24
|
+
/** Scene root-layer children, used to resolve wf layers by name. */
|
|
25
|
+
getLayers(): LayerDef[];
|
|
26
|
+
/** Commit changed defs/layers to the renderer. */
|
|
27
|
+
update(...defs: RenderableDef[]): void;
|
|
28
|
+
}
|
|
29
|
+
export interface RouteColors {
|
|
30
|
+
readonly passed: string;
|
|
31
|
+
readonly remaining: string;
|
|
32
|
+
readonly active: string;
|
|
33
|
+
}
|
|
34
|
+
export interface IconConfig {
|
|
35
|
+
readonly canvas: HTMLCanvasElement;
|
|
36
|
+
/**
|
|
37
|
+
* Target layer name in the scene. Renderer locates the layer once per
|
|
38
|
+
* (name, layer) slot and reuses it.
|
|
39
|
+
*
|
|
40
|
+
* TODO(future): when renderer learns to create layers dynamically
|
|
41
|
+
* inside `createWayfindingRenderer`, this field is removed — renderer
|
|
42
|
+
* will own layer creation by icon name.
|
|
43
|
+
*/
|
|
44
|
+
readonly layer: string;
|
|
45
|
+
readonly x: number;
|
|
46
|
+
readonly y: number;
|
|
47
|
+
/** Rotation in radians. */
|
|
48
|
+
readonly rotation?: number;
|
|
49
|
+
readonly hidden?: boolean;
|
|
50
|
+
readonly dimmed?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Anchor point [0..1, 0..1]. Set at first setIcon for an icon key;
|
|
53
|
+
* ignored on subsequent updates (origin is fixed in ImageDef at creation).
|
|
54
|
+
*/
|
|
55
|
+
readonly origin?: [number, number];
|
|
56
|
+
/**
|
|
57
|
+
* When true, renderer registers a camera-roll handler that snaps the icon
|
|
58
|
+
* rotation to the nearest cardinal direction (N/S/E/W, 90° steps) as the
|
|
59
|
+
* camera rotates past 45° thresholds. Used for label-like icons
|
|
60
|
+
* (source/destination/transition) that should remain readable.
|
|
61
|
+
*
|
|
62
|
+
* Do NOT enable for icons whose `rotation` carries meaning (e.g. position
|
|
63
|
+
* arrow heading, kiosk heading) — the snap would override that meaning.
|
|
64
|
+
*
|
|
65
|
+
* Set at first setIcon for an icon key; ignored on subsequent updates.
|
|
66
|
+
*/
|
|
67
|
+
readonly cardinalSnap?: boolean;
|
|
68
|
+
readonly onClick?: () => void;
|
|
69
|
+
}
|
|
70
|
+
export interface WayfindingRendererConfig {
|
|
71
|
+
/** Optional color overrides; defaults applied if omitted. */
|
|
72
|
+
readonly colors?: Partial<RouteColors>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* WayfindingRenderer — generic ImageDef/LineDef renderer for wayfinding visuals.
|
|
76
|
+
*
|
|
77
|
+
* Mutators (`setIcon`/`clearIcons`/`setTrail`/`setRouteLines`/`clearRoute`) only
|
|
78
|
+
* mutate scene state; call `flush()` to commit a batch. The route animation tick
|
|
79
|
+
* updates the renderer directly, outside any batch.
|
|
80
|
+
*/
|
|
81
|
+
export interface WayfindingRenderer {
|
|
82
|
+
/**
|
|
83
|
+
* Place or update an icon by `(name, key)`. First call creates it in
|
|
84
|
+
* `config.layer`; later calls mutate in place. `config: null` hides it.
|
|
85
|
+
* `config.layer` must stay stable for a `(name, key)`. Buffered — `flush()` to commit.
|
|
86
|
+
*/
|
|
87
|
+
setIcon(name: string, config: IconConfig | null, key?: string | number): void;
|
|
88
|
+
/** Hide every icon sharing one of `names` (kept alive for reuse). Buffered. */
|
|
89
|
+
clearIcons(names: string | readonly string[]): void;
|
|
90
|
+
/**
|
|
91
|
+
* Place or update a named dot trail between two points; `from/to = null`
|
|
92
|
+
* clears it. `options` required when non-null; `canvas`/`layer` must stay
|
|
93
|
+
* stable. Buffered — `flush()` to commit.
|
|
94
|
+
*/
|
|
95
|
+
setTrail(name: string, from: Point2Like | null, to: Point2Like | null, options?: {
|
|
96
|
+
readonly canvas: HTMLCanvasElement;
|
|
97
|
+
readonly layer: string;
|
|
98
|
+
}): void;
|
|
99
|
+
/**
|
|
100
|
+
* Replace static route lines (passed + remaining) and (re)start the remaining
|
|
101
|
+
* animation. Buffered — `flush()` to commit.
|
|
102
|
+
*/
|
|
103
|
+
setRouteLines(passed: readonly {
|
|
104
|
+
readonly p0: Point2Like;
|
|
105
|
+
readonly p1: Point2Like;
|
|
106
|
+
}[], remaining: readonly {
|
|
107
|
+
readonly p0: Point2Like;
|
|
108
|
+
readonly p1: Point2Like;
|
|
109
|
+
}[], options: {
|
|
110
|
+
readonly linesLayer: string;
|
|
111
|
+
readonly animatedLinesLayer: string;
|
|
112
|
+
readonly resetAnimation?: boolean;
|
|
113
|
+
}): void;
|
|
114
|
+
/** Recompute icon/trail bounds for `scale`; returns changed defs to render. */
|
|
115
|
+
applyScale(scale: number): RenderableDef[];
|
|
116
|
+
/** Apply camera roll to cardinal-snap icons; returns changed defs to render. */
|
|
117
|
+
applyRoll(cameraAngle: number): RenderableDef[];
|
|
118
|
+
/** Fire the hit clickable icon's `onClick` from already-picked defs. */
|
|
119
|
+
handleClick(defs: readonly RenderableDef[]): void;
|
|
120
|
+
/** Whether a clickable icon is under the already-picked defs (for cursor). */
|
|
121
|
+
handleHover(defs: readonly RenderableDef[]): boolean;
|
|
122
|
+
/** Clear route lines, drop trails, hide icons. Buffered — `flush()` to commit. */
|
|
123
|
+
clearRoute(): void;
|
|
124
|
+
/** Commit buffered mutations to the renderer (via the injected port). */
|
|
125
|
+
flush(): void;
|
|
126
|
+
/** Full teardown (managers cleared). Does NOT flush. */
|
|
127
|
+
destroy(): void;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WayfindingRuntime, WayfindingRuntimeConfig } from './types.js';
|
|
2
|
+
export declare function createWayfindingRuntime({ engine, renderer, iconProvider, floorContext, layers, snapThreshold, rerouteThreshold, onTransitionClick, onRouteUpdate, onRouteDistance, }: WayfindingRuntimeConfig): WayfindingRuntime;
|
|
3
|
+
//# sourceMappingURL=createWayfindingRuntime.d.ts.map
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { Box } from '@expofp/geometry';
|
|
2
|
+
import { createRerouteController, getRouteLength, } from '../core/index.js';
|
|
3
|
+
import { createEndpointView } from './endpointView.js';
|
|
4
|
+
import { getRouteLines } from './getRouteLines.js';
|
|
5
|
+
import { createPositionTrailView } from './positionTrailView.js';
|
|
6
|
+
import { CURRENT_POSITION_POINT_ID } from './positionView.js';
|
|
7
|
+
import { createRouteLinesView } from './routeLinesView.js';
|
|
8
|
+
import { computeRouteRenderData } from './routeRenderData.js';
|
|
9
|
+
import { computeRouteUpdate } from './routeUpdate.js';
|
|
10
|
+
import { snapPositionToRoute } from './snapPositionToRoute.js';
|
|
11
|
+
import { createTrailView } from './trailView.js';
|
|
12
|
+
import { createTransitionView } from './transitionView.js';
|
|
13
|
+
const linesToPoints = (lines) => lines.flatMap((line) => [line.p0, line.p1]);
|
|
14
|
+
export function createWayfindingRuntime({ engine, renderer, iconProvider, floorContext, layers, snapThreshold, rerouteThreshold, onTransitionClick, onRouteUpdate, onRouteDistance, }) {
|
|
15
|
+
const rerouteController = createRerouteController(rerouteThreshold);
|
|
16
|
+
const endpoints = createEndpointView({
|
|
17
|
+
renderer,
|
|
18
|
+
iconProvider,
|
|
19
|
+
floorContext,
|
|
20
|
+
layer: layers.points,
|
|
21
|
+
});
|
|
22
|
+
const transitions = createTransitionView({
|
|
23
|
+
renderer,
|
|
24
|
+
iconProvider,
|
|
25
|
+
floorContext,
|
|
26
|
+
onTransitionClick,
|
|
27
|
+
layer: layers.points,
|
|
28
|
+
});
|
|
29
|
+
const trails = createTrailView({ renderer, iconProvider, floorContext, layer: layers.trail });
|
|
30
|
+
const positionTrails = createPositionTrailView({
|
|
31
|
+
renderer,
|
|
32
|
+
iconProvider,
|
|
33
|
+
floorContext,
|
|
34
|
+
trails,
|
|
35
|
+
iconLayer: layers.currentPosition,
|
|
36
|
+
snapThreshold,
|
|
37
|
+
});
|
|
38
|
+
const routeLinesView = createRouteLinesView({
|
|
39
|
+
renderer,
|
|
40
|
+
linesLayer: layers.lines,
|
|
41
|
+
animatedLinesLayer: layers.linesAnimated,
|
|
42
|
+
});
|
|
43
|
+
let routeLines = [];
|
|
44
|
+
let currentFrom = null;
|
|
45
|
+
let currentTo = null;
|
|
46
|
+
let currentPosition = null;
|
|
47
|
+
let lastVisibleDistance = 0;
|
|
48
|
+
// Sticky-snap: flips to true the first time a CP for the current route snaps. Suppresses the
|
|
49
|
+
// user-location off-graph trail on later drifts back into the snap-gap zone. Reset on setRoute.
|
|
50
|
+
let hasSnappedCurrentRoute = false;
|
|
51
|
+
// Live snap state of the latest tick (not sticky) — drives the greyed-out source variant.
|
|
52
|
+
let snappedToRoute = false;
|
|
53
|
+
function getCurrentRouteLayer() {
|
|
54
|
+
if (floorContext.showAllFloors())
|
|
55
|
+
return null;
|
|
56
|
+
return floorContext.getActiveFloor();
|
|
57
|
+
}
|
|
58
|
+
function getVisibleLines() {
|
|
59
|
+
const layer = getCurrentRouteLayer();
|
|
60
|
+
if (layer === null)
|
|
61
|
+
return routeLines.filter((line) => !line.virtual);
|
|
62
|
+
return routeLines.filter((line) => line.p0.layer === layer && !line.virtual);
|
|
63
|
+
}
|
|
64
|
+
// Non-virtual lines across all floors — the same set emitted via `onRouteUpdate`
|
|
65
|
+
// (see `computeRouteUpdate`), so reroute drift is measured against the full route.
|
|
66
|
+
function shouldReroute(position) {
|
|
67
|
+
return rerouteController.shouldReroute(position, routeLines.filter((line) => !line.virtual));
|
|
68
|
+
}
|
|
69
|
+
function getRouteSnapshot() {
|
|
70
|
+
if (!currentFrom || !currentTo)
|
|
71
|
+
return null;
|
|
72
|
+
const visibleLines = getVisibleLines();
|
|
73
|
+
if (!visibleLines.length)
|
|
74
|
+
return null;
|
|
75
|
+
return {
|
|
76
|
+
from: currentFrom,
|
|
77
|
+
to: currentTo,
|
|
78
|
+
routePoints: linesToPoints(visibleLines),
|
|
79
|
+
visibleLines,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function renderVisibleRoute(snap = null, preserveProgress = false) {
|
|
83
|
+
if (!currentFrom || !currentTo)
|
|
84
|
+
return;
|
|
85
|
+
const data = computeRouteRenderData({
|
|
86
|
+
routeLines,
|
|
87
|
+
currentRouteLayer: getCurrentRouteLayer(),
|
|
88
|
+
snap,
|
|
89
|
+
});
|
|
90
|
+
routeLinesView.draw(data, preserveProgress);
|
|
91
|
+
lastVisibleDistance = data.distance;
|
|
92
|
+
}
|
|
93
|
+
function syncTrail(snap) {
|
|
94
|
+
const route = getRouteSnapshot();
|
|
95
|
+
if (!route) {
|
|
96
|
+
positionTrails.setTrail(null, null);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// CP and kiosk are mutually exclusive in practice. Pick FROM first; fall through to TO.
|
|
100
|
+
// Sticky-snap suppression only applies to live-CP FROM — kiosk anchors are static.
|
|
101
|
+
const livePosition = currentPosition && snap && !snap.snapped ? currentPosition : null;
|
|
102
|
+
const pickAnchor = (endpoint) => {
|
|
103
|
+
if (endpoint.bounds?.length)
|
|
104
|
+
return null;
|
|
105
|
+
if (endpoint.id === CURRENT_POSITION_POINT_ID)
|
|
106
|
+
return livePosition;
|
|
107
|
+
return endpoint;
|
|
108
|
+
};
|
|
109
|
+
// `hasSnappedCurrentRoute` only flips for a live-CP FROM (booth/kiosk origins never snap),
|
|
110
|
+
// so it already implies a CP origin — no need to re-check the endpoint id here.
|
|
111
|
+
const fromAnchor = hasSnappedCurrentRoute ? null : pickAnchor(route.from);
|
|
112
|
+
// The trail tip is the route's graph endpoint on the same side as its
|
|
113
|
+
// off-graph anchor: first point for FROM, last point for TO.
|
|
114
|
+
if (fromAnchor) {
|
|
115
|
+
positionTrails.setTrail(route.routePoints[0], fromAnchor);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const toAnchor = pickAnchor(route.to);
|
|
119
|
+
positionTrails.setTrail(toAnchor ? route.routePoints[route.routePoints.length - 1] : null, toAnchor);
|
|
120
|
+
}
|
|
121
|
+
function syncRouteVisuals() {
|
|
122
|
+
if (!currentFrom || !currentTo)
|
|
123
|
+
return;
|
|
124
|
+
const route = getRouteSnapshot();
|
|
125
|
+
if (!route) {
|
|
126
|
+
endpoints.hide();
|
|
127
|
+
transitions.clear();
|
|
128
|
+
positionTrails.clearTrail();
|
|
129
|
+
renderVisibleRoute();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
endpoints.place(currentFrom, currentTo, route.routePoints, snappedToRoute);
|
|
133
|
+
transitions.place(routeLines);
|
|
134
|
+
renderVisibleRoute();
|
|
135
|
+
syncTrail(null);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Re-applies source/destination icons only — used when the CP appears/disappears or snaps onto
|
|
139
|
+
* the route, since the source icon's visibility (CP vs kiosk) and grey variant depend on both.
|
|
140
|
+
*/
|
|
141
|
+
function syncEndpoints() {
|
|
142
|
+
if (!currentFrom || !currentTo)
|
|
143
|
+
return;
|
|
144
|
+
const route = getRouteSnapshot();
|
|
145
|
+
if (route)
|
|
146
|
+
endpoints.place(currentFrom, currentTo, route.routePoints, snappedToRoute);
|
|
147
|
+
}
|
|
148
|
+
function snapPosition() {
|
|
149
|
+
snappedToRoute = false;
|
|
150
|
+
if (!currentPosition || !currentFrom || !currentTo)
|
|
151
|
+
return null;
|
|
152
|
+
const nonVirtual = routeLines.filter((line) => !line.virtual);
|
|
153
|
+
if (!nonVirtual.length)
|
|
154
|
+
return null;
|
|
155
|
+
const route = getRouteSnapshot();
|
|
156
|
+
const result = route
|
|
157
|
+
? positionTrails.applyToRoute(currentPosition, route, nonVirtual)
|
|
158
|
+
: // Selected floor doesn't pass through the route — view has nothing
|
|
159
|
+
// to draw, but distance must still update. Snap against the full
|
|
160
|
+
// non-virtual route directly.
|
|
161
|
+
snapPositionToRoute(currentPosition, nonVirtual, currentFrom, currentTo, snapThreshold);
|
|
162
|
+
if (result.snapped)
|
|
163
|
+
hasSnappedCurrentRoute = true;
|
|
164
|
+
snappedToRoute = result.snapped;
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
function emitRouteUpdate() {
|
|
168
|
+
const route = computeRouteUpdate(routeLines, getVisibleLines());
|
|
169
|
+
onRouteUpdate(route.lines, route.bounds);
|
|
170
|
+
return route;
|
|
171
|
+
}
|
|
172
|
+
function setRoute({ from, to, waypoints, accessible }) {
|
|
173
|
+
const newRouteLines = getRouteLines({ engine, from, to, waypoints, accessible });
|
|
174
|
+
if (!newRouteLines.length) {
|
|
175
|
+
clearRoute();
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
hasSnappedCurrentRoute = false;
|
|
179
|
+
currentFrom = from;
|
|
180
|
+
currentTo = to;
|
|
181
|
+
routeLines = newRouteLines;
|
|
182
|
+
syncRouteVisuals();
|
|
183
|
+
const { lines } = emitRouteUpdate();
|
|
184
|
+
const snap = snapPosition();
|
|
185
|
+
syncEndpoints(); // snapPosition may have flipped the snap state — refresh the source variant
|
|
186
|
+
syncTrail(snap);
|
|
187
|
+
if (snap)
|
|
188
|
+
renderVisibleRoute(snap, true);
|
|
189
|
+
onRouteDistance(snap ? lastVisibleDistance : getRouteLength(lines));
|
|
190
|
+
renderer.flush();
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
function onPositionChanged(position) {
|
|
194
|
+
const hadPosition = currentPosition !== null;
|
|
195
|
+
currentPosition = position;
|
|
196
|
+
if (!position) {
|
|
197
|
+
snappedToRoute = false; // no CP → the source reverts from greyed to full-colour
|
|
198
|
+
positionTrails.hideIcon();
|
|
199
|
+
syncTrail(null);
|
|
200
|
+
if (hadPosition)
|
|
201
|
+
syncEndpoints();
|
|
202
|
+
renderVisibleRoute();
|
|
203
|
+
onRouteDistance(lastVisibleDistance);
|
|
204
|
+
renderer.flush();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
positionTrails.placeIcon(position);
|
|
208
|
+
const snap = snapPosition();
|
|
209
|
+
syncEndpoints(); // CP presence / snap state may have changed — refresh source visibility + variant
|
|
210
|
+
syncTrail(snap);
|
|
211
|
+
if (snap)
|
|
212
|
+
renderVisibleRoute(snap, true);
|
|
213
|
+
onRouteDistance(lastVisibleDistance);
|
|
214
|
+
renderer.flush();
|
|
215
|
+
}
|
|
216
|
+
function onFloorChanged() {
|
|
217
|
+
syncRouteVisuals();
|
|
218
|
+
const snap = snapPosition();
|
|
219
|
+
syncEndpoints(); // snap state may differ on the new floor — refresh the source variant
|
|
220
|
+
syncTrail(snap);
|
|
221
|
+
if (snap)
|
|
222
|
+
renderVisibleRoute(snap, true);
|
|
223
|
+
if (routeLines.length)
|
|
224
|
+
emitRouteUpdate();
|
|
225
|
+
onRouteDistance(lastVisibleDistance);
|
|
226
|
+
renderer.flush();
|
|
227
|
+
}
|
|
228
|
+
function clearRoute() {
|
|
229
|
+
hasSnappedCurrentRoute = false;
|
|
230
|
+
routeLines = [];
|
|
231
|
+
currentFrom = null;
|
|
232
|
+
currentTo = null;
|
|
233
|
+
lastVisibleDistance = 0;
|
|
234
|
+
endpoints.hide();
|
|
235
|
+
transitions.clear();
|
|
236
|
+
positionTrails.clearTrail();
|
|
237
|
+
routeLinesView.clear();
|
|
238
|
+
onRouteUpdate([], new Box());
|
|
239
|
+
onRouteDistance(0);
|
|
240
|
+
renderer.flush();
|
|
241
|
+
}
|
|
242
|
+
function destroy() {
|
|
243
|
+
clearRoute();
|
|
244
|
+
currentPosition = null;
|
|
245
|
+
renderer.destroy();
|
|
246
|
+
}
|
|
247
|
+
return { setRoute, onPositionChanged, onFloorChanged, shouldReroute, clearRoute, destroy };
|
|
248
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RouteEndpoint, type RoutePoint } from '../core/index.js';
|
|
2
|
+
import { type WayfindingRenderer } from '../renderer/index.js';
|
|
3
|
+
import { type FloorContext, type IconProvider } from './types.js';
|
|
4
|
+
interface EndpointView {
|
|
5
|
+
/**
|
|
6
|
+
* Place source/destination icons. `snapped` — the live CP is currently on the route, so its
|
|
7
|
+
* source shows greyed-out. A kiosk anchor (off-graph origin that isn't the CP) stays hidden;
|
|
8
|
+
* everything else (booth, CP off-route, no live CP) shows full-colour.
|
|
9
|
+
*/
|
|
10
|
+
place(from: RouteEndpoint, to: RouteEndpoint, visibleRoutePoints: readonly RoutePoint[], snapped: boolean): void;
|
|
11
|
+
hide(): void;
|
|
12
|
+
}
|
|
13
|
+
export declare function createEndpointView({ renderer, iconProvider, floorContext, layer, }: {
|
|
14
|
+
renderer: Pick<WayfindingRenderer, 'setIcon'>;
|
|
15
|
+
iconProvider: Pick<IconProvider, 'getIcon'>;
|
|
16
|
+
floorContext: Pick<FloorContext, 'isLayerVisible'>;
|
|
17
|
+
layer: string;
|
|
18
|
+
}): EndpointView;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=endpointView.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CURRENT_POSITION_POINT_ID } from './positionView.js';
|
|
2
|
+
function isSourceIconHidden(from, onFloor) {
|
|
3
|
+
// Hide only a kiosk anchor — an off-graph origin that isn't the live current position.
|
|
4
|
+
return !onFloor || (!from.bounds && from.id !== CURRENT_POSITION_POINT_ID);
|
|
5
|
+
}
|
|
6
|
+
export function createEndpointView({ renderer, iconProvider, floorContext, layer, }) {
|
|
7
|
+
return {
|
|
8
|
+
place(from, to, visibleRoutePoints, snapped) {
|
|
9
|
+
if (!visibleRoutePoints.length)
|
|
10
|
+
return;
|
|
11
|
+
const fromOnFloor = floorContext.isLayerVisible(from.layer);
|
|
12
|
+
const toOnFloor = floorContext.isLayerVisible(to.layer);
|
|
13
|
+
const sourcePosition = visibleRoutePoints[0];
|
|
14
|
+
const destPosition = visibleRoutePoints[visibleRoutePoints.length - 1];
|
|
15
|
+
const sourceInactive = from.id === CURRENT_POSITION_POINT_ID && snapped;
|
|
16
|
+
renderer.setIcon('source', {
|
|
17
|
+
canvas: iconProvider.getIcon(sourceInactive ? 'source_inactive' : 'source').canvas,
|
|
18
|
+
layer,
|
|
19
|
+
x: sourcePosition.x,
|
|
20
|
+
y: sourcePosition.y,
|
|
21
|
+
hidden: isSourceIconHidden(from, fromOnFloor),
|
|
22
|
+
dimmed: !fromOnFloor,
|
|
23
|
+
cardinalSnap: true,
|
|
24
|
+
});
|
|
25
|
+
renderer.setIcon('destination', {
|
|
26
|
+
canvas: iconProvider.getIcon('destination').canvas,
|
|
27
|
+
layer,
|
|
28
|
+
x: destPosition.x,
|
|
29
|
+
y: destPosition.y,
|
|
30
|
+
hidden: !toOnFloor || !to.bounds,
|
|
31
|
+
dimmed: !toOnFloor,
|
|
32
|
+
cardinalSnap: true,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
hide() {
|
|
36
|
+
renderer.setIcon('source', null);
|
|
37
|
+
renderer.setIcon('destination', null);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type RouteEndpoint, type RouteLine, type WayfindingEngine } from '../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Choose between `engine.buildRoute` and `engine.buildWaypointsRoute` based on
|
|
4
|
+
* whether intermediate waypoints are supplied, and return the resulting route lines.
|
|
5
|
+
* @param root0
|
|
6
|
+
* @param root0.engine
|
|
7
|
+
* @param root0.from
|
|
8
|
+
* @param root0.to
|
|
9
|
+
* @param root0.waypoints
|
|
10
|
+
* @param root0.accessible
|
|
11
|
+
*/
|
|
12
|
+
export declare function getRouteLines({ engine, from, to, waypoints, accessible, }: {
|
|
13
|
+
readonly engine: WayfindingEngine;
|
|
14
|
+
readonly from: RouteEndpoint;
|
|
15
|
+
readonly to: RouteEndpoint;
|
|
16
|
+
readonly waypoints?: readonly RouteEndpoint[];
|
|
17
|
+
readonly accessible: boolean;
|
|
18
|
+
}): RouteLine[];
|
|
19
|
+
//# sourceMappingURL=getRouteLines.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choose between `engine.buildRoute` and `engine.buildWaypointsRoute` based on
|
|
3
|
+
* whether intermediate waypoints are supplied, and return the resulting route lines.
|
|
4
|
+
* @param root0
|
|
5
|
+
* @param root0.engine
|
|
6
|
+
* @param root0.from
|
|
7
|
+
* @param root0.to
|
|
8
|
+
* @param root0.waypoints
|
|
9
|
+
* @param root0.accessible
|
|
10
|
+
*/
|
|
11
|
+
export function getRouteLines({ engine, from, to, waypoints, accessible, }) {
|
|
12
|
+
if (waypoints?.length) {
|
|
13
|
+
return engine.buildWaypointsRoute([from, ...waypoints, to], { accessible }).lines;
|
|
14
|
+
}
|
|
15
|
+
return engine.buildRoute(from, to, { accessible }).lines;
|
|
16
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createWayfindingRuntime } from './createWayfindingRuntime.js';
|
|
2
|
+
export { CURRENT_POSITION_POINT_ID } from './positionView.js';
|
|
3
|
+
export type { FloorContext, IconAsset, IconName, IconProvider, LayerNames, PositionInput, SetRouteInput, WayfindingRuntime, WayfindingRuntimeConfig, } from './types.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type RouteEndpoint, type RouteLine, type RoutePoint, type RouteSnapResult } from '../core/index.js';
|
|
2
|
+
import type { WayfindingRenderer } from '../renderer/index.js';
|
|
3
|
+
import type { TrailView } from './trailView.js';
|
|
4
|
+
import type { FloorContext, IconProvider, PositionInput } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Snapshot of the visible route the position-trail view operates on. The runtime computes it
|
|
7
|
+
* (visibility filtering, route-points derivation) and passes it in so the view stays free of
|
|
8
|
+
* route-state knowledge.
|
|
9
|
+
*/
|
|
10
|
+
export interface RouteSnapshot {
|
|
11
|
+
readonly from: RouteEndpoint;
|
|
12
|
+
readonly to: RouteEndpoint;
|
|
13
|
+
readonly routePoints: readonly RoutePoint[];
|
|
14
|
+
readonly visibleLines: readonly RouteLine[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Owns the user-position icon and the single off-graph trail slot. The runtime decides which end of
|
|
18
|
+
* the route needs a trail (kiosk FROM, kiosk TO, or off-route CP FROM — never both at once in
|
|
19
|
+
* practice) and passes the corresponding `tip` + `anchor` here.
|
|
20
|
+
*/
|
|
21
|
+
interface PositionTrailView {
|
|
22
|
+
/** Place the position icon at the raw position. */
|
|
23
|
+
placeIcon(position: PositionInput): void;
|
|
24
|
+
/** Hide the position icon. */
|
|
25
|
+
hideIcon(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Snap the position to `routeLines` (caller-provided — typically the full non-virtual route
|
|
28
|
+
* across all floors) and place the position icon at the snap point or at the raw position.
|
|
29
|
+
* Returns the snap result; the trail is **not** touched — call `setTrail` separately.
|
|
30
|
+
*
|
|
31
|
+
* `routeLines` is decoupled from `route.visibleLines` so the snap stays correct regardless of
|
|
32
|
+
* which floor is currently selected in the UI.
|
|
33
|
+
*/
|
|
34
|
+
applyToRoute(position: PositionInput, route: RouteSnapshot, routeLines: readonly RouteLine[]): RouteSnapResult;
|
|
35
|
+
/**
|
|
36
|
+
* Draw the trail from `tip` (a route's graph endpoint) to `anchor` (the off-graph point).
|
|
37
|
+
* Passing either as null hides the trail.
|
|
38
|
+
*/
|
|
39
|
+
setTrail(tip: RoutePoint | null, anchor: RoutePoint | null): void;
|
|
40
|
+
/** Clear the trail slot (does not touch the icon). */
|
|
41
|
+
clearTrail(): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createPositionTrailView({ renderer, iconProvider, floorContext, trails, iconLayer, snapThreshold, }: {
|
|
44
|
+
renderer: Pick<WayfindingRenderer, 'setIcon'>;
|
|
45
|
+
iconProvider: Pick<IconProvider, 'getIcon'>;
|
|
46
|
+
floorContext: Pick<FloorContext, 'isLayerVisible'>;
|
|
47
|
+
trails: TrailView;
|
|
48
|
+
iconLayer: string;
|
|
49
|
+
snapThreshold: number;
|
|
50
|
+
}): PositionTrailView;
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=positionTrailView.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createPositionView } from './positionView.js';
|
|
2
|
+
import { snapPositionToRoute } from './snapPositionToRoute.js';
|
|
3
|
+
const TRAIL_SLOT = 'trail';
|
|
4
|
+
export function createPositionTrailView({ renderer, iconProvider, floorContext, trails, iconLayer, snapThreshold, }) {
|
|
5
|
+
const positionIcon = createPositionView({ renderer, iconProvider, layer: iconLayer });
|
|
6
|
+
return {
|
|
7
|
+
placeIcon(position) {
|
|
8
|
+
const hidden = !floorContext.isLayerVisible(position.layer);
|
|
9
|
+
positionIcon.place(position, hidden);
|
|
10
|
+
},
|
|
11
|
+
hideIcon() {
|
|
12
|
+
positionIcon.hide();
|
|
13
|
+
},
|
|
14
|
+
applyToRoute(position, route, routeLines) {
|
|
15
|
+
const snap = snapPositionToRoute(position, routeLines, route.from, route.to, snapThreshold);
|
|
16
|
+
const hidden = !floorContext.isLayerVisible(position.layer);
|
|
17
|
+
if (snap.snapped) {
|
|
18
|
+
positionIcon.place({ ...position, x: snap.snappedPoint.x, y: snap.snappedPoint.y }, hidden);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
positionIcon.place(position, hidden);
|
|
22
|
+
}
|
|
23
|
+
return snap;
|
|
24
|
+
},
|
|
25
|
+
setTrail(tip, anchor) {
|
|
26
|
+
if (tip && anchor)
|
|
27
|
+
trails.place(TRAIL_SLOT, tip, anchor);
|
|
28
|
+
else
|
|
29
|
+
trails.clear(TRAIL_SLOT);
|
|
30
|
+
},
|
|
31
|
+
clearTrail() {
|
|
32
|
+
trails.clear(TRAIL_SLOT);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|