@displayxr/inline3d 1.49.3 → 1.51.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,62 @@ 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
+ ## Unreleased
9
+
10
+ ## 1.51.0 — 2026-11-08
11
+
12
+ ### Added — `./call`: `openTabShare()`, share a browser tab in 3D
13
+
14
+ Opens the browser's tab picker and returns a track for `shareContent`. In a DisplayXR Browser with
15
+ stereo tab capture (1.9.4+), a picked tab comes back as a true side-by-side track, so receivers need
16
+ nothing new; a window, a screen or any other browser comes back flat. Needs
17
+ `MediaStreamTrackProcessor` for the stereo path.
18
+
19
+ ### Fixed — `./splat` (`engine: 'playcanvas'`): no false "footprint fix SKIPPED" warning on PlayCanvas 2.23
20
+
21
+ Preview tier. PlayCanvas 2.23 fixed the side-by-side splat footprint upstream, so the SDK now
22
+ leaves that shader chunk untouched there instead of warning that splats will draw at half height.
23
+ 2.22.3 is still patched as before, and remains the only version validated on a panel.
24
+
25
+ ## 1.50.0 — 2026-11-04
26
+
27
+ ### Fixed — `./camera`, `./call`: half-width side-by-side webcams keep their aspect ratio
28
+
29
+ A USB stereo webcam that sends 3840x2160 half-SBS came through as a 3840x1080 centre crop: each eye
30
+ 2x too tall, with half the vertical view. Such a camera (listed by USB id, since its shape matches
31
+ a 4K webcam's) is now read in its native mode and sent as 2560x720 with square-pixel eyes. Needs
32
+ `MediaStreamTrackProcessor`; without it the camera opens as before. `prefer: 'mono'` stays raw.
33
+
34
+ ### Added — `@displayxr/inline3d/hands`: tracked hands, and pinch controls for every viewer (`./hands`, `./viewer`, `./model`, `./splat`)
35
+
36
+ Preview tier. The DisplayXR Browser 1.8.0+ (Windows, with a hand provider such as an Ultraleap
37
+ tracker) exposes standard WebXR Hand Input on an inline-3D session: one `XRInputSource` per hand
38
+ with a 25-joint `XRHand`, located in the same rig frame as the eye views. The new subpath reads it
39
+ and turns it into controls:
40
+
41
+ - `watchHands(wall, onFrame)` — the tracked hands every session frame (`poses`, `radii`,
42
+ `joint(name)`, `thumbTip`, `indexTip`, `palmLength`), the browser's per-frame gate
43
+ (`state.gate`: `'available'` or why hands are withheld) and the hand `inputsourceschange`.
44
+ - `HandGestureController` / `PinchDetector` — the DisplayXR Model Viewer's gesture math, ported:
45
+ hand-relative throughout — the pinch thresholds (2.5 cm enter / 4 cm exit), the orbit gain
46
+ (10 rad/m) and the two-hand separation guard are all stated for a 9.5 cm palm and scale by the
47
+ hand's own palm length, so a hand that arrives scaled turns the subject by the same angle for
48
+ the same finger travel; one pinched hand orbits, two pinched hands zoom (separation ratio), roll
49
+ (twist) and orbit (midpoint drag); EMA-smoothed, anchored on every mode change.
50
+ - `handControls(wall, target)` — drives anything with the viewer pose surface from those
51
+ gestures; and the option **`hands: true`** on `addModel` / `addSplat` (both engines, every
52
+ entry) wires it for you, exposed as `handle.hands`. Pointer and wheel keep working alongside.
53
+ - `SceneViewer.nudgePose(delta)` and `PlayCanvasSplatViewer.nudgePose(delta)` — move the pose by
54
+ an eased delta, counting as input for the idle turntable (what the hands call; a page may too).
55
+ - `OrbitPose.roll` on both viewers — degrees about the view axis, outside yaw and pitch (Euler
56
+ order `'ZXY'`; the PlayCanvas pivot quaternion gains the same Rz factor), bit-identical to
57
+ before at roll 0; `getPose()` now reports `roll`. (The first cut left the PlayCanvas viewer
58
+ without a roll axis, so the two-hand twist did nothing on the default engine — found on the
59
+ panel 2026-11-04.)
60
+
61
+ Inert everywhere else: no hands, nothing moves, no errors. New sample `samples/hands/` (the raw
62
+ surface), and `?hands` on `samples/model/` and `samples/splat/`. The porting guide's "no input
63
+ sources" row is corrected (web#229). Tracks DisplayXR/displayxr-web#228.
8
64
  ## 1.49.3 — 2026-11-04
9
65
 
10
66
  ### Fixed — a camera view is not "woven" before the camera's first frame; `frameAgeMs` tells a stalled track (`.`, `./camera`, `./call`)
package/README.md CHANGED
@@ -35,6 +35,9 @@ import { addModel } from '@displayxr/inline3d/model'; // experimen
35
35
  import { addPlayer } from '@displayxr/inline3d/player'; // preview: a media player, built
36
36
  // on addVideo, with transport
37
37
  import { SceneViewer } from '@displayxr/inline3d/viewer'; // experimental: framing + orbit
38
+ import { handControls } from '@displayxr/inline3d/hands'; // experimental: tracked hands,
39
+ // pinch to orbit (or `hands: true`
40
+ // on addModel / addSplat)
38
41
  ```
39
42
 
40
43
  No build step or bundler required — it's plain ES modules. You can also import a pinned version by
@@ -66,7 +69,9 @@ The repo's splat sample (`samples/splat/`) renders with PlayCanvas and keeps `?e
66
69
  its A/B switch.
67
70
  Bundlers: the engine is reached through a named-import module (`js/inline3d-playcanvas-engine.js`,
68
71
  so the unused ~40% of the engine tree-shakes away); its sort workers are Blob URLs (CSP
69
- `worker-src blob:`), and esbuild needs `node:worker_threads` marked external.
72
+ `worker-src blob:`), and esbuild needs `node:worker_threads` marked external. Pin the engine to
73
+ exactly `playcanvas@2.22.3`, the version validated on a panel
74
+ ([why](docs/playcanvas-adapter.md)).
70
75
 
71
76
  **Splat effects (preview, `engine: 'playcanvas'`).** Shader effects on the splats themselves,
72
77
  keyed on world position and time so both eyes agree: `addSplat(…, { reveal: 'inflate' | 'sweep' |
package/call.d.ts CHANGED
@@ -420,6 +420,22 @@ export function watchShareableApps(cb: (apps: ShareableApp[]) => void, opts?: {
420
420
  */
421
421
  export function openAppShare(deviceId: string, opts?: { width?: number; height?: number; frameRate?: number; mediaDevices?: MediaDevices }): Promise<AppShare>;
422
422
 
423
+ /** An opened tab share (`openTabShare`): pass `stream` (+ `descriptor`) to `shareContent`. The track is yours to stop. */
424
+ export interface TabShare {
425
+ stream: MediaStream;
426
+ track: MediaStreamTrack;
427
+ /** true: a side-by-side track. false: the user picked a window or screen, or the browser captures tabs flat. */
428
+ stereo: boolean;
429
+ descriptor: { kind: 'content'; format: CallFormat; width: number; height: number; convergence: { known: true; frac: number }; appName: null };
430
+ }
431
+
432
+ /**
433
+ * Let the user pick a browser tab to share, in 3D where the browser can (the browser's picker is
434
+ * the consent). Comes back flat (`stereo: false`) for a window, a screen, or a browser without stereo
435
+ * tab capture. Cancelling rejects with `code: 'share-denied'`, anything else with `'share-failed'`.
436
+ */
437
+ export function openTabShare(opts?: { width?: number; frameRate?: number; mediaDevices?: MediaDevices }): Promise<TabShare>;
438
+
423
439
  /**
424
440
  * `error` codes — fatal for a feature, never for the widget. `'camera-busy'` (held by another
425
441
  * app: audio-only until `retryCamera()`), `'no-camera'`, `'permission-denied'`, `'unreachable'`