@expofp/wayfinding 3.37.1 → 3.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,6 +22,18 @@ export interface WayfindingConfig {
22
22
  readonly layers: WayfindingLayers;
23
23
  /** Plan GPS calibration; the snap and reroute thresholds are derived from it. */
24
24
  readonly gpsConfig?: GpsConfig;
25
+ /**
26
+ * Draws every icon facing the camera, through `ImageDef.screenSpace`. Set it when the plan
27
+ * renders in 3D, together with the `overlay` option of `initWayfindingLayers`.
28
+ *
29
+ * An icon then keeps its plan anchor and the on-screen size `onScale` gives it, and stands up to
30
+ * face the viewer. Two costs come with that. It takes {@link IconConfig.cardinalSnap} out of
31
+ * use, because the renderer owns a screen-space def's orientation and ignores a roll rule on
32
+ * one. It also changes what {@link IconConfig.rotation} turns about, from the plan's normal to
33
+ * the screen's, so an icon whose rotation carries a plan heading points at a screen direction
34
+ * instead.
35
+ */
36
+ readonly screenSpaceIcons?: boolean;
25
37
  readonly onTransitionClick?: (point: TransitionPointDef) => void;
26
38
  /** Fired when the route changes. `bounds.isEmpty()` detects an empty/cleared route. */
27
39
  readonly onRouteUpdate?: (lines: readonly RouteLine[], bounds: Box) => void;
@@ -4,7 +4,9 @@ import { createWayfindingRuntime, } from './runtime/index.js';
4
4
  export function createWayfinding(config) {
5
5
  const { dataSource, renderer: rendererPort, iconProvider, floorContext, layers, gpsConfig, } = config;
6
6
  const engine = createWayfindingEngine(dataSource);
7
- const renderer = createWayfindingRenderer(rendererPort, layers);
7
+ const renderer = createWayfindingRenderer(rendererPort, layers, {
8
+ screenSpaceIcons: config.screenSpaceIcons,
9
+ });
8
10
  const snapThresholdMeters = getThresholdOrDefault(gpsConfig?.snapThreshold, SNAP_THRESHOLD_METERS);
9
11
  const snapThreshold = getThresholdFromMetersToSvg({
10
12
  thresholdInMeters: snapThresholdMeters,
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export type { GpsConfig, Wayfinding, WayfindingConfig } from './createWayfinding.js';
8
8
  export { createWayfinding } from './createWayfinding.js';
9
- export type { WayfindingLayers } from './renderer/index.js';
9
+ export type { WayfindingLayerOptions, WayfindingLayers } from './renderer/index.js';
10
10
  export { initWayfindingLayers, resolveWayfindingLayers } from './renderer/index.js';
11
11
  export type { GraphDataSource } from './core/index.js';
12
12
  export type { IconConfig, RendererPort } from './renderer/index.js';
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ export { initWayfindingLayers, resolveWayfindingLayers } from './renderer/index.
10
10
  // route endpoint so the runtime renders that point as the live position.
11
11
  export { CURRENT_POSITION_POINT_ID } from './runtime/index.js';
12
12
  // Pure routing helper: orders waypoints for the shortest visiting sequence
13
- // (nearest-neighbour + 2-opt). Stateless and instance-free, so callers can plan
13
+ // (nearest-neighbor + 2-opt). Stateless and instance-free, so callers can plan
14
14
  // multi-stop routes before a wayfinding instance exists (e.g. the SDK
15
15
  // `getOptimizedRoutes`).
16
16
  export { optimizeWaypointOrder } from './core/routing/optimizeWaypointOrder.js';
@@ -14,7 +14,7 @@ const DEFAULT_COLORS = {
14
14
  export function createWayfindingRenderer(port, layers, config = {}) {
15
15
  const colors = { ...DEFAULT_COLORS, ...config.colors };
16
16
  const layerManager = createLayerManager(port);
17
- const iconManager = createIconManager(layerManager, port, layers);
17
+ const iconManager = createIconManager(layerManager, port, layers, config.screenSpaceIcons);
18
18
  const trailManager = createTrailManager(layerManager, layers.trail);
19
19
  const routeLineManager = createRouteLineManager(layerManager, colors, port, layers);
20
20
  const wfRenderer = {
@@ -36,7 +36,8 @@ export interface IconManager {
36
36
  * @param layerManager - Tracks what changed.
37
37
  * @param port - The host's roll rule and commit.
38
38
  * @param layers - The icon layers to place into.
39
+ * @param screenSpace - Whether every icon faces the camera rather than lying on the plan.
39
40
  * @returns The manager.
40
41
  */
41
- export declare function createIconManager(layerManager: LayerManager, port: RendererPort, layers: Pick<WayfindingLayers, 'icons' | 'currentPosition'>): IconManager;
42
+ export declare function createIconManager(layerManager: LayerManager, port: RendererPort, layers: Pick<WayfindingLayers, 'icons' | 'currentPosition'>, screenSpace?: boolean): IconManager;
42
43
  //# sourceMappingURL=iconManager.d.ts.map
@@ -9,6 +9,9 @@ import { Rect } from '@expofp/geometry';
9
9
  */
10
10
  export function rescaleIcon(def, pixelSize) {
11
11
  const bounds = def.bounds;
12
+ // `ImageSource` also covers a `Blob`, which holds bytes and reports no pixel size. Every icon def
13
+ // is built from `IconConfig.canvas` in `createIcon`, so the source here is always a canvas, and a
14
+ // canvas reports its own size.
12
15
  const source = def.source;
13
16
  const width = source.width * pixelSize;
14
17
  const height = source.height * pixelSize;
@@ -22,9 +25,10 @@ export function rescaleIcon(def, pixelSize) {
22
25
  * @param layerManager - Tracks what changed.
23
26
  * @param port - The host's roll rule and commit.
24
27
  * @param layers - The icon layers to place into.
28
+ * @param screenSpace - Whether every icon faces the camera rather than lying on the plan.
25
29
  * @returns The manager.
26
30
  */
27
- export function createIconManager(layerManager, port, layers) {
31
+ export function createIconManager(layerManager, port, layers, screenSpace = false) {
28
32
  const icons = new Map();
29
33
  const iconKeysByName = new Map();
30
34
  const keyToName = new Map();
@@ -62,6 +66,10 @@ export function createIconManager(layerManager, port, layers) {
62
66
  }
63
67
  function createIcon(iconKey, name, cfg, rotation = cfg.rotation) {
64
68
  const layer = layerFor(cfg);
69
+ // The caller's own answer wins where it gave one. An icon whose rotation carries a plan bearing
70
+ // opts out, because a screen-space def spins about the screen's normal and would point at a
71
+ // fixed screen direction instead.
72
+ const facesCamera = cfg.screenSpace ?? screenSpace;
65
73
  layerManager.touchLayer(layer);
66
74
  // Size zero on purpose: an icon's size is only expressible in terms of the current pixel size, and
67
75
  // `rescaleIcon` is its one writer. The renderer lays a def out as it registers, hidden or not, so
@@ -72,8 +80,11 @@ export function createIconManager(layerManager, port, layers) {
72
80
  hidden: cfg.hidden ?? false,
73
81
  dim: cfg.dimmed ? 1 : 0,
74
82
  origin: cfg.origin,
83
+ screenSpace: facesCamera,
75
84
  };
76
- if (cfg.cardinalSnap)
85
+ // A screen-space icon already stands up to face the viewer, so it has no readability to keep
86
+ // through a quarter turn. Its own billboarding is what a cardinal snap would be for.
87
+ if (cfg.cardinalSnap && !facesCamera)
77
88
  port.attachCardinalSnap(def);
78
89
  def.onScale = ({ pixelSize }) => rescaleIcon(def, pixelSize);
79
90
  layer.children.push(def);
@@ -1,5 +1,5 @@
1
1
  export { createWayfindingRenderer } from './createWayfindingRenderer.js';
2
- export type { WayfindingLayers } from './layers.js';
2
+ export type { WayfindingLayerOptions, WayfindingLayers } from './layers.js';
3
3
  export { initWayfindingLayers, resolveWayfindingLayers } from './layers.js';
4
4
  export type { IconConfig, RendererPort, WayfindingRenderer } from './types.js';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -6,6 +6,20 @@ import type { ImageDef, LayerDef, LineDef } from '@expofp/renderer';
6
6
  * images and lines cannot share one. Which layers exist and how they stack among themselves is this
7
7
  * package's business; where the block sits in a scene is the host's.
8
8
  */
9
+ /** How the host wants the block built. */
10
+ export interface WayfindingLayerOptions {
11
+ /**
12
+ * Draws the whole block above 3D content, through `LayerDef.overlay`.
13
+ *
14
+ * Set it when the plan renders in 3D. A route runs along the floor, so a booth volume standing
15
+ * between it and a tilted camera hides it. The overlay tier turns depth testing off for the
16
+ * block and draws it after every plan-tier layer, which is the case the tier exists for.
17
+ *
18
+ * Set on the block alone, because `overlay` accumulates down the layer tree. Every layer inside
19
+ * inherits it, and none of them can opt back out.
20
+ */
21
+ readonly overlay?: boolean;
22
+ }
9
23
  export interface WayfindingLayers {
10
24
  /** Dot trail between an off-graph anchor and the route. */
11
25
  readonly trail: LayerDef<ImageDef>;
@@ -23,9 +37,10 @@ export interface WayfindingLayers {
23
37
  *
24
38
  * Mount it wherever wayfinding belongs in the scene — the route has to draw over the pathway layers
25
39
  * (efp #1206). `createWayfinding` finds it there and fills it.
40
+ * @param options - How to build the block. See {@link WayfindingLayerOptions}.
26
41
  * @returns The block to mount.
27
42
  */
28
- export declare function initWayfindingLayers(): LayerDef<LayerDef>;
43
+ export declare function initWayfindingLayers(options?: WayfindingLayerOptions): LayerDef<LayerDef>;
29
44
  /**
30
45
  * Finds the mounted block in a scene and hands back the layers inside it, so a host that mounted
31
46
  * the block needs to carry nothing but its scene.
@@ -11,9 +11,10 @@ const CURRENT_POSITION_LAYER_NAME = 'wf-current-position';
11
11
  *
12
12
  * Mount it wherever wayfinding belongs in the scene — the route has to draw over the pathway layers
13
13
  * (efp #1206). `createWayfinding` finds it there and fills it.
14
+ * @param options - How to build the block. See {@link WayfindingLayerOptions}.
14
15
  * @returns The block to mount.
15
16
  */
16
- export function initWayfindingLayers() {
17
+ export function initWayfindingLayers(options = {}) {
17
18
  const trail = { name: TRAIL_LAYER_NAME, children: [] };
18
19
  const lines = { name: LINES_LAYER_NAME, children: [] };
19
20
  const linesAnimated = { name: LINES_ANIMATED_LAYER_NAME, children: [] };
@@ -29,6 +30,9 @@ export function initWayfindingLayers() {
29
30
  // Once for the block, which the children inherit: a search dims the venue behind the route,
30
31
  // never the route.
31
32
  dim: 0,
33
+ // Once for the block as well, and for the same reason. `overlay` accumulates down the tree, so
34
+ // the five layers above inherit it and none of them can drop back to the plan tier.
35
+ overlay: options.overlay,
32
36
  };
33
37
  }
34
38
  /**
@@ -55,11 +55,35 @@ export interface IconConfig {
55
55
  * Set at first setIcon for an icon key; ignored on subsequent updates.
56
56
  */
57
57
  readonly cardinalSnap?: boolean;
58
+ /**
59
+ * Overrides {@link WayfindingRendererConfig.screenSpaceIcons} for this icon alone.
60
+ *
61
+ * Pass `false` where {@link rotation} carries a plan bearing, such as the position arrow's device
62
+ * heading or a kiosk's facing. A screen-space def spins about the screen's normal rather than the
63
+ * plan's, so such an icon would point at a fixed screen direction instead of at the bearing it
64
+ * was given. Leave it unset for an icon whose rotation means nothing, which is most of them.
65
+ *
66
+ * Set at first setIcon for an icon key, as {@link origin} and {@link cardinalSnap} are. A later
67
+ * call that changes it is ignored unless the canvas changes too, which rebuilds the def.
68
+ */
69
+ readonly screenSpace?: boolean;
58
70
  readonly onClick?: () => void;
59
71
  }
60
72
  export interface WayfindingRendererConfig {
61
73
  /** Optional color overrides; defaults applied if omitted. */
62
74
  readonly colors?: Partial<RouteColors>;
75
+ /**
76
+ * Draws every icon facing the camera, through `ImageDef.screenSpace`.
77
+ *
78
+ * Set it when the plan renders in 3D. An icon keeps its plan anchor and the on-screen size
79
+ * `onScale` gives it, and stands up to face the viewer instead of lying on the floor.
80
+ *
81
+ * This takes {@link IconConfig.cardinalSnap} out of use, because billboarding is already what
82
+ * that rule was for. It also changes what {@link IconConfig.rotation} turns about, from the
83
+ * plan's normal to the screen's, so an icon whose rotation carries a plan bearing opts out
84
+ * through {@link IconConfig.screenSpace}.
85
+ */
86
+ readonly screenSpaceIcons?: boolean;
63
87
  }
64
88
  /**
65
89
  * WayfindingRenderer — generic ImageDef/LineDef renderer for wayfinding visuals.
@@ -14,6 +14,11 @@ export function createPositionView({ renderer, iconProvider, }) {
14
14
  x: input.x,
15
15
  y: input.y,
16
16
  rotation,
17
+ // The arrow points along the device's own heading, which is a direction in the venue, so it
18
+ // stays flat on the plan and turns with it. A screen-space def spins about the screen's
19
+ // normal and would hold one screen direction whatever the plan is turned to. The
20
+ // headingless dot has no direction to lose, so it takes the renderer's own setting.
21
+ screenSpace: hasHeading ? false : undefined,
17
22
  hidden,
18
23
  dimmed: hidden,
19
24
  origin: [0.5, 0.5],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expofp/wayfinding",
3
- "version": "3.37.1",
3
+ "version": "3.39.0",
4
4
  "type": "module",
5
5
  "description": "ExpoFP SDK internal: framework-neutral wayfinding (routing, snapping, scene rendering)",
6
6
  "homepage": "https://developer.expofp.com/",
@@ -34,12 +34,12 @@
34
34
  "dependencies": {
35
35
  "ngraph.graph": "^19.1.0",
36
36
  "tslib": "^2.3.0",
37
- "@expofp/geometry": "3.37.1"
37
+ "@expofp/geometry": "3.39.0"
38
38
  },
39
39
  "peerDependencies": {
40
- "@expofp/renderer": "3.37.1"
40
+ "@expofp/renderer": "3.39.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@expofp/renderer": "3.37.1"
43
+ "@expofp/renderer": "3.39.0"
44
44
  }
45
45
  }