@displayxr/inline3d 1.43.0 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,78 @@ entry points (`.`, `./three`) are frozen for 1.x, while the **scene subpaths** (
5
5
  `./splat`, `./model`) are a preview tier whose options may change in any release. Entries below say
6
6
  which tier they touch, because that is what tells you whether an upgrade can move your pixels.
7
7
 
8
+ ## 1.44.0 — 2026-10-13
9
+
10
+ Touches the **core** `./three` (`DepthCursor`), `./cursor-depth`, and the **preview tier** (`./model`,
11
+ `./splat`, `./call`, `./camera`: `cursor: 'depth'`). **One deliberate look change:** a LIFTED depth
12
+ cursor is now softened by default. On the glass nothing changes (nothing is drawn there).
13
+
14
+ ### Depth cursor responsiveness (`./model`, three.js)
15
+
16
+ - The arrow follows the **newest** pointer position every frame, and the pick is amortised (3 rays a
17
+ frame) instead of casting the whole footprint synchronously.
18
+ - BVH pick: three-mesh-bvh 0.9.15 (MeshBVH only, MIT) is vendored at `js/vendor/three-mesh-bvh.js`
19
+ and loaded **only** with `cursor: 'depth'`. Nothing on the page is mutated. Skinned, morphing,
20
+ instanced and batched meshes, and partial draw ranges, keep three's own raycast.
21
+ - The pointer comes from `pointerrawupdate` (coalesced samples) where the browser has it.
22
+ - CarConcept, headless: `update()` 8.2 → 0.16 ms mean, 50.6 → 0.5 ms max.
23
+ - New `cursor: { debug: true | 'both' }`: rolling cost stats; `'both'` keeps the OS cursor visible
24
+ beside the sprite (the latency check).
25
+ - If you vendor the SDK, copy `js/vendor/` too. The BVH slice imports bare `three`.
26
+
27
+ ### Depth cursor: crosstalk softening of the lifted arrow (runtime ADR-046 Amendment 2 §B.7 parity)
28
+
29
+ - On a lenticular panel a lifted, white, sharp-edged arrow ghosts: crosstalk leaks a copy one
30
+ disparity away, and its visibility scales with contrast × edge sharpness. The runtime softens its
31
+ cursor by lift; every web depth cursor now does the same, constant for constant, so web and
32
+ native cursors look alike: an edge blur of 0.1 × the ghost offset (the neighbouring views' split,
33
+ panel px), capped at 6% of the arrow's drawn height and 8 px, and an opacity easing from 1 to 0.5
34
+ with a smoothstep over 0.03 eye baselines of lift. Both are exactly zero on the glass, and the
35
+ tip (the click point) never moves. Defaults tuned on a Windows Leia panel and confirmed on a Mac.
36
+ - **`cursor.soften`** on `./model`, `./splat`, `./call`, `./camera`, and `soften` on `DepthCursor`:
37
+ `'both'` (default), `'off'` (the crisp arrow, pixel-identical to 1.43), `'alpha'`, `'blur'`, or
38
+ `{ mode, blurK, blurMaxPx, blurMaxFrac, alphaMin, fullD }` over the defaults. Validated at the call.
39
+ - Drawn in the cursor's own material: a quad padded by the blur radius whose fragment shader
40
+ evaluates the arrow analytically at the runtime's 13 blur taps (three and PlayCanvas), or the
41
+ same function rasterised into the woven buffer (stereo pairs). Never a CSS `filter` or `opacity`.
42
+ A frame with nothing to soften draws the crisp mesh as before; zero cost without `cursor: 'depth'`.
43
+ - `./cursor-depth` exports the pieces: `cursorSoften`, `cursorGhostPx`, `cursorProjectToCanvas`,
44
+ `cursorPlacementSoften`, `cursorSoftArrowGLSL`, `cursorSoftArrowRaster`, `cursorSoftArrowSample`,
45
+ `resolveCursorSoften`, `parseCursorSoften`, `CURSOR_SOFTEN_DEFAULTS`, `CURSOR_SOFTEN_KERNEL`, and
46
+ more. The samples take `&soften=both|off|alpha|blur` next to `?cursor=depth`.
47
+ - docs/authoring-inline-3d.md §A cursor that respects depth.
48
+
49
+ ### Fixed — `cursor: 'depth'` (PlayCanvas): no GL error storm from an app's own `ShaderMaterial`
50
+
51
+ - The depth pick pass renders into two colour targets (pick id + pick depth). A mesh whose
52
+ `pc.ShaderMaterial` writes only one output (a custom backdrop, frame or chrome quad) had every
53
+ pick draw rejected with `GL_INVALID_OPERATION: Active draw buffers with missing fragment shader
54
+ outputs` — hundreds a second on a panel, until Chromium stopped reporting them. Such meshes are now
55
+ left out of the pick pass (they never reached the pick buffer anyway), so nothing the cursor sees
56
+ changes. Materials that handle `DEPTH_PICK_PASS`, followed through their `#include`s (the
57
+ engine's gsplat material included), stay in.
58
+
59
+ ### Fixed — the depth cursor drifted off a still pointer while the camera moved
60
+
61
+ - With `cursor: 'depth'` and the default `anchor: 'hybrid'`, a still pointer's arrow was pinned in
62
+ the SCENE, so anything that moved the camera rig under it carried it away: `./model`'s idle
63
+ turntable (8°/s) put it ~0.26 of the canvas off the pointer within ~6 s, and a damped drag or a
64
+ scripted camera did the same. The kept line of sight now lives in the display's frame (relative
65
+ to the canvas, in canvas heights), as in the runtime's `u_cursor_depth`, which works in display
66
+ space: a rig orbit, pan or zoom leaves the arrow on the pointer, and a head move still
67
+ parallaxes it like content. A canvas whose shape changes under a still pointer (a resize)
68
+ re-anchors, as does the head pulling back past the anchor eye. `'screen'` and `'world'` are
69
+ unchanged (neither keeps state across frames).
70
+
71
+ ## 1.43.1 — 2026-10-13
72
+
73
+ ### Fixed — `warmEntity` on a display-rig layer
74
+
75
+ - `warmEntity` warmed only through the eye camera(s). A mesh on a layer moved to the display rig
76
+ (`setLayerRig`, e.g. a round stage) is drawn **only** by the layer rig's cameras, with their own
77
+ `shaderParams`, so its real program still linked on first draw while the warm issued a variant
78
+ nobody draws. It now warms through the layer rig's display/post cameras too.
79
+
8
80
  ## 1.43.0 — 2026-10-12
9
81
 
10
82
  ### `./splat` (PlayCanvas) — `warmEffect`: no frozen frame when a reveal first plays
@@ -1,13 +1,15 @@
1
1
  # Third-party notices — `@displayxr/inline3d`
2
2
 
3
- `@displayxr/inline3d` is Apache-2.0 (see `LICENSE`). It bundles no third-party code, but parts of
4
- it are adapted from, or run on, the MIT-licensed projects below. Their notices follow.
3
+ `@displayxr/inline3d` is Apache-2.0 (see `LICENSE`). It bundles one piece of third-party code —
4
+ a slice of three-mesh-bvh, below — and parts of it are adapted from, or run on, the MIT-licensed
5
+ projects below. Their notices follow.
5
6
 
6
7
  | Project | Version | How it is used |
7
8
  |---|---|---|
8
9
  | [PlayCanvas engine](https://github.com/playcanvas/engine) | 2.22.3 | Optional peer (`playcanvas`): the default splat backend and the `./model` backend. Shader code in this package is **adapted** from it: the non-square-pixel footprint rewrite of `src/scene/shader-lib/glsl/chunks/gsplat/vert/gsplatCorner.js` (`js/inline3d-splat-playcanvas.js`), the quad-extent cap in `gsplatCommon.js` (`js/inline3d-splat-perf.js`), and the dissolve effect and its hash/noise/fbm helpers from `scripts/esm/gsplat/shader-effect-dissolve.mjs` (`js/inline3d-splat-effects.js`). Each adapted site carries a provenance comment. |
9
10
  | [three.js](https://github.com/mrdoob/three.js) | ≥ 0.150 | Optional peer (`three`): the `./three` helpers, the Spark splat path, `./model`'s `engine: 'three'`. The PlayCanvas backend's `environment: 'room'` is **adapted** from `examples/jsm/environments/RoomEnvironment.js` (its scene numbers, re-rendered by ray casting in `js/inline3d-model-playcanvas.js`, `ROOM_ENVIRONMENT`). |
10
11
  | [Spark](https://github.com/sparkjsdev/spark) | ≥ 2.0 | Optional peer (`@sparkjsdev/spark`): the Spark splat backend. |
12
+ | [three-mesh-bvh](https://github.com/gkjohnson/three-mesh-bvh) | 0.9.15 | **Bundled**: `js/vendor/three-mesh-bvh.js` is its `MeshBVH`, minified by `tools/vendor/three-mesh-bvh.mjs` (three stays external). Loaded only when a page opts into `cursor: 'depth'` on `./model`'s three.js engine, for the cursor's BVH pick (`js/inline3d-cursor-bvh.js`). |
11
13
 
12
14
  ## PlayCanvas engine — MIT
13
15
 
@@ -75,6 +77,28 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
75
77
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
76
78
  THE SOFTWARE.
77
79
 
80
+ ## three-mesh-bvh — MIT
81
+
82
+ Copyright (c) 2018 Garrett Johnson
83
+
84
+ Permission is hereby granted, free of charge, to any person obtaining a copy
85
+ of this software and associated documentation files (the "Software"), to deal
86
+ in the Software without restriction, including without limitation the rights
87
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
88
+ copies of the Software, and to permit persons to whom the Software is
89
+ furnished to do so, subject to the following conditions:
90
+
91
+ The above copyright notice and this permission notice shall be included in all
92
+ copies or substantial portions of the Software.
93
+
94
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
95
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
96
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
97
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
98
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
99
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
100
+ SOFTWARE.
101
+
78
102
  ## Sample assets (repo samples only, not in the npm package)
79
103
 
80
104
  The pages under `samples/` show these assets. None is in the published `@displayxr/inline3d`
package/camera.d.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  // until the snapshot is updated in the same change (`node tools/api-surface.mjs --update`).
13
13
 
14
14
  import type { Inline3D, Inline3DUnsupported, TileHandle } from './index.js';
15
+ import type { CursorSoftenOption } from './cursor-depth.js';
15
16
 
16
17
  /** What the camera delivers: a side-by-side pair (left eye left) or one flat picture. */
17
18
  export type CameraFormat = 'sbs' | 'mono';
@@ -193,6 +194,8 @@ export interface PairCursorOptions {
193
194
  height?: number;
194
195
  /** `'window'` (default): the cursor stays while the pointer is over DOM on the tile (a badge). `'canvas'`: only over the canvas itself. */
195
196
  pointerScope?: 'canvas' | 'window';
197
+ /** Crosstalk softening of the lifted arrow (runtime ADR-046 Amendment 2 §B.7), drawn into the woven pixels. Default `'both'`. */
198
+ soften?: CursorSoftenOption;
196
199
  }
197
200
 
198
201
  export interface CameraViewOptions {
package/cursor-depth.d.ts CHANGED
@@ -66,7 +66,9 @@ export function placeCursor(g: CursorGeometry, disparity: number, heightFraction
66
66
  /**
67
67
  * Where along the cursor's depth the sprite goes.
68
68
  * - `'hybrid'` (default): on the line of sight while the pointer MOVES (exactly over it), and
69
- * world-FIXED while it is still, so it parallaxes with the content as the head moves.
69
+ * fixed relative to the DISPLAY while it is still, so it parallaxes with the content as the head
70
+ * moves. Anchored in the display's frame, not the scene's: a rig that orbits, pans or zooms
71
+ * (an idle spin, a damped drag, a scripted camera) does not move a still cursor off the pointer.
70
72
  * - `'screen'`: always on the line of sight. Never parallaxes; on a head-tracked display it can
71
73
  * read as "at the glass".
72
74
  * - `'world'`: always straight in front of the pointer's canvas point. Parallaxes; drifts ~mm off
@@ -85,6 +87,15 @@ export interface CursorOptions {
85
87
  anchor?: CursorAnchorMode;
86
88
  /** `'window'`: keep the cursor over DOM layered on the canvas, and hide the CSS cursor page-wide meanwhile. Default `'canvas'`. */
87
89
  pointerScope?: 'canvas' | 'window';
90
+ /**
91
+ * Diagnostics, off by default and free when off. `true`: log the cursor's per-frame cost
92
+ * (update ms, rays a frame, frame interval, pointer-sample age) as rolling stats, at most once a
93
+ * second. `'both'`: that, and the OS cursor stays visible beside the sprite, for filming the lag
94
+ * between them on a panel. three.js backend (./model `engine: 'three'`, ./splat on Spark).
95
+ */
96
+ debug?: boolean | 'both';
97
+ /** Crosstalk softening of the lifted arrow (runtime ADR-046 Amendment 2 §B.7). Default `'both'`. */
98
+ soften?: CursorSoftenOption;
88
99
  }
89
100
  export const CURSOR_OPTION_KEYS: readonly (keyof CursorOptions)[];
90
101
  /** `cursor` as ./model and ./splat accept it: null = off; throws on a bad value. */
@@ -152,3 +163,115 @@ export class CursorDepthPlacer {
152
163
  nowSec: number,
153
164
  ): CursorPlacement;
154
165
  }
166
+
167
+ // ── Crosstalk softening of the lifted cursor (runtime ADR-046 Amendment 2 §B.7) ─────────────
168
+
169
+ /**
170
+ * `'both'` (default): edge blur AND opacity easing; `'off'`: neither (the crisp arrow, exactly as
171
+ * before 1.44); `'alpha'` / `'blur'`: one of them (A/B presets).
172
+ */
173
+ export type CursorSoftenPreset = 'both' | 'off' | 'alpha' | 'blur';
174
+ /** The tuning knobs, each over the runtime's default (U_CURSOR_SOFTEN_DEFAULT_*). */
175
+ export interface CursorSoftenTuning {
176
+ /** Which effects; default `'both'`. */
177
+ mode?: CursorSoftenPreset;
178
+ /** Panel px of blur per panel px of ghost offset (the views' on-screen split). Default 0.1; >= 0. */
179
+ blurK?: number;
180
+ /** Hard ceiling on the blur radius, panel px. Default 8; [0, 8]. */
181
+ blurMaxPx?: number;
182
+ /** Blur cap as a fraction of the drawn arrow's height. Default 0.06; [0, 0.5] (0 = off). */
183
+ blurMaxFrac?: number;
184
+ /** Opacity at full lift. Default 0.5; [0, 1]. */
185
+ alphaMin?: number;
186
+ /** |disparity| (eye baselines) at which the opacity reaches alphaMin. Default 0.03; > 0. */
187
+ fullD?: number;
188
+ }
189
+ /** `cursor.soften`: a preset, a tuning object, `true` (= default) or `false` (= `'off'`). */
190
+ export type CursorSoftenOption = CursorSoftenPreset | CursorSoftenTuning | boolean;
191
+ /** The resolved parameter set. */
192
+ export interface CursorSoftenParams {
193
+ blur: boolean;
194
+ alpha: boolean;
195
+ blurK: number;
196
+ blurMaxPx: number;
197
+ blurMaxFrac: number;
198
+ alphaMin: number;
199
+ fullD: number;
200
+ }
201
+ export const CURSOR_SOFTEN_PRESETS: readonly CursorSoftenPreset[];
202
+ export const CURSOR_SOFTEN_DEFAULTS: Readonly<CursorSoftenParams>;
203
+ export const CURSOR_SOFTEN_KEYS: readonly (keyof CursorSoftenTuning)[];
204
+ /** `cursor.soften` resolved over the defaults; throws on a bad value. */
205
+ export function resolveCursorSoften(opt: unknown, who?: string): CursorSoftenParams;
206
+ /**
207
+ * The runtime's lenient string parse (u_cursor_soften_params_parse), e.g. for URL parameters: a
208
+ * value that does not parse or is out of range keeps its default. `changed`: any knob was set.
209
+ */
210
+ export function parseCursorSoften(
211
+ mode?: string | null,
212
+ blurK?: string | null,
213
+ blurMaxPx?: string | null,
214
+ blurMaxFrac?: string | null,
215
+ alphaMin?: string | null,
216
+ fullD?: string | null,
217
+ ): { params: CursorSoftenParams; changed: boolean };
218
+ /**
219
+ * One frame's softening (u_cursor_draw_soften_sized): blur radius (panel px) = blurK × |ghostPx|,
220
+ * capped at blurMaxPx and blurMaxFrac × `spritePx`; opacity eases 1 → alphaMin by a smoothstep
221
+ * over |d| up to fullD. (0, 1) on the glass.
222
+ */
223
+ export function cursorSoften(d: number, ghostPx: number, spritePx?: number, params?: CursorSoftenParams | null): { blurPx: number; alpha: number };
224
+ /** Largest panel-px distance between the hotspot in NEIGHBOURING views (canvas-normalised origins). */
225
+ export function cursorGhostPx(origins: ArrayLike<ArrayLike<number> | null> | null, pxW: number, pxH: number): number;
226
+ /** Canvas-normalised [u, v] (v down) of a point seen from a view, or null when not in front of it. */
227
+ export function cursorProjectToCanvas(view: CursorView | XRView, point: ArrayLike<number>): [number, number] | null;
228
+ /** A placement's softening; `radius` / `aa` in arrow heights; `soft` false = draw the crisp arrow. */
229
+ export interface CursorSoftening {
230
+ blurPx: number;
231
+ alpha: number;
232
+ ghostPx: number;
233
+ spritePx: number;
234
+ radius: number;
235
+ aa: number;
236
+ soft: boolean;
237
+ }
238
+ /** The softening for a placement drawn with `views` on a `panelW` × `panelH` px canvas, the arrow `heightFraction` of it tall. */
239
+ export function cursorPlacementSoften(
240
+ placement: CursorPlacement | { active: false } | null,
241
+ views: ArrayLike<CursorView | XRView>,
242
+ panelW: number,
243
+ panelH: number,
244
+ heightFraction: number,
245
+ params?: CursorSoftenParams,
246
+ ): CursorSoftening;
247
+ /** The runtime's 13-tap blur kernel, `[dx, dy, weight]` in units of the radius. */
248
+ export const CURSOR_SOFTEN_KERNEL: ReadonlyArray<readonly [number, number, number]>;
249
+ /** The arrow's [width, height] in its own units (height 1). */
250
+ export const CURSOR_ARROW_SIZE: readonly [number, number];
251
+ /** The arrow's box padded by `radius` (+ `aa`), arrow units, y down, tip at the origin: [x0, y0, x1, y1]. */
252
+ export function cursorSoftArrowBox(radius: number, aa?: number): [number, number, number, number];
253
+ /** Signed distance to a polygon (negative inside). */
254
+ export function cursorPolygonSd(poly: ReadonlyArray<readonly [number, number]>, x: number, y: number): number;
255
+ /** The softened arrow at (x, y) of arrow units: straight RGB + alpha (before opacity). The shader's reference. */
256
+ export function cursorSoftArrowSample(
257
+ x: number,
258
+ y: number,
259
+ radius: number,
260
+ aa: number,
261
+ fill?: readonly number[],
262
+ outline?: readonly number[],
263
+ polys?: ReadonlyArray<ReadonlyArray<readonly [number, number]>>,
264
+ ): [number, number, number, number];
265
+ /** GLSL defining `vec4 inline3dSoftArrow(vec2 p, float radius, float aa, vec3 fill, vec3 outline)`. */
266
+ export function cursorSoftArrowGLSL(polys?: ReadonlyArray<ReadonlyArray<readonly [number, number]>>): string;
267
+ /** The softened arrow rasterised for a 2D canvas, its tip at (cx, cy), `sizePx` tall: straight-alpha RGBA bytes. */
268
+ export function cursorSoftArrowRaster(
269
+ cx: number,
270
+ cy: number,
271
+ sizePx: number,
272
+ radius: number,
273
+ alpha: number,
274
+ fill?: readonly number[],
275
+ outline?: readonly number[],
276
+ polys?: ReadonlyArray<ReadonlyArray<readonly [number, number]>>,
277
+ ): { x0: number; y0: number; width: number; height: number; data: Uint8ClampedArray };