@displayxr/inline3d 1.29.0 → 1.31.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 +187 -0
- package/README.md +9 -5
- package/call.d.ts +318 -238
- package/camera.d.ts +257 -0
- package/dist/call.js +17 -12
- package/index.d.ts +56 -0
- package/js/call/capture.js +4 -212
- package/js/call/disparity.js +4 -432
- package/js/call/element.js +14 -10
- package/js/call/options.js +132 -0
- package/js/call/signaling.js +66 -9
- package/js/call/ui.js +106 -9
- package/js/call/wire.js +8 -153
- package/js/camera/capture.js +220 -0
- package/js/camera/converge.js +82 -0
- package/js/camera/disparity.js +432 -0
- package/js/camera/geometry.js +80 -0
- package/js/camera/metadata.js +398 -0
- package/js/inline3d-call.js +435 -320
- package/js/inline3d-camera.js +678 -0
- package/js/inline3d-model-playcanvas.js +1 -0
- package/js/inline3d-model.js +1 -0
- package/js/inline3d-splat-deferred.js +153 -0
- package/js/inline3d-splat-pc-entry.js +45 -0
- package/js/inline3d-splat-playcanvas.js +14 -3
- package/js/inline3d-splat.js +3 -145
- package/js/inline3d-viewer-ease.js +360 -0
- package/js/inline3d-viewer.js +15 -4
- package/js/inline3d.js +13 -0
- package/model.d.ts +8 -1
- package/package.json +22 -2
- package/splat-playcanvas.d.ts +4 -0
- package/splat.d.ts +8 -1
- package/viewer.d.ts +9 -0
package/js/inline3d-model.js
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// inline3d-splat-deferred.js — the engine:'playcanvas' handle, shared by both splat entries.
|
|
2
|
+
//
|
|
3
|
+
// Internal. ./splat (Spark by default) and ./splat/playcanvas (PlayCanvas only) both build their
|
|
4
|
+
// PlayCanvas handle here. This module must never import `three` or `@sparkjsdev/spark`, directly
|
|
5
|
+
// or through anything it imports: that is what lets ./splat/playcanvas keep both off the page
|
|
6
|
+
// (test/splat-pc-entry.test.mjs walks the import graph to hold it to that).
|
|
7
|
+
|
|
8
|
+
import { normalizeCameraPose } from './inline3d-splat-shared.js';
|
|
9
|
+
import { validateEffectCall } from './inline3d-splat-effects.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The `engine: 'playcanvas'` handle, returned SYNCHRONOUSLY like the Spark one.
|
|
13
|
+
*
|
|
14
|
+
* The adapter module is loaded on demand, so for a moment the handle exists before its
|
|
15
|
+
* implementation does. Rather than make every caller await something new, the handle starts as
|
|
16
|
+
* stubs that QUEUE: `exclude()` (which a product page calls on the very next line), `setPose`,
|
|
17
|
+
* `setFocus`, `remove` — and the adapter replays the queue into the real implementation, on the
|
|
18
|
+
* SAME object, the moment it arrives. `ready` resolves to this object, as it does on Spark.
|
|
19
|
+
* Fields (`viewer`, `mesh`, `rig`, …) are null until then; `viewer` in particular appears one
|
|
20
|
+
* module-load later than on Spark.
|
|
21
|
+
*/
|
|
22
|
+
/** setRig's type/options shape, checked at the call before the adapter has loaded (it re-checks all of it). */
|
|
23
|
+
function validateSetRigArgs(type, o) {
|
|
24
|
+
if (!['display', 'camera', 'auto'].includes(type)) {
|
|
25
|
+
throw new Error(`@displayxr/inline3d/splat: setRig("${type}") — expected 'display', 'camera' or 'auto'.`);
|
|
26
|
+
}
|
|
27
|
+
if (o !== undefined && (o === null || typeof o !== 'object')) {
|
|
28
|
+
throw new TypeError('@displayxr/inline3d/splat: setRig options must be an object.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function addSplatDeferred(wall, canvas, src, opts) {
|
|
33
|
+
const pending = [];
|
|
34
|
+
const queue = (name) => (...args) => {
|
|
35
|
+
pending.push([name, args]);
|
|
36
|
+
return name === 'setFocus' ? out : undefined;
|
|
37
|
+
};
|
|
38
|
+
const page = opts.controls === 'page';
|
|
39
|
+
const pageOnly = (name) => () => {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`@displayxr/inline3d/splat: ${name}() is not available with controls:'page' — the page owns the ` +
|
|
42
|
+
'camera. Drive it with handle.setCameraPose(matrixWorld, { verticalFovDeg, near, far }).',
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
// Before the adapter lands, a page calling setCameraPose every frame keeps ONE pending pose
|
|
46
|
+
// (validated now, so a bad call throws at its own line): last call wins, as it will after.
|
|
47
|
+
let pendingPose = null;
|
|
48
|
+
const out = {
|
|
49
|
+
backend: 'playcanvas',
|
|
50
|
+
engine: null,
|
|
51
|
+
viewer: null,
|
|
52
|
+
mesh: null,
|
|
53
|
+
frame: null,
|
|
54
|
+
camera: null,
|
|
55
|
+
rig: null,
|
|
56
|
+
perf: null,
|
|
57
|
+
setPose: page ? pageOnly('setPose') : queue('setPose'),
|
|
58
|
+
resetPose: page ? pageOnly('resetPose') : queue('resetPose'),
|
|
59
|
+
setCameraPose(matrixWorld, o) {
|
|
60
|
+
if (!page) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
"@displayxr/inline3d/splat: setCameraPose() needs addSplat(…, { controls:'page' }) — " +
|
|
63
|
+
'with the default controls the SDK owns the camera (use setPose).',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const pose = normalizeCameraPose(matrixWorld, o);
|
|
67
|
+
if (pendingPose) pendingPose[1] = [pose.matrixWorld, pose];
|
|
68
|
+
else pending.push((pendingPose = ['setCameraPose', [pose.matrixWorld, pose]]));
|
|
69
|
+
return out;
|
|
70
|
+
},
|
|
71
|
+
getCameraPose: () =>
|
|
72
|
+
pendingPose
|
|
73
|
+
? {
|
|
74
|
+
matrixWorld: Float32Array.from(pendingPose[1][1].matrixWorld),
|
|
75
|
+
verticalFovDeg: pendingPose[1][1].verticalFovDeg,
|
|
76
|
+
near: pendingPose[1][1].near,
|
|
77
|
+
far: pendingPose[1][1].far,
|
|
78
|
+
convergence: pendingPose[1][1].convergence,
|
|
79
|
+
}
|
|
80
|
+
: null,
|
|
81
|
+
setFocus: queue('setFocus'),
|
|
82
|
+
// A swap requested before the first asset has landed runs once it has (the adapter's own
|
|
83
|
+
// setSource replaces this stub on the same object by then).
|
|
84
|
+
setSource: (...args) => out.ready.then(() => out.setSource(...args)),
|
|
85
|
+
prepareSource: (...args) => out.ready.then(() => out.prepareSource(...args)),
|
|
86
|
+
// A rig switch before the first asset has landed: validated now (controls:'page' and a bad
|
|
87
|
+
// type throw at the call's own line), applied once it has.
|
|
88
|
+
setRig: page
|
|
89
|
+
? pageOnly('setRig')
|
|
90
|
+
: (type, o) => {
|
|
91
|
+
validateSetRigArgs(type, o);
|
|
92
|
+
return out.ready.then(() => out.setRig(type, o));
|
|
93
|
+
},
|
|
94
|
+
// A video before the first asset has landed: controls:'page' throws at the call's own line,
|
|
95
|
+
// the rest is validated by the adapter's setVideo, which runs once the asset is on screen.
|
|
96
|
+
setVideo: page ? pageOnly('setVideo') : (src, o) => out.ready.then(() => out.setVideo(src, o)),
|
|
97
|
+
// Effects before the adapter has loaded: validated NOW (a bad call throws at its own line),
|
|
98
|
+
// then run once the first asset is on screen.
|
|
99
|
+
playEffect: (name, o) => {
|
|
100
|
+
validateEffectCall(name, o, 'play');
|
|
101
|
+
return out.ready.then(() => out.playEffect(name, o));
|
|
102
|
+
},
|
|
103
|
+
setEffect: (name, params) => {
|
|
104
|
+
validateEffectCall(name, params, 'set');
|
|
105
|
+
pending.push(['setEffect', [name, params]]);
|
|
106
|
+
return out;
|
|
107
|
+
},
|
|
108
|
+
stopEffect: queue('stopEffect'),
|
|
109
|
+
// A layer-rig request before the adapter has loaded: queued, replayed in order once it has
|
|
110
|
+
// (it needs no asset, only the engine's layers — which the adapter resolves lazily).
|
|
111
|
+
setLayerRig: (...args) => {
|
|
112
|
+
pending.push(['setLayerRig', args]);
|
|
113
|
+
return out;
|
|
114
|
+
},
|
|
115
|
+
setLayerRigOptions: (...args) => {
|
|
116
|
+
pending.push(['setLayerRigOptions', args]);
|
|
117
|
+
return out;
|
|
118
|
+
},
|
|
119
|
+
layerRigState: () => ({ display: [], disabled: false, path: null, engaged: false, rounded: false, reason: 'the PlayCanvas adapter has not loaded yet', viewerDistance: 0.6, gain: null, planeM: null, photoConvergenceM: null, planeOffset: 0, located: null }),
|
|
120
|
+
makeSbsMaterial: () => {
|
|
121
|
+
throw new Error('@displayxr/inline3d/splat: makeSbsMaterial() needs the engine — call it after `await handle.ready`.');
|
|
122
|
+
},
|
|
123
|
+
effects: () => [],
|
|
124
|
+
getFocus: () => null,
|
|
125
|
+
// A plain data slot the adapter reads at call time, so a callback assigned on the very next
|
|
126
|
+
// line after addSplat — before the module has loaded — is the one that fires.
|
|
127
|
+
onFocusChange: null,
|
|
128
|
+
pick: () => null,
|
|
129
|
+
// Splat accounting (resident / budget / first frame); null until the adapter has loaded.
|
|
130
|
+
stats: () => null,
|
|
131
|
+
remove: queue('remove'),
|
|
132
|
+
exclude: queue('exclude'),
|
|
133
|
+
unexclude: queue('unexclude'),
|
|
134
|
+
};
|
|
135
|
+
// `firstWoven` exists from the first line, like every other field a page reads right away; the
|
|
136
|
+
// adapter settles it with the core handle's own once the module has loaded.
|
|
137
|
+
out.firstWoven = new Promise((resolve) => {
|
|
138
|
+
out._resolveFirstWoven = resolve;
|
|
139
|
+
});
|
|
140
|
+
// The ONE owner of `ready`: the adapter returns its load promise and never touches this field.
|
|
141
|
+
out.ready = import('./inline3d-splat-playcanvas.js')
|
|
142
|
+
.then((m) => m.attachPlayCanvasSplat(out, wall, canvas, src, opts, pending))
|
|
143
|
+
.catch((err) => {
|
|
144
|
+
// The adapter warns about its own load failures; this is for the module not arriving.
|
|
145
|
+
if (!out.viewer) console.warn('[inline3d/splat] engine:playcanvas failed to start', err);
|
|
146
|
+
if (out._resolveFirstWoven) {
|
|
147
|
+
out._resolveFirstWoven(Object.freeze({ woven: false, confirmed: false, reason: 'layer-failed', ms: 0 }));
|
|
148
|
+
delete out._resolveFirstWoven;
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
});
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// inline3d-splat-pc-entry.js — `@displayxr/inline3d/splat/playcanvas`: addSplat on PlayCanvas only.
|
|
2
|
+
//
|
|
3
|
+
// EXPERIMENTAL, like ./splat. Same addSplat, same handle, same options, with the engine fixed to
|
|
4
|
+
// 'playcanvas'. The difference is what the page downloads: ./splat statically imports `three`
|
|
5
|
+
// and `@sparkjsdev/spark` because Spark is its default engine and its Spark path is synchronous,
|
|
6
|
+
// so a page passing engine:'playcanvas' there still pays for both (~1.7 MB gzipped). This entry
|
|
7
|
+
// imports neither, directly or transitively. `playcanvas` itself is still loaded on demand, when
|
|
8
|
+
// the first splat is added.
|
|
9
|
+
//
|
|
10
|
+
// import { createInline3D } from '@displayxr/inline3d';
|
|
11
|
+
// import { addSplat } from '@displayxr/inline3d/splat/playcanvas';
|
|
12
|
+
//
|
|
13
|
+
// const wall = await createInline3D();
|
|
14
|
+
// const scene = addSplat(wall, canvas, 'lake.sog', { reveal: 'sweep' });
|
|
15
|
+
//
|
|
16
|
+
// Not here: measureSplatBounds (it measures a Spark mesh — import it from ./splat).
|
|
17
|
+
|
|
18
|
+
import { resolveRevealOption } from './inline3d-splat-effects.js';
|
|
19
|
+
import { CAPTURE_FITS, playcanvasCannotRead, resolveControls } from './inline3d-splat-shared.js';
|
|
20
|
+
import { addSplatDeferred } from './inline3d-splat-deferred.js';
|
|
21
|
+
|
|
22
|
+
export { applySplatPerf, SPLAT_PERF_PRESETS } from './inline3d-splat-perf.js';
|
|
23
|
+
export { readSogCamera, readSogMeta } from './inline3d-sog.js';
|
|
24
|
+
export { resolveRig } from './inline3d-splat-rig.js';
|
|
25
|
+
|
|
26
|
+
/** addSplat with `engine: 'playcanvas'`; the same validation, at the call, as ./splat's. */
|
|
27
|
+
export function addSplat(wall, canvas, src, opts = {}) {
|
|
28
|
+
if (opts.engine !== undefined && opts.engine !== 'playcanvas') {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`@displayxr/inline3d/splat/playcanvas: engine "${opts.engine}" — this entry is PlayCanvas ` +
|
|
31
|
+
"only. Import addSplat from '@displayxr/inline3d/splat' for Spark.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (opts.captureFit !== undefined && !CAPTURE_FITS.includes(opts.captureFit)) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`@displayxr/inline3d/splat: captureFit "${opts.captureFit}" — expected ` +
|
|
37
|
+
`${CAPTURE_FITS.map((f) => `'${f}'`).join(' or ')}.`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
resolveControls(opts);
|
|
41
|
+
resolveRevealOption(opts.reveal);
|
|
42
|
+
const why = playcanvasCannotRead(src, opts);
|
|
43
|
+
if (why) throw new Error(`@displayxr/inline3d/splat: ${why}`);
|
|
44
|
+
return addSplatDeferred(wall, canvas, src, { ...opts, engine: 'playcanvas' });
|
|
45
|
+
}
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
sequenceSpans,
|
|
68
68
|
} from './inline3d-splat-effects.js';
|
|
69
69
|
import { LiveOutgoing, resolveOutgoingOption, defaultOutgoing, yieldIdle } from './inline3d-splat-live.js';
|
|
70
|
+
import { viewerEaseFor, frameTrackingState } from './inline3d-viewer-ease.js';
|
|
70
71
|
import { VideoPlane, validateSetVideo, PAGE_VIDEO_ERROR, eyeSplit, EYE_SPLIT_UNIFORM, makeSbsMaterial } from './inline3d-splat-video.js';
|
|
71
72
|
import { LayerRigCameras, validateLayerRig, validateLayerRigOptions, DEFAULT_VIEWER_DISTANCE_M } from './inline3d-splat-layer-rig.js';
|
|
72
73
|
import { RigTracker, remapViews, nodePose, sameRig } from './inline3d-splat-rig-map.js';
|
|
@@ -817,8 +818,13 @@ export class PlayCanvasSplatViewer {
|
|
|
817
818
|
sky = false,
|
|
818
819
|
pageCamera = false,
|
|
819
820
|
toneMapping = 'none',
|
|
821
|
+
viewerEase,
|
|
820
822
|
} = opts;
|
|
821
823
|
this.canvas = canvas;
|
|
824
|
+
// The tracking-acquisition ease (./inline3d-viewer-ease.js): undefined = the session's
|
|
825
|
+
// createInline3D({ viewerEase }) default. Built on the first 3D frame, which knows the session.
|
|
826
|
+
this._viewerEaseOpt = viewerEase;
|
|
827
|
+
this.viewerEase = null;
|
|
822
828
|
// controls:'page': the PAGE owns the camera (setPageCamera). No orbit, no idle, no fit; the
|
|
823
829
|
// rig node carries the page's matrix instead of the inverse pivot.
|
|
824
830
|
this.pageCamera = pageCamera === true;
|
|
@@ -1194,7 +1200,7 @@ export class PlayCanvasSplatViewer {
|
|
|
1194
1200
|
}
|
|
1195
1201
|
|
|
1196
1202
|
/** wall.addScene's frame callback. Validate BEFORE drawing; replay the last good frame else. */
|
|
1197
|
-
onFrame(views, layer) {
|
|
1203
|
+
onFrame(views, layer, frame) {
|
|
1198
1204
|
if (this._disposed) return;
|
|
1199
1205
|
if (this._mode !== '3d') this.stopMono();
|
|
1200
1206
|
// The rig these views were located with: Blink chained the rig declared BEFORE this callback,
|
|
@@ -1224,6 +1230,9 @@ export class PlayCanvasSplatViewer {
|
|
|
1224
1230
|
vps.push(vp);
|
|
1225
1231
|
}
|
|
1226
1232
|
this._cacheGood(views, vps);
|
|
1233
|
+
// Ease a tracking acquisition/loss on the copies (never the XRViews), before anything —
|
|
1234
|
+
// the rig map, the layer rig, the live outgoing photo — reads them.
|
|
1235
|
+
(this.viewerEase ||= viewerEaseFor(frame, this._viewerEaseOpt)).apply(this._lastGood.entries, frameTrackingState(frame));
|
|
1227
1236
|
this._drawEntries(this._lastGood.entries, this._lastGood);
|
|
1228
1237
|
}
|
|
1229
1238
|
|
|
@@ -2139,6 +2148,7 @@ export class PlayCanvasSplatViewer {
|
|
|
2139
2148
|
if (this._disposed) return;
|
|
2140
2149
|
const box = this.canvas.getBoundingClientRect();
|
|
2141
2150
|
if (box.width < 1 || box.height < 1) return;
|
|
2151
|
+
this.viewerEase?.reset(); // a new window size moves every projection: not a viewer jump
|
|
2142
2152
|
const dpr = Math.min((typeof window !== 'undefined' && window.devicePixelRatio) || 1, 2) * this.renderScale;
|
|
2143
2153
|
// Clamp to the device's GL limits BEFORE sizing (./inline3d-buffer-limit.js). Measured on an
|
|
2144
2154
|
// Adreno 740 tablet: MAX_TEXTURE_SIZE 4096, a 5120-wide request silently became a 4096-wide
|
|
@@ -3072,6 +3082,7 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
|
|
|
3072
3082
|
nearClip: opts.nearClip,
|
|
3073
3083
|
farClip: opts.farClip,
|
|
3074
3084
|
sky: opts.sky,
|
|
3085
|
+
viewerEase: opts.viewerEase,
|
|
3075
3086
|
});
|
|
3076
3087
|
|
|
3077
3088
|
let handle = null;
|
|
@@ -3394,9 +3405,9 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
|
|
|
3394
3405
|
let initialViewRig = null; // the rig addScene is created with (controls:'page'), else the shorthand
|
|
3395
3406
|
if (wall && wall.supported) {
|
|
3396
3407
|
const onFrame = diag
|
|
3397
|
-
? (views, layer) => {
|
|
3408
|
+
? (views, layer, frame) => {
|
|
3398
3409
|
const t = performance.now();
|
|
3399
|
-
viewer.onFrame(views, layer);
|
|
3410
|
+
viewer.onFrame(views, layer, frame);
|
|
3400
3411
|
diag.frame(views, t); // after the draw: the overlay state is what this frame showed
|
|
3401
3412
|
}
|
|
3402
3413
|
: viewer.onFrame;
|
package/js/inline3d-splat.js
CHANGED
|
@@ -23,8 +23,9 @@ import { SparkRenderer, SplatMesh } from '@sparkjsdev/spark';
|
|
|
23
23
|
import { EyeCamera, EdgeFeather, cameraRigFromCamera } from './inline3d-three.js';
|
|
24
24
|
import { SceneViewer, boundsFromPositions } from './inline3d-viewer.js';
|
|
25
25
|
import { readSogCamera } from './inline3d-sog.js';
|
|
26
|
-
import { resolveRevealOption, effectsNotOnSpark
|
|
26
|
+
import { resolveRevealOption, effectsNotOnSpark } from './inline3d-splat-effects.js';
|
|
27
27
|
import { applySplatPerf, splatPerfMeshOptions } from './inline3d-splat-perf.js';
|
|
28
|
+
import { addSplatDeferred } from './inline3d-splat-deferred.js';
|
|
28
29
|
import {
|
|
29
30
|
toArray3,
|
|
30
31
|
canvasNdc,
|
|
@@ -36,7 +37,6 @@ import {
|
|
|
36
37
|
isStreamedUrl,
|
|
37
38
|
STREAMED_NEEDS_PLAYCANVAS,
|
|
38
39
|
resolveControls,
|
|
39
|
-
normalizeCameraPose,
|
|
40
40
|
declareViewRig,
|
|
41
41
|
} from './inline3d-splat-shared.js';
|
|
42
42
|
import {
|
|
@@ -246,6 +246,7 @@ export function addSplat(wall, canvas, src, opts = {}) {
|
|
|
246
246
|
renderScale,
|
|
247
247
|
feather,
|
|
248
248
|
logTag: '[inline3d/splat]',
|
|
249
|
+
viewerEase: opts.viewerEase,
|
|
249
250
|
}).useEyeCamera(EyeCamera, EdgeFeather);
|
|
250
251
|
|
|
251
252
|
// Spark renders through the ordinary three.js pipeline, so splats and meshes co-exist and
|
|
@@ -677,149 +678,6 @@ export function addSplat(wall, canvas, src, opts = {}) {
|
|
|
677
678
|
return out;
|
|
678
679
|
}
|
|
679
680
|
|
|
680
|
-
/**
|
|
681
|
-
* The `engine: 'playcanvas'` handle, returned SYNCHRONOUSLY like the Spark one.
|
|
682
|
-
*
|
|
683
|
-
* The adapter module is loaded on demand, so for a moment the handle exists before its
|
|
684
|
-
* implementation does. Rather than make every caller await something new, the handle starts as
|
|
685
|
-
* stubs that QUEUE: `exclude()` (which a product page calls on the very next line), `setPose`,
|
|
686
|
-
* `setFocus`, `remove` — and the adapter replays the queue into the real implementation, on the
|
|
687
|
-
* SAME object, the moment it arrives. `ready` resolves to this object, as it does on Spark.
|
|
688
|
-
* Fields (`viewer`, `mesh`, `rig`, …) are null until then; `viewer` in particular appears one
|
|
689
|
-
* module-load later than on Spark.
|
|
690
|
-
*/
|
|
691
|
-
/** setRig's type/options shape, checked at the call before the adapter has loaded (it re-checks all of it). */
|
|
692
|
-
function validateSetRigArgs(type, o) {
|
|
693
|
-
if (!['display', 'camera', 'auto'].includes(type)) {
|
|
694
|
-
throw new Error(`@displayxr/inline3d/splat: setRig("${type}") — expected 'display', 'camera' or 'auto'.`);
|
|
695
|
-
}
|
|
696
|
-
if (o !== undefined && (o === null || typeof o !== 'object')) {
|
|
697
|
-
throw new TypeError('@displayxr/inline3d/splat: setRig options must be an object.');
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function addSplatDeferred(wall, canvas, src, opts) {
|
|
702
|
-
const pending = [];
|
|
703
|
-
const queue = (name) => (...args) => {
|
|
704
|
-
pending.push([name, args]);
|
|
705
|
-
return name === 'setFocus' ? out : undefined;
|
|
706
|
-
};
|
|
707
|
-
const page = opts.controls === 'page';
|
|
708
|
-
const pageOnly = (name) => () => {
|
|
709
|
-
throw new Error(
|
|
710
|
-
`@displayxr/inline3d/splat: ${name}() is not available with controls:'page' — the page owns the ` +
|
|
711
|
-
'camera. Drive it with handle.setCameraPose(matrixWorld, { verticalFovDeg, near, far }).',
|
|
712
|
-
);
|
|
713
|
-
};
|
|
714
|
-
// Before the adapter lands, a page calling setCameraPose every frame keeps ONE pending pose
|
|
715
|
-
// (validated now, so a bad call throws at its own line): last call wins, as it will after.
|
|
716
|
-
let pendingPose = null;
|
|
717
|
-
const out = {
|
|
718
|
-
backend: 'playcanvas',
|
|
719
|
-
engine: null,
|
|
720
|
-
viewer: null,
|
|
721
|
-
mesh: null,
|
|
722
|
-
frame: null,
|
|
723
|
-
camera: null,
|
|
724
|
-
rig: null,
|
|
725
|
-
perf: null,
|
|
726
|
-
setPose: page ? pageOnly('setPose') : queue('setPose'),
|
|
727
|
-
resetPose: page ? pageOnly('resetPose') : queue('resetPose'),
|
|
728
|
-
setCameraPose(matrixWorld, o) {
|
|
729
|
-
if (!page) {
|
|
730
|
-
throw new Error(
|
|
731
|
-
"@displayxr/inline3d/splat: setCameraPose() needs addSplat(…, { controls:'page' }) — " +
|
|
732
|
-
'with the default controls the SDK owns the camera (use setPose).',
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
const pose = normalizeCameraPose(matrixWorld, o);
|
|
736
|
-
if (pendingPose) pendingPose[1] = [pose.matrixWorld, pose];
|
|
737
|
-
else pending.push((pendingPose = ['setCameraPose', [pose.matrixWorld, pose]]));
|
|
738
|
-
return out;
|
|
739
|
-
},
|
|
740
|
-
getCameraPose: () =>
|
|
741
|
-
pendingPose
|
|
742
|
-
? {
|
|
743
|
-
matrixWorld: Float32Array.from(pendingPose[1][1].matrixWorld),
|
|
744
|
-
verticalFovDeg: pendingPose[1][1].verticalFovDeg,
|
|
745
|
-
near: pendingPose[1][1].near,
|
|
746
|
-
far: pendingPose[1][1].far,
|
|
747
|
-
convergence: pendingPose[1][1].convergence,
|
|
748
|
-
}
|
|
749
|
-
: null,
|
|
750
|
-
setFocus: queue('setFocus'),
|
|
751
|
-
// A swap requested before the first asset has landed runs once it has (the adapter's own
|
|
752
|
-
// setSource replaces this stub on the same object by then).
|
|
753
|
-
setSource: (...args) => out.ready.then(() => out.setSource(...args)),
|
|
754
|
-
prepareSource: (...args) => out.ready.then(() => out.prepareSource(...args)),
|
|
755
|
-
// A rig switch before the first asset has landed: validated now (controls:'page' and a bad
|
|
756
|
-
// type throw at the call's own line), applied once it has.
|
|
757
|
-
setRig: page
|
|
758
|
-
? pageOnly('setRig')
|
|
759
|
-
: (type, o) => {
|
|
760
|
-
validateSetRigArgs(type, o);
|
|
761
|
-
return out.ready.then(() => out.setRig(type, o));
|
|
762
|
-
},
|
|
763
|
-
// A video before the first asset has landed: controls:'page' throws at the call's own line,
|
|
764
|
-
// the rest is validated by the adapter's setVideo, which runs once the asset is on screen.
|
|
765
|
-
setVideo: page ? pageOnly('setVideo') : (src, o) => out.ready.then(() => out.setVideo(src, o)),
|
|
766
|
-
// Effects before the adapter has loaded: validated NOW (a bad call throws at its own line),
|
|
767
|
-
// then run once the first asset is on screen.
|
|
768
|
-
playEffect: (name, o) => {
|
|
769
|
-
validateEffectCall(name, o, 'play');
|
|
770
|
-
return out.ready.then(() => out.playEffect(name, o));
|
|
771
|
-
},
|
|
772
|
-
setEffect: (name, params) => {
|
|
773
|
-
validateEffectCall(name, params, 'set');
|
|
774
|
-
pending.push(['setEffect', [name, params]]);
|
|
775
|
-
return out;
|
|
776
|
-
},
|
|
777
|
-
stopEffect: queue('stopEffect'),
|
|
778
|
-
// A layer-rig request before the adapter has loaded: queued, replayed in order once it has
|
|
779
|
-
// (it needs no asset, only the engine's layers — which the adapter resolves lazily).
|
|
780
|
-
setLayerRig: (...args) => {
|
|
781
|
-
pending.push(['setLayerRig', args]);
|
|
782
|
-
return out;
|
|
783
|
-
},
|
|
784
|
-
setLayerRigOptions: (...args) => {
|
|
785
|
-
pending.push(['setLayerRigOptions', args]);
|
|
786
|
-
return out;
|
|
787
|
-
},
|
|
788
|
-
layerRigState: () => ({ display: [], disabled: false, path: null, engaged: false, rounded: false, reason: 'the PlayCanvas adapter has not loaded yet', viewerDistance: 0.6, gain: null, planeM: null, photoConvergenceM: null, planeOffset: 0, located: null }),
|
|
789
|
-
makeSbsMaterial: () => {
|
|
790
|
-
throw new Error('@displayxr/inline3d/splat: makeSbsMaterial() needs the engine — call it after `await handle.ready`.');
|
|
791
|
-
},
|
|
792
|
-
effects: () => [],
|
|
793
|
-
getFocus: () => null,
|
|
794
|
-
// A plain data slot the adapter reads at call time, so a callback assigned on the very next
|
|
795
|
-
// line after addSplat — before the module has loaded — is the one that fires.
|
|
796
|
-
onFocusChange: null,
|
|
797
|
-
pick: () => null,
|
|
798
|
-
// Splat accounting (resident / budget / first frame); null until the adapter has loaded.
|
|
799
|
-
stats: () => null,
|
|
800
|
-
remove: queue('remove'),
|
|
801
|
-
exclude: queue('exclude'),
|
|
802
|
-
unexclude: queue('unexclude'),
|
|
803
|
-
};
|
|
804
|
-
// `firstWoven` exists from the first line, like every other field a page reads right away; the
|
|
805
|
-
// adapter settles it with the core handle's own once the module has loaded.
|
|
806
|
-
out.firstWoven = new Promise((resolve) => {
|
|
807
|
-
out._resolveFirstWoven = resolve;
|
|
808
|
-
});
|
|
809
|
-
// The ONE owner of `ready`: the adapter returns its load promise and never touches this field.
|
|
810
|
-
out.ready = import('./inline3d-splat-playcanvas.js')
|
|
811
|
-
.then((m) => m.attachPlayCanvasSplat(out, wall, canvas, src, opts, pending))
|
|
812
|
-
.catch((err) => {
|
|
813
|
-
// The adapter warns about its own load failures; this is for the module not arriving.
|
|
814
|
-
if (!out.viewer) console.warn('[inline3d/splat] engine:playcanvas failed to start', err);
|
|
815
|
-
if (out._resolveFirstWoven) {
|
|
816
|
-
out._resolveFirstWoven(Object.freeze({ woven: false, confirmed: false, reason: 'layer-failed', ms: 0 }));
|
|
817
|
-
delete out._resolveFirstWoven;
|
|
818
|
-
}
|
|
819
|
-
throw err;
|
|
820
|
-
});
|
|
821
|
-
return out;
|
|
822
|
-
}
|
|
823
681
|
|
|
824
682
|
/**
|
|
825
683
|
* Percentile bounds from the loaded splats — the fallback when no sidecar was supplied.
|