@displayxr/inline3d 1.11.0 → 1.12.1

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/model.d.ts CHANGED
@@ -1,13 +1,25 @@
1
- // Type definitions for @displayxr/inline3d/model.
1
+ // Type definitions for @displayxr/inline3d/model (and ./model/three).
2
2
  // EXPERIMENTAL — not covered by the 1.x semver promise. See docs/sdk-stability.md.
3
3
 
4
4
  import type { SceneViewer, SubjectBounds, OrbitPose } from './viewer.js';
5
5
  import type { FirstWovenResult } from './index.js';
6
+ import type { PlayCanvasSplatViewer, SplatCameraPose, SplatFrameInfo } from './splat.js';
6
7
 
7
8
  export interface ModelOptions {
9
+ /**
10
+ * Which engine renders the model (1.12). `'playcanvas'` is the DEFAULT (optional peer
11
+ * `playcanvas >=2.22.3 <3`); `'three'` is the 1.11 renderer, byte for byte (optional peer
12
+ * `three`). Both are imported dynamically, so a page loads only the engine it renders with.
13
+ *
14
+ * With no `engine`: a three.js object in `GLTFLoader` / `DRACOLoader` / `KTX2Loader` / `envMap`
15
+ * selects three; otherwise PlayCanvas, falling back to three (one console warning) when
16
+ * `playcanvas` cannot be imported, and rejecting `ready` naming both when neither can. An
17
+ * explicit `engine` never falls back. Anything else throws at call time.
18
+ */
19
+ engine?: 'playcanvas' | 'three';
8
20
  /** Metres of world the tile's height spans (default 0.24). */
9
21
  virtualDisplayHeight?: number;
10
- /** Precomputed subject bounds. Rarely needed for a mesh — Box3 is exact. */
22
+ /** Precomputed subject bounds. Rarely needed for a mesh — its bounds are exact. */
11
23
  frame?: SubjectBounds;
12
24
  /** Degrees/second of turntable once idle (default 8). */
13
25
  idleSpin?: number;
@@ -25,26 +37,42 @@ export interface ModelOptions {
25
37
  /** Per-eye buffer scale; 0.5–0.7 is usually free (default 1). */
26
38
  renderScale?: number;
27
39
  feather?: number;
28
- /** Built-in three-point lighting. Meshes arrive unlit; splats do not need this. */
29
40
  /**
30
- * How the mesh is lit. `room` (default) bakes IBL from three's procedural RoomEnvironment —
31
- * metal and glass need it, because a punctual rig leaves a `metalness: 1` surface with nothing
32
- * to reflect and it renders black. `studio` is the older three-point punctual rig.
41
+ * How the mesh is lit.
42
+ *
43
+ * PlayCanvas (default engine): `room` (default; `neutral` is an alias) is image-based lighting
44
+ * from an in-memory neutral photo studio fitted to the Khronos glTF Sample Viewer's "Studio
45
+ * Neutral", with Khronos PBR Neutral tone mapping and exposure 1 — the Sample Viewer's look.
46
+ * three: `room` bakes three's procedural RoomEnvironment, untonemapped (unchanged since 1.11).
47
+ *
48
+ * Both: `studio` is the three-point punctual rig (metal renders dark under it — nothing to
49
+ * reflect); `none` adds no light at all. Anything else throws on PlayCanvas.
33
50
  */
34
- environment?: 'room' | 'studio' | 'none';
35
- /** A PMREM-processed environment texture. Better than `environment` for metal; overrides it. */
36
- envMap?: object;
37
- /** Hand in the GLTFLoader class instead of resolving it from `three/addons/`. */
51
+ environment?: 'room' | 'neutral' | 'studio' | 'none';
52
+ /**
53
+ * PlayCanvas only: yaw of the environment about +Y, degrees (default 0 = the Sample Viewer's
54
+ * default orientation).
55
+ */
56
+ environmentRotation?: number;
57
+ /**
58
+ * Your own environment; overrides `environment`.
59
+ * three: a PMREM-processed texture. PlayCanvas: a URL of an equirect image (.hdr/.png/.jpg) or
60
+ * a `pc.Texture` (equirect or cubemap), prefiltered for you. A three texture on
61
+ * `engine: 'playcanvas'` throws; with no `engine` it selects three.
62
+ */
63
+ envMap?: object | string;
64
+ /** three only: hand in the GLTFLoader class instead of resolving it from `three/addons/`. */
38
65
  GLTFLoader?: unknown;
39
66
 
40
67
  /**
41
- * Where **your page** serves three's Draco decoder and Basis (KTX2) transcoder.
68
+ * Where **your page** serves the Draco decoder and Basis (KTX2) transcoder — the SAME files on
69
+ * both engines: three's `libs/draco/` (`draco_wasm_wrapper.js` + `draco_decoder.wasm`) and
70
+ * `libs/basis/` (`basis_transcoder.js` + `.wasm`). The PlayCanvas engine's own Draco and Basis
71
+ * workers load them.
42
72
  *
43
- * `addModel` reads the asset's `extensionsUsed` before parsing and attaches only the decoders it
44
- * declares, so an uncompressed model never touches any of this. But a decoder that IS needed has
45
- * to come from somewhere, and the default is deliberately **not a CDN** — a page shipping an
46
- * offline build must not acquire a network dependency by loading a compressed file. Copy the
47
- * files out of `three` and serve them yourself:
73
+ * The asset's `extensionsUsed` decides which decoders are wired, so an uncompressed model never
74
+ * touches any of this. The default is deliberately **not a CDN** — a page shipping an offline
75
+ * build must not acquire a network dependency by loading a compressed file:
48
76
  *
49
77
  * ```sh
50
78
  * cp -r node_modules/three/examples/jsm/libs/draco/ public/draco/
@@ -52,25 +80,48 @@ export interface ModelOptions {
52
80
  * ```
53
81
  *
54
82
  * A string is a parent directory holding `draco/` and `basis/`; an object overrides either key.
55
- * `EXT_meshopt_compression` needs nothing served — its decoder is pure JS.
83
+ * On PlayCanvas the engine keeps ONE Draco and ONE Basis worker pool per page: the first tile
84
+ * that needs one sets its path, and a later tile asking for another path is warned.
56
85
  *
57
86
  * @default {draco:'/draco/', basis:'/basis/'}
58
87
  */
59
88
  decoderPath?: string | { draco?: string; basis?: string };
60
- /**
61
- * DRACOLoader class **or** a ready instance, instead of resolving `three/addons/`. A class is
62
- * constructed and pointed at `decoderPath`; an instance is used exactly as you configured it.
63
- */
89
+ /** three only: DRACOLoader class **or** a ready instance, instead of resolving `three/addons/`. */
64
90
  DRACOLoader?: unknown;
91
+ /** three only: KTX2Loader class or instance. */
92
+ KTX2Loader?: unknown;
65
93
  /**
66
- * KTX2Loader class or instance. `detectSupport()` is called for you with this tile's renderer,
67
- * and the transcoder is loaded eagerly so a mis-served path throws instead of silently
68
- * resolving a model with no textures.
94
+ * The `MeshoptDecoder` for `EXT_meshopt_compression`. Nothing to serve (pure JS). Default: three
95
+ * resolves `three/addons/libs/meshopt_decoder.module.js`; PlayCanvas resolves
96
+ * `meshoptimizer/decoder` (optional peer `meshoptimizer >=1`), only for an asset that declares it.
69
97
  */
70
- KTX2Loader?: unknown;
71
- /** The `MeshoptDecoder` namespace, instead of resolving `three/addons/`. Nothing to serve. */
72
98
  meshoptDecoder?: unknown;
73
99
 
100
+ /** PlayCanvas only: the `playcanvas` module (or a namespace with the same members) to use. */
101
+ playcanvas?: object;
102
+ /** PlayCanvas only: MSAA on the tile's WebGL context (default true, as three's renderer). */
103
+ antialias?: boolean;
104
+ /** PlayCanvas only: the context's `preserveDrawingBuffer` (default false). */
105
+ preserveDrawingBuffer?: boolean;
106
+ /** PlayCanvas only: raise every projection's near plane to at least this (depth mapping only). */
107
+ nearClip?: number;
108
+ /** PlayCanvas only: lower every projection's far plane to at most this. */
109
+ farClip?: number;
110
+ /** PlayCanvas only: the drag's tilt cap in degrees (default 15, tilt-and-relax). */
111
+ orbitMaxDeg?: number;
112
+ /** PlayCanvas only: the orbit's easing time constants, seconds. */
113
+ orbitEase?: { drag?: number; rest?: number };
114
+ /**
115
+ * PlayCanvas only: who owns the camera — the SDK's viewer (default) or the page, through
116
+ * {@link ModelHandle.setCameraPose} every frame (the attach rig; converges on the model's bounds
117
+ * centre unless the pose gives a `convergence`).
118
+ */
119
+ controls?: 'viewer' | 'page';
120
+ /** `controls: 'page'`: the comfort depth of the declared rig, in (0, 1] (default 0.3). */
121
+ comfortDepth?: number;
122
+ /** `controls: 'page'` only: runs inside the adapter's frame, before the draw. */
123
+ onBeforeFrame?: (frame: SplatFrameInfo) => void;
124
+
74
125
  /** Element whose visibility gates the lazy create/close lifecycle. */
75
126
  observe?: Element;
76
127
  /** Forwarded to the core window: see `TileOptions.firstWovenHoldMs`. */
@@ -79,16 +130,43 @@ export interface ModelOptions {
79
130
 
80
131
  /** What {@link addModel} returns — the same shape as addSplat's handle. */
81
132
  export interface ModelHandle {
82
- readonly viewer: SceneViewer;
83
- /** The loaded glTF scene root; null until `ready` resolves. */
133
+ /** Which engine renders this tile. Can change from `'playcanvas'` to `'three'` on the fallback. */
134
+ readonly backend: 'playcanvas' | 'three';
135
+ /**
136
+ * three: the SceneViewer. PlayCanvas: the backend's viewer (the SceneViewer pose surface
137
+ * without three). Null until the backend module has loaded — one module fetch after addModel
138
+ * returns (1.11 returned it synchronously; `@displayxr/inline3d/model/three` still does).
139
+ */
140
+ readonly viewer: SceneViewer | PlayCanvasSplatViewer | null;
141
+ /**
142
+ * The loaded glTF root — a three `Object3D`, or on PlayCanvas the render `pc.Entity`
143
+ * (`instantiateRenderEntity()`). Null until `ready` resolves.
144
+ */
84
145
  model: object | null;
146
+ /** PlayCanvas only: the container resource (animations, materials, textures). */
147
+ readonly container?: unknown;
148
+ /**
149
+ * PlayCanvas only (ADVANCED, not covered by the semver promise): `app` is the tile's AppBase,
150
+ * `root` the content root (the model's parent — add entities, lights or an `anim` component
151
+ * under it), `camera` the eye camera. Null on three and until the engine has booted.
152
+ */
153
+ readonly engine: { readonly app: unknown; readonly root: unknown; readonly camera: unknown } | null;
85
154
  /** Bounds used for framing; null until `ready` resolves. */
86
155
  frame: SubjectBounds | null;
87
156
  /** Resolves once the model has loaded and been framed; rejects if the load failed. */
88
157
  readonly ready: Promise<ModelHandle>;
89
158
 
159
+ /** Throws on `controls: 'page'`. */
90
160
  setPose(pose?: OrbitPose): void;
161
+ /** Throws on `controls: 'page'`. */
91
162
  resetPose(): void;
163
+ /** `controls: 'page'` only (throws otherwise): the camera for the next frame, model space. */
164
+ setCameraPose(
165
+ matrixWorld: ArrayLike<number>,
166
+ opts: { verticalFovDeg: number; near?: number; far?: number; convergence?: number | null },
167
+ ): ModelHandle;
168
+ /** The last pose the page set (a copy), or null. */
169
+ getCameraPose(): SplatCameraPose | null;
92
170
 
93
171
  remove(): void;
94
172
  exclude(el: Element): void;
@@ -102,10 +180,11 @@ export interface ModelHandle {
102
180
 
103
181
  /**
104
182
  * Load a glTF/GLB into an inline-3D window. Safe to call with an unsupported wall — it renders a
105
- * flat, orbitable view instead, so pages need no branch.
183
+ * flat, orbitable view instead, so pages need no branch. Renders with PlayCanvas by default
184
+ * (1.12); pass `engine: 'three'` for the 1.11 renderer.
106
185
  *
107
186
  * Compressed assets (Draco, meshopt, KTX2/Basis) load too: the asset's declared extensions decide
108
- * which decoders are imported, and nothing is imported for an asset that declares none. Draco and
187
+ * which decoders are wired, and nothing is wired for an asset that declares none. Draco and
109
188
  * KTX2 additionally need their runtime files served by your page — see {@link ModelOptions.decoderPath}.
110
189
  * When a decoder is needed and unavailable, `ready` rejects with an Error naming the glTF
111
190
  * extension, the option that fixes it and the path it looked in; the extension is also on the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@displayxr/inline3d",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "description": "Turn any HTML <canvas> into a glasses-free-3D window on a DisplayXR display, inside an ordinary web page. Dependency-free; progressive enhancement (falls back to plain 2D everywhere else).",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",
@@ -24,6 +24,10 @@
24
24
  "import": "./js/inline3d-splat.js"
25
25
  },
26
26
  "./model": {
27
+ "types": "./model.d.ts",
28
+ "import": "./js/inline3d-model-entry.js"
29
+ },
30
+ "./model/three": {
27
31
  "types": "./model.d.ts",
28
32
  "import": "./js/inline3d-model.js"
29
33
  },
@@ -47,6 +51,9 @@
47
51
  "js/inline3d-splat-playcanvas.js",
48
52
  "js/inline3d-splat-shared.js",
49
53
  "js/inline3d-model.js",
54
+ "js/inline3d-model-entry.js",
55
+ "js/inline3d-model-playcanvas.js",
56
+ "js/inline3d-playcanvas-engine.js",
50
57
  "index.d.ts",
51
58
  "three.d.ts",
52
59
  "viewer.d.ts",
@@ -64,7 +71,8 @@
64
71
  "stereo",
65
72
  "3d-display",
66
73
  "lightfield",
67
- "three"
74
+ "three",
75
+ "playcanvas"
68
76
  ],
69
77
  "homepage": "https://displayxr.github.io/displayxr-web/",
70
78
  "repository": {
@@ -78,7 +86,8 @@
78
86
  "peerDependencies": {
79
87
  "three": ">=0.150.0",
80
88
  "@sparkjsdev/spark": ">=2.0.0",
81
- "playcanvas": ">=2.22.3 <3"
89
+ "playcanvas": ">=2.22.3 <3",
90
+ "meshoptimizer": ">=1.0.0"
82
91
  },
83
92
  "peerDependenciesMeta": {
84
93
  "three": {
@@ -89,6 +98,9 @@
89
98
  },
90
99
  "playcanvas": {
91
100
  "optional": true
101
+ },
102
+ "meshoptimizer": {
103
+ "optional": true
92
104
  }
93
105
  },
94
106
  "publishConfig": {
package/splat.d.ts CHANGED
@@ -466,7 +466,9 @@ export interface SplatHandle {
466
466
  * Swap the asset (URL or bytes) in place. PlayCanvas backend only — throws on Spark.
467
467
  *
468
468
  * The new file loads BEHIND the current one; then the two crossfade over `fadeMs` (0 = a cut)
469
- * and the old one is released. The rig waterfall re-runs for the new file (rig, lens, focus and
469
+ * and the old one is released. The crossfade blends the two IMAGES linearly: the last frame of
470
+ * the old asset is frozen (both eyes) and lerped with the live new one, so the mix is exactly
471
+ * `t` whatever the two photos' depth order (the old asset's own motion stops during the fade). The rig waterfall re-runs for the new file (rig, lens, focus and
470
472
  * frame update; `onFocusChange` fires). The pose (yaw/pitch/zoom/depth) is kept unless
471
473
  * `resetPose`. A newer call supersedes an older one still loading. Resolves once the fade has
472
474
  * finished; rejects if the new asset cannot be loaded (the current one stays on screen).