@displayxr/inline3d 1.7.1 → 1.9.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 +86 -0
- package/README.md +22 -0
- package/js/inline3d-splat-perf.js +231 -0
- package/js/inline3d-splat-playcanvas.js +2101 -0
- package/js/inline3d-splat-rig.js +268 -3
- package/js/inline3d-splat-shared.js +318 -0
- package/js/inline3d-splat.js +182 -118
- package/js/inline3d-three.js +43 -9
- package/js/inline3d-viewer.js +27 -41
- package/package.json +8 -2
- package/splat.d.ts +191 -7
- package/three.d.ts +17 -0
package/splat.d.ts
CHANGED
|
@@ -34,6 +34,52 @@ export interface SplatPerfOptions {
|
|
|
34
34
|
lodSplatCount?: number;
|
|
35
35
|
/** Minimum on-screen splat size multiplier (needs `lod`); up to ~5 is often invisible. */
|
|
36
36
|
lodRenderScale?: number;
|
|
37
|
+
/**
|
|
38
|
+
* `engine: 'playcanvas'` only — engine-native knobs, passed straight to `app.scene.gsplat`
|
|
39
|
+
* (and winning over the Spark-knob mapping).
|
|
40
|
+
*
|
|
41
|
+
* `splatBudget` is a splat count **per tile, all views included**: every view of a tile is
|
|
42
|
+
* drawn through one engine camera, so one budget covers both eyes of a 3D tile. It only acts
|
|
43
|
+
* on a Streamed SOG (a flat `.sog` draws every splat). Unset on a Streamed SOG = 600k
|
|
44
|
+
* (`STREAMED_SPLAT_BUDGET`); unset on anything else, or `perf: false` = the engine's 1M.
|
|
45
|
+
*/
|
|
46
|
+
splatBudget?: number;
|
|
47
|
+
/**
|
|
48
|
+
* `engine: 'playcanvas'`, Streamed SOG only: how a chunk's LOD is chosen. Unset = the engine's
|
|
49
|
+
* `'distance'`.
|
|
50
|
+
*/
|
|
51
|
+
lodMode?: 'distance' | 'error';
|
|
52
|
+
/** Streamed SOG only: camera travel (in the file's own units) before LOD re-evaluates. Engine default 1. */
|
|
53
|
+
lodUpdateDistance?: number;
|
|
54
|
+
/** Streamed SOG only: camera rotation in degrees before LOD re-evaluates. Engine default 0 (off). */
|
|
55
|
+
lodUpdateAngle?: number;
|
|
56
|
+
/** Streamed SOG only: how many coarser levels may stand in while a finer one streams. Engine default 0. */
|
|
57
|
+
lodUnderfillLimit?: number;
|
|
58
|
+
/** `engine: 'playcanvas'` only: cull splats whose quad DIAMETER is under this many px. */
|
|
59
|
+
minPixelSize?: number;
|
|
60
|
+
/** `engine: 'playcanvas'` only: the forward-pass alpha floor (engine default 1/255). */
|
|
61
|
+
alphaClipForward?: number;
|
|
62
|
+
/** `engine: 'playcanvas'` only: the engine's AA compensation, for AA-trained assets. */
|
|
63
|
+
antiAlias?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The PlayCanvas backend's viewer (`engine: 'playcanvas'`): the SceneViewer pose surface without
|
|
68
|
+
* three. Not field-compatible with SceneViewer — see docs/playcanvas-adapter.md.
|
|
69
|
+
*/
|
|
70
|
+
export interface PlayCanvasSplatViewer {
|
|
71
|
+
idleSpin: number;
|
|
72
|
+
readonly is3D: boolean;
|
|
73
|
+
depthOffset: number;
|
|
74
|
+
/** The engine's `AppBase`, once booted. */
|
|
75
|
+
readonly app: unknown;
|
|
76
|
+
fitTo(center: number[], extent: number[]): void;
|
|
77
|
+
setPose(pose?: OrbitPose): void;
|
|
78
|
+
getPose(opts?: { target?: boolean }): Required<OrbitPose>;
|
|
79
|
+
resetPose(): void;
|
|
80
|
+
getSubjectBounds(): SubjectBounds & { front: number; back: number; scale: number };
|
|
81
|
+
setFocus(point: number[] | { x: number; y: number; z: number } | null, opts?: { snap?: boolean; recentre?: boolean }): PlayCanvasSplatViewer;
|
|
82
|
+
getFocus(opts?: { target?: boolean }): { x: number; y: number; z: number };
|
|
37
83
|
}
|
|
38
84
|
|
|
39
85
|
/** Camera intrinsics for ONE eye, in pixels, OpenCV convention. */
|
|
@@ -92,14 +138,26 @@ export interface ResolvedRig {
|
|
|
92
138
|
focalEqMm: number;
|
|
93
139
|
/** The live focus, in the splat's own space. */
|
|
94
140
|
focus: number[];
|
|
141
|
+
/**
|
|
142
|
+
* Which step answered the focus. The order: `caller` › `caller-convergence` › `block` (a
|
|
143
|
+
* considered block focus) › `nearest-clump` (the nearest substantial disparity clump in the
|
|
144
|
+
* central half of the frame — needs the block's or the caller's lens) › `block-cloud-median` (a
|
|
145
|
+
* block focus a converter computed as a whole-cloud median) › `median-disparity` › `default`.
|
|
146
|
+
*/
|
|
95
147
|
focusSource:
|
|
96
148
|
| 'caller'
|
|
97
149
|
| 'caller-convergence'
|
|
98
150
|
| 'block'
|
|
151
|
+
| 'nearest-clump'
|
|
152
|
+
| 'block-cloud-median'
|
|
99
153
|
| 'median-disparity'
|
|
100
154
|
| 'default'
|
|
101
155
|
| 'picked'
|
|
102
156
|
| 'set';
|
|
157
|
+
/** The block's own `focus.source` string (e.g. `'convergence'`, `'cloud-median'`), for diagnostics. */
|
|
158
|
+
blockFocusSource: string | null;
|
|
159
|
+
/** Fraction of the central crop's opacity-weighted mass the winning clump carried (`nearest-clump` only). */
|
|
160
|
+
clumpMassFrac: number | null;
|
|
103
161
|
/** What Space returns to. */
|
|
104
162
|
focusDefault: number[];
|
|
105
163
|
focusDefaultSource: string;
|
|
@@ -112,6 +170,27 @@ export interface ResolvedRig {
|
|
|
112
170
|
}
|
|
113
171
|
|
|
114
172
|
export interface SplatOptions {
|
|
173
|
+
/**
|
|
174
|
+
* Which renderer. `'spark'` (the default) is three.js + Spark. `'playcanvas'` is the PlayCanvas
|
|
175
|
+
* engine (optional peer `playcanvas >=2.22.3 <3`, loaded by dynamic import only when asked):
|
|
176
|
+
* same handle, reads `.sog` / `.ply` / a Streamed-SOG `lod-meta.json`. Anything else throws.
|
|
177
|
+
*
|
|
178
|
+
* A Streamed SOG is loaded BY URL only — its `lod-meta.json`, or the directory holding it (a
|
|
179
|
+
* URL ending in `/`). It is a directory of chunk files named by relative path, so bytes of a
|
|
180
|
+
* `lod-meta.json` throw at call time with a message giving the URL form, and so does a streamed
|
|
181
|
+
* URL with `engine: 'spark'`.
|
|
182
|
+
*/
|
|
183
|
+
engine?: 'spark' | 'playcanvas';
|
|
184
|
+
/**
|
|
185
|
+
* `engine: 'playcanvas'` only: the WebGL context's `preserveDrawingBuffer` (default false) —
|
|
186
|
+
* the knob for the weave's zero-copy read race on large canvases.
|
|
187
|
+
*/
|
|
188
|
+
preserveDrawingBuffer?: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* `engine: 'playcanvas'` only: the `playcanvas` module namespace to use instead of
|
|
191
|
+
* `import('playcanvas')` — for a page that already bundles its own copy.
|
|
192
|
+
*/
|
|
193
|
+
playcanvas?: unknown;
|
|
115
194
|
/** Metres of world the tile's height spans (default 0.24). */
|
|
116
195
|
virtualDisplayHeight?: number;
|
|
117
196
|
/**
|
|
@@ -124,7 +203,17 @@ export interface SplatOptions {
|
|
|
124
203
|
flipY?: boolean;
|
|
125
204
|
/** Degrees/second of turntable once idle (default 8). */
|
|
126
205
|
idleSpin?: number;
|
|
206
|
+
/**
|
|
207
|
+
* Drag + wheel. On the PlayCanvas backend the drag is TILT-AND-RELAX: measured as a fraction of
|
|
208
|
+
* the canvas box from the press, it tilts up to ±`orbitMaxDeg` (a half-width swipe reaches it)
|
|
209
|
+
* and relaxes back to rest on release; `idleSpin` resumes once at rest. On Spark (SceneViewer)
|
|
210
|
+
* it is still the cumulative turntable (a full-width drag = 180°).
|
|
211
|
+
*/
|
|
127
212
|
orbit?: boolean;
|
|
213
|
+
/** PlayCanvas: the largest drag tilt, degrees, either axis (default 15). */
|
|
214
|
+
orbitMaxDeg?: number;
|
|
215
|
+
/** PlayCanvas: easing time constants, seconds — `drag` while held (0.2), `rest` after release (0.6). */
|
|
216
|
+
orbitEase?: { drag?: number; rest?: number };
|
|
128
217
|
fit?: 'contain' | 'height' | 'cover' | 'none';
|
|
129
218
|
/** Fraction of the tile the subject may occupy (default 0.8) — width AND height. */
|
|
130
219
|
margin?: number;
|
|
@@ -138,7 +227,11 @@ export interface SplatOptions {
|
|
|
138
227
|
/** Per-eye buffer scale; 0.5–0.7 is usually free (default 1). */
|
|
139
228
|
renderScale?: number;
|
|
140
229
|
feather?: number;
|
|
141
|
-
/**
|
|
230
|
+
/**
|
|
231
|
+
* Spark: minimum ms between splat sorts (default 16, so both eyes share one sort per frame).
|
|
232
|
+
* PlayCanvas: accepted and has no effect — the engine re-sorts when the camera ROTATES, with one
|
|
233
|
+
* directional sort serving every view.
|
|
234
|
+
*/
|
|
142
235
|
sortIntervalMs?: number;
|
|
143
236
|
/**
|
|
144
237
|
* Cut overdraw. UNSET changes nothing — every Spark default stays where Spark put it, so an
|
|
@@ -150,7 +243,7 @@ export interface SplatOptions {
|
|
|
150
243
|
* `true`, −5…−20 % measured) and `'aggressive'` (−22 %) tighten the quad extent instead, which
|
|
151
244
|
* is the axis that actually pays on the web; both move pixels.
|
|
152
245
|
*/
|
|
153
|
-
perf?:
|
|
246
|
+
perf?: boolean | 'exact' | 'balanced' | 'aggressive' | SplatPerfOptions;
|
|
154
247
|
/**
|
|
155
248
|
* Which view rig. `'auto'` (the default) reads it off the ASSET — a `.sog` carrying a `camera`
|
|
156
249
|
* block was lifted from a photograph and gets a camera rig that conserves the recording
|
|
@@ -158,6 +251,14 @@ export interface SplatOptions {
|
|
|
158
251
|
* detectable when `src` is BYTES.
|
|
159
252
|
*/
|
|
160
253
|
rig?: 'auto' | 'display' | 'camera';
|
|
254
|
+
/**
|
|
255
|
+
* Camera rig only: what gives when the canvas is not the capture's shape. `'height'` (default,
|
|
256
|
+
* the 1.7 behaviour) keeps the capture's vertical extent and widens or narrows the horizontal to
|
|
257
|
+
* the canvas. `'cover'` always fills the tile with photograph: a canvas WIDER than the capture
|
|
258
|
+
* keeps the width and crops top/bottom (a 4:3 capture in a 16:9 tile); a narrower one is
|
|
259
|
+
* `'height'`. Both backends; the 3D rig's vertical FOV follows the crop. Anything else throws.
|
|
260
|
+
*/
|
|
261
|
+
captureFit?: 'height' | 'cover';
|
|
161
262
|
/** Camera rig only: the distance in world metres that sits ON the glass. */
|
|
162
263
|
convergence?: number;
|
|
163
264
|
/**
|
|
@@ -189,13 +290,68 @@ export interface SplatOptions {
|
|
|
189
290
|
observe?: Element;
|
|
190
291
|
}
|
|
191
292
|
|
|
293
|
+
/** `handle.stats()` on `engine: 'playcanvas'`. */
|
|
294
|
+
export interface SplatStats {
|
|
295
|
+
/** `'streamed'` for a `lod-meta.json`, `'flat'` for `.sog`/`.ply`, null before load. */
|
|
296
|
+
kind: 'flat' | 'streamed' | null;
|
|
297
|
+
/**
|
|
298
|
+
* Splats the engine placed in this tile's work buffer on the LAST frame: after LOD selection
|
|
299
|
+
* and the budget, before per-view frustum culling. Every view of the tile draws from this set.
|
|
300
|
+
*/
|
|
301
|
+
resident: number;
|
|
302
|
+
/** The largest `resident` seen so far. */
|
|
303
|
+
peakResident: number;
|
|
304
|
+
/** The tile's splat budget (all views included), or null. */
|
|
305
|
+
budget: number | null;
|
|
306
|
+
/** The asset's own count: every splat of a flat source, the finest level of a Streamed SOG. */
|
|
307
|
+
numSplats: number;
|
|
308
|
+
/** Views drawn last frame (1 in mono, the runtime's view count in 3D). */
|
|
309
|
+
views: number;
|
|
310
|
+
/** Streamed SOG only (null otherwise): LOD levels, chunk files, chunk files currently loaded. */
|
|
311
|
+
lodLevels: number | null;
|
|
312
|
+
files: number | null;
|
|
313
|
+
filesLoaded: number | null;
|
|
314
|
+
/**
|
|
315
|
+
* `performance.now()` (ms since navigation start) of the first frame that drew a non-empty
|
|
316
|
+
* set — the page's time to first splat. Null until then.
|
|
317
|
+
*/
|
|
318
|
+
firstFrameMs: number | null;
|
|
319
|
+
}
|
|
320
|
+
|
|
192
321
|
/** What {@link addSplat} returns: a TileHandle plus the objects behind it. */
|
|
193
322
|
export interface SplatHandle {
|
|
194
|
-
|
|
195
|
-
|
|
323
|
+
/**
|
|
324
|
+
* SceneViewer on Spark; the PlayCanvas backend's own viewer on `engine: 'playcanvas'` (null
|
|
325
|
+
* there until the backend module has loaded — one module fetch after addSplat returns).
|
|
326
|
+
*/
|
|
327
|
+
readonly viewer: SceneViewer | PlayCanvasSplatViewer;
|
|
328
|
+
/**
|
|
329
|
+
* Spark's SplatMesh; on `engine: 'playcanvas'` a `{ numSplats, entity, asset, resource }`
|
|
330
|
+
* record of the engine objects. Null until the asset is loaded.
|
|
331
|
+
*/
|
|
196
332
|
readonly mesh: object;
|
|
197
|
-
/** Spark's SparkRenderer. */
|
|
198
|
-
readonly spark
|
|
333
|
+
/** Spark's SparkRenderer (absent on `engine: 'playcanvas'`). */
|
|
334
|
+
readonly spark?: object;
|
|
335
|
+
/** Which backend is rendering: `'playcanvas'` or `'spark'`; null until it has loaded. */
|
|
336
|
+
readonly backend: 'playcanvas' | 'spark' | null;
|
|
337
|
+
/**
|
|
338
|
+
* ADVANCED — not covered by the semver promise. The renderer objects behind this window, for a
|
|
339
|
+
* page that wants to add its own content. Null until the backend has booted.
|
|
340
|
+
*
|
|
341
|
+
* PlayCanvas: `{ app, root, camera }` — the tile's `pc.AppBase`; the content root entity (the
|
|
342
|
+
* splat's content space — add your own entities under it, e.g. a glTF through the engine's
|
|
343
|
+
* container loader, skinned and animated included); the eye-rig camera entity. `remove()`
|
|
344
|
+
* destroys the app, and everything under `root` with it.
|
|
345
|
+
*
|
|
346
|
+
* Spark: `{ renderer, scene, camera }` — the WebGLRenderer, the scene, and whichever camera draws
|
|
347
|
+
* the current frame.
|
|
348
|
+
*/
|
|
349
|
+
readonly engine:
|
|
350
|
+
| { readonly app: unknown; readonly root: unknown; readonly camera: unknown }
|
|
351
|
+
| { readonly renderer: unknown; readonly scene: unknown; readonly camera: unknown }
|
|
352
|
+
| null;
|
|
353
|
+
/** `engine: 'playcanvas'` only: the live focus, in the splat's own space. */
|
|
354
|
+
getFocus?(opts?: { target?: boolean }): number[] | null;
|
|
199
355
|
/** Bounds actually used for framing; null until `ready` resolves. */
|
|
200
356
|
frame: SubjectBounds | null;
|
|
201
357
|
/**
|
|
@@ -222,8 +378,36 @@ export interface SplatHandle {
|
|
|
222
378
|
point: number[] | { x: number; y: number; z: number } | null,
|
|
223
379
|
opts?: { snap?: boolean },
|
|
224
380
|
): SplatHandle;
|
|
225
|
-
/**
|
|
381
|
+
/**
|
|
382
|
+
* Swap the asset (URL or bytes) in place. PlayCanvas backend only — throws on Spark.
|
|
383
|
+
*
|
|
384
|
+
* The new file loads BEHIND the current one; then the two crossfade over `fadeMs` (0 = a cut)
|
|
385
|
+
* and the old one is released. The rig waterfall re-runs for the new file (rig, lens, focus and
|
|
386
|
+
* frame update; `onFocusChange` fires). The pose (yaw/pitch/zoom/depth) is kept unless
|
|
387
|
+
* `resetPose`. A newer call supersedes an older one still loading. Resolves once the fade has
|
|
388
|
+
* finished; rejects if the new asset cannot be loaded (the current one stays on screen).
|
|
389
|
+
*/
|
|
390
|
+
setSource(
|
|
391
|
+
src: string | Blob | ArrayBuffer | Uint8Array,
|
|
392
|
+
opts?: { fadeMs?: number; resetPose?: boolean },
|
|
393
|
+
): Promise<SplatHandle>;
|
|
394
|
+
/**
|
|
395
|
+
* Called with the live focus (the splat's own space) whenever it moves — easing included — and
|
|
396
|
+
* which waterfall step it came from. PlayCanvas backend; assign any time, even before `ready`.
|
|
397
|
+
*/
|
|
398
|
+
onFocusChange: ((point: number[], info: { focusSource: ResolvedRig['focusSource'] | null }) => void) | null;
|
|
399
|
+
/**
|
|
400
|
+
* What is under a point on the canvas, in the splat's own space — the double-click's pick.
|
|
401
|
+
* PlayCanvas: the nearest gaussian CENTRE to the ray over the FULL centre set (haze under 5 %
|
|
402
|
+
* opacity skipped); on a Streamed SOG, over the chunks currently resident. Spark: its surface
|
|
403
|
+
* raycast, falling back to the nearest centre.
|
|
404
|
+
*/
|
|
226
405
|
pick(clientX: number, clientY: number): number[] | null;
|
|
406
|
+
/**
|
|
407
|
+
* `engine: 'playcanvas'` only: splat accounting for this tile. Null until the backend module
|
|
408
|
+
* has loaded.
|
|
409
|
+
*/
|
|
410
|
+
stats?(): SplatStats | null;
|
|
227
411
|
|
|
228
412
|
/** Close this window and release its GPU resources. */
|
|
229
413
|
remove(): void;
|
package/three.d.ts
CHANGED
|
@@ -100,6 +100,23 @@ export function cameraRigFromCamera(
|
|
|
100
100
|
opts?: CameraRigOptions,
|
|
101
101
|
): XRViewRigInit;
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* The same CAMERA-rig descriptor as {@link cameraRigFromCamera}, from a plain pose instead of a
|
|
105
|
+
* three.js camera — for a renderer that is not three. Agrees with cameraRigFromCamera to the bit
|
|
106
|
+
* for the same pose.
|
|
107
|
+
*/
|
|
108
|
+
export function cameraRigFromPose(
|
|
109
|
+
pose: {
|
|
110
|
+
/** WORLD position. */
|
|
111
|
+
position: { x: number; y: number; z: number };
|
|
112
|
+
/** WORLD orientation quaternion. */
|
|
113
|
+
orientation: { x: number; y: number; z: number; w: number };
|
|
114
|
+
/** FULL vertical angle in DEGREES (three's `camera.fov` convention). */
|
|
115
|
+
fov: number;
|
|
116
|
+
},
|
|
117
|
+
opts?: CameraRigOptions,
|
|
118
|
+
): XRViewRigInit;
|
|
119
|
+
|
|
103
120
|
/**
|
|
104
121
|
* Build a DISPLAY-rig descriptor — the default rig, made explicit and posable: the canvas is a
|
|
105
122
|
* portal onto a virtual display `virtualDisplayHeight` metres tall. Adds what the scalar
|