@displayxr/inline3d 1.6.1 → 1.7.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 +98 -0
- package/README.md +1 -0
- package/js/inline3d-sog.js +295 -0
- package/js/inline3d-splat-perf.js +255 -0
- package/js/inline3d-splat-rig.js +330 -0
- package/js/inline3d-splat.js +481 -8
- package/js/inline3d-viewer.js +125 -2
- package/package.json +4 -1
- package/splat.d.ts +164 -0
- package/viewer.d.ts +16 -0
package/js/inline3d-viewer.js
CHANGED
|
@@ -52,6 +52,15 @@ const DEFAULT_DEPTH_LIMIT = 4.0;
|
|
|
52
52
|
/** Milliseconds of no interaction before the idle turntable starts. */
|
|
53
53
|
const IDLE_DELAY_MS = 2500;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Per-frame easing factor for a focus change, matching the gallery's `EASE`.
|
|
57
|
+
*
|
|
58
|
+
* Deliberately per FRAME and not per second, because that is what the reference implementation
|
|
59
|
+
* does and a focus change is a one-off gesture response rather than a continuous motion — the
|
|
60
|
+
* difference between 60 and 120 Hz here is a settle that takes half as long, not a bug.
|
|
61
|
+
*/
|
|
62
|
+
const FOCUS_EASE = 0.18;
|
|
63
|
+
|
|
55
64
|
const clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);
|
|
56
65
|
// NaN/Infinity into a transform silently blanks the tile — three propagates it into the
|
|
57
66
|
// matrix and every vertex lands undefined. Reject at the setter instead.
|
|
@@ -226,6 +235,23 @@ export class SceneViewer {
|
|
|
226
235
|
|
|
227
236
|
this._fitScale = 1;
|
|
228
237
|
this._zoom = 1;
|
|
238
|
+
// FOCUS — the point everything turns about, eased. `_focus` is where it is now, `_target`
|
|
239
|
+
// where it is going; `_orbitCentre` is where the pivot sits afterwards, and it is what
|
|
240
|
+
// separates the two rigs: a DISPLAY rig brings the focused point to the middle of the tile
|
|
241
|
+
// (centre 0), a CAMERA rig leaves the capture exactly where it was placed and only moves
|
|
242
|
+
// what the rotation turns about (centre = the focus point). See setFocus().
|
|
243
|
+
// Plain triples, not THREE.Vector3: this module takes its THREE by injection and is tested
|
|
244
|
+
// against a stub, so every three.js type it reaches for is one more thing a consumer has to
|
|
245
|
+
// supply. Three numbers need no library.
|
|
246
|
+
this._focus = { x: 0, y: 0, z: 0 };
|
|
247
|
+
this._targetFocus = { x: 0, y: 0, z: 0 };
|
|
248
|
+
this._orbitCentre = { x: 0, y: 0, z: 0 };
|
|
249
|
+
this._focusRecentres = true;
|
|
250
|
+
this._focusSettled = true;
|
|
251
|
+
/** Called after every focus ease step, with the live focus. Set by ./splat. */
|
|
252
|
+
this.onFocusChange = null;
|
|
253
|
+
/** Called at the end of every _tick, after the transform is applied. */
|
|
254
|
+
this.onTick = null;
|
|
229
255
|
this._targetZoom = 1;
|
|
230
256
|
// Author-driven slide along the depth axis, display metres, +z toward the viewer. Applied
|
|
231
257
|
// by _applyTransform, PRESERVED by fitTo, cleared by resetPose. Default 0 means every page
|
|
@@ -307,7 +333,10 @@ export class SceneViewer {
|
|
|
307
333
|
const c = Array.isArray(center) ? center : [center.x, center.y, center.z];
|
|
308
334
|
const e = Array.isArray(extent) ? extent : [extent.x, extent.y, extent.z];
|
|
309
335
|
|
|
310
|
-
|
|
336
|
+
// Through the focus, not around it: framing a subject IS pointing the viewer at its centre,
|
|
337
|
+
// and keeping the two in one place is what stops an orbit turning about somewhere the fit
|
|
338
|
+
// has since moved away from. Snapped — a refit is not a gesture.
|
|
339
|
+
this.setFocus(c, { snap: true });
|
|
311
340
|
// Recorded for getSubjectBounds(). Model units; the fit scale is applied at read time so a
|
|
312
341
|
// later zoom or orbit needs no re-measure.
|
|
313
342
|
this._subjectHalf = [Math.abs(e[0]) / 2, Math.abs(e[1]) / 2, Math.abs(e[2]) / 2];
|
|
@@ -480,6 +509,57 @@ export class SceneViewer {
|
|
|
480
509
|
this._applyTransform();
|
|
481
510
|
}
|
|
482
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Point the viewer at something — the one point that is simultaneously the orbit centre, the
|
|
514
|
+
* pivot plane and (on a camera rig) the convergence distance.
|
|
515
|
+
*
|
|
516
|
+
* Those three are the same thing and saying so is the point of this method. A viewer that lets
|
|
517
|
+
* them drift apart orbits about one place, converges at another and rotates the picture around
|
|
518
|
+
* a third, which is how "the scene swings away when I turn it" happens.
|
|
519
|
+
*
|
|
520
|
+
* The two rigs differ in what MOVES, and only in that:
|
|
521
|
+
*
|
|
522
|
+
* - **`recentre: true`** (a display rig, the default) — the focused point is brought to the
|
|
523
|
+
* middle of the tile and onto the zero-disparity plane. That is what a portal does: you
|
|
524
|
+
* chose a subject, so the subject is what the window shows.
|
|
525
|
+
* - **`recentre: false`** (a camera rig) — the capture stays exactly where it was placed and
|
|
526
|
+
* only the rotation centre moves. Translating a camera-rig scene would move the viewpoint,
|
|
527
|
+
* and the neutral view IS the photograph; nothing may move it.
|
|
528
|
+
*
|
|
529
|
+
* Eased at {@link FOCUS_EASE} per frame unless `snap`.
|
|
530
|
+
*
|
|
531
|
+
* @param {{x:number,y:number,z:number}|number[]|null} point in CONTENT space (the space your
|
|
532
|
+
* object sits in, i.e. `viewer.content`'s local space). Null resets to the origin.
|
|
533
|
+
* @param {object} [opts]
|
|
534
|
+
* @param {boolean} [opts.snap=false] arrive immediately.
|
|
535
|
+
* @param {boolean} [opts.recentre] see above. Sticky: set once when the rig is chosen.
|
|
536
|
+
*/
|
|
537
|
+
setFocus(point, { snap = false, recentre } = {}) {
|
|
538
|
+
if (recentre !== undefined) this._focusRecentres = !!recentre;
|
|
539
|
+
const p = point == null ? [0, 0, 0] : Array.isArray(point) ? point : [point.x, point.y, point.z];
|
|
540
|
+
this._targetFocus = { x: finite(p[0], 0), y: finite(p[1], 0), z: finite(p[2], 0) };
|
|
541
|
+
this._focusSettled = false;
|
|
542
|
+
if (snap) {
|
|
543
|
+
this._focus = { ...this._targetFocus };
|
|
544
|
+
this._focusSettled = true;
|
|
545
|
+
this._applyFocus();
|
|
546
|
+
this._applyTransform();
|
|
547
|
+
this.onFocusChange?.(this._focus);
|
|
548
|
+
}
|
|
549
|
+
return this;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Where the viewer is pointed, in content space.
|
|
554
|
+
*
|
|
555
|
+
* @param {object} [opts]
|
|
556
|
+
* @param {boolean} [opts.target=false] the value being eased TOWARD, as with getPose().
|
|
557
|
+
*/
|
|
558
|
+
getFocus({ target = false } = {}) {
|
|
559
|
+
const v = target ? this._targetFocus : this._focus;
|
|
560
|
+
return { x: v.x, y: v.y, z: v.z };
|
|
561
|
+
}
|
|
562
|
+
|
|
483
563
|
/** Return to the framed default pose, depth slide included. */
|
|
484
564
|
resetPose() {
|
|
485
565
|
this.setPose({ yaw: 0, pitch: 0, zoom: 1, depthOffset: 0 });
|
|
@@ -757,7 +837,13 @@ export class SceneViewer {
|
|
|
757
837
|
// The depth slide lives here, not in fitTo, so it survives a refit and cannot be left
|
|
758
838
|
// stale by a code path that forgets it. x/y are never written: the fit centres the subject
|
|
759
839
|
// on the tile and sliding it sideways is a scene concern, not a viewer one.
|
|
760
|
-
|
|
840
|
+
// The orbit centre is where the pivot SITS; the depth slide rides on top of it. Both are
|
|
841
|
+
// zero for the ordinary framed subject, so this is identity for every existing page.
|
|
842
|
+
this._pivot.position.set(
|
|
843
|
+
this._orbitCentre.x,
|
|
844
|
+
this._orbitCentre.y,
|
|
845
|
+
this._orbitCentre.z + this._depthOffset,
|
|
846
|
+
);
|
|
761
847
|
// Order 'XYZ' == R = Rx(pitch) · Ry(yaw), and the order is the whole point.
|
|
762
848
|
//
|
|
763
849
|
// Yaw must act in the subject's OWN frame (spin it on its axis); pitch must act in the
|
|
@@ -824,7 +910,44 @@ export class SceneViewer {
|
|
|
824
910
|
} else {
|
|
825
911
|
this._zoom = this._targetZoom;
|
|
826
912
|
}
|
|
913
|
+
this._easeFocus();
|
|
827
914
|
this._applyTransform();
|
|
915
|
+
this.onTick?.();
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Walk the live focus toward its target. A no-op — not even a vector compare — for every
|
|
920
|
+
* viewer that never sets one.
|
|
921
|
+
*/
|
|
922
|
+
_easeFocus() {
|
|
923
|
+
if (this._focusSettled) return;
|
|
924
|
+
const f = this._focus;
|
|
925
|
+
const t = this._targetFocus;
|
|
926
|
+
const dx = t.x - f.x;
|
|
927
|
+
const dy = t.y - f.y;
|
|
928
|
+
const dz = t.z - f.z;
|
|
929
|
+
if (dx * dx + dy * dy + dz * dz < 1e-10) {
|
|
930
|
+
f.x = t.x;
|
|
931
|
+
f.y = t.y;
|
|
932
|
+
f.z = t.z;
|
|
933
|
+
this._focusSettled = true;
|
|
934
|
+
} else {
|
|
935
|
+
f.x += dx * FOCUS_EASE;
|
|
936
|
+
f.y += dy * FOCUS_EASE;
|
|
937
|
+
f.z += dz * FOCUS_EASE;
|
|
938
|
+
}
|
|
939
|
+
this._applyFocus();
|
|
940
|
+
this.onFocusChange?.(f);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/** Write the current focus into the scene graph. */
|
|
944
|
+
_applyFocus() {
|
|
945
|
+
const f = this._focus;
|
|
946
|
+
this._centering.position.set(-f.x, -f.y, -f.z);
|
|
947
|
+
const c = this._orbitCentre;
|
|
948
|
+
c.x = this._focusRecentres ? 0 : f.x;
|
|
949
|
+
c.y = this._focusRecentres ? 0 : f.y;
|
|
950
|
+
c.z = this._focusRecentres ? 0 : f.z;
|
|
828
951
|
}
|
|
829
952
|
|
|
830
953
|
_bindOrbit() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@displayxr/inline3d",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
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",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
"js/inline3d-three.js",
|
|
41
41
|
"js/inline3d-viewer.js",
|
|
42
42
|
"js/inline3d-splat.js",
|
|
43
|
+
"js/inline3d-sog.js",
|
|
44
|
+
"js/inline3d-splat-perf.js",
|
|
45
|
+
"js/inline3d-splat-rig.js",
|
|
43
46
|
"js/inline3d-model.js",
|
|
44
47
|
"index.d.ts",
|
|
45
48
|
"three.d.ts",
|
package/splat.d.ts
CHANGED
|
@@ -3,6 +3,114 @@
|
|
|
3
3
|
|
|
4
4
|
import type { SceneViewer, SubjectBounds, OrbitPose } from './viewer.js';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The knobs behind `SplatOptions.perf`. Every one is a Spark 2.1.0 setting except `alphaRadius`,
|
|
8
|
+
* which is a patch to Spark's own vertex shader (there is no option for it). Bit-exact vs lossy,
|
|
9
|
+
* and the defaults each one overrides, are tabled in `js/inline3d-splat-perf.js`.
|
|
10
|
+
*/
|
|
11
|
+
export interface SplatPerfOptions {
|
|
12
|
+
/** Shrink each quad to the radius where its alpha reaches `alphaFloor`. Bit-exact by default. */
|
|
13
|
+
alphaRadius?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The alpha each splat's tail may be cut at, PER SPLAT. Defaults to `minAlpha`, which is the
|
|
16
|
+
* bit-exact cut; above it this is a lossy crop that scales with each splat's own opacity.
|
|
17
|
+
*/
|
|
18
|
+
alphaFloor?: number;
|
|
19
|
+
/** Drop splats and fragments under this alpha. Spark's default is `0.5/255`. */
|
|
20
|
+
minAlpha?: number;
|
|
21
|
+
/** Quad extent in σ, globally. Spark's default is `Math.sqrt(8)`. */
|
|
22
|
+
maxStdDev?: number;
|
|
23
|
+
/** Drop splats smaller than this, in pixels. Spark's default is 0. */
|
|
24
|
+
minPixelRadius?: number;
|
|
25
|
+
/** Clamp on quad size in pixels — note it SQUASHES rather than crops. Default 512. */
|
|
26
|
+
maxPixelRadius?: number;
|
|
27
|
+
/** 1 = Gaussian falloff, 0 = flat. Not a perf knob; 0 costs MORE. */
|
|
28
|
+
falloff?: number;
|
|
29
|
+
/** Build LOD data at load, so Spark can substitute merged splats against a budget. */
|
|
30
|
+
lod?: boolean | 'quality';
|
|
31
|
+
/** LOD budget multiplier (needs `lod`). */
|
|
32
|
+
lodSplatScale?: number;
|
|
33
|
+
/** Absolute LOD budget in splats (needs `lod`). */
|
|
34
|
+
lodSplatCount?: number;
|
|
35
|
+
/** Minimum on-screen splat size multiplier (needs `lod`); up to ~5 is often invisible. */
|
|
36
|
+
lodRenderScale?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Camera intrinsics for ONE eye, in pixels, OpenCV convention. */
|
|
40
|
+
export interface SogIntrinsics {
|
|
41
|
+
fx: number;
|
|
42
|
+
fy: number;
|
|
43
|
+
cx: number;
|
|
44
|
+
cy: number;
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The `camera` block of a `.sog`'s `meta.json` (v2), plus the fields this SDK derives from it.
|
|
51
|
+
* v2 is a superset of v1: everything but `convention` is optional, `intrinsics` included.
|
|
52
|
+
*/
|
|
53
|
+
export interface SogCamera {
|
|
54
|
+
convention: 'opencv';
|
|
55
|
+
/** Which rig the asset asks for. Null when the block does not say. */
|
|
56
|
+
rig: 'camera' | 'display' | null;
|
|
57
|
+
rest: { position: number[]; rotation: number[] };
|
|
58
|
+
/** Null when the block carried none, or none that were usable — estimate one instead. */
|
|
59
|
+
intrinsics: SogIntrinsics | null;
|
|
60
|
+
stereo: { baseline_m: number } | null;
|
|
61
|
+
/**
|
|
62
|
+
* The point that is simultaneously the orbit centre, the pivot plane and the convergence
|
|
63
|
+
* distance. In the splat's own space. The three distances are advisory.
|
|
64
|
+
*/
|
|
65
|
+
focus: {
|
|
66
|
+
point: number[];
|
|
67
|
+
subject_m: number | null;
|
|
68
|
+
near_m: number | null;
|
|
69
|
+
far_m: number | null;
|
|
70
|
+
source: string | null;
|
|
71
|
+
} | null;
|
|
72
|
+
/** The camera rig's ABSOLUTE scalars. Null when unstated. */
|
|
73
|
+
dxr: { ipdFactor: number | null; parallaxFactor: number | null };
|
|
74
|
+
/** Full vertical angle of the capture in RADIANS; null without intrinsics. */
|
|
75
|
+
verticalFov: number | null;
|
|
76
|
+
/** Principal point off the frame centre, fraction of the frame, y UP; null without intrinsics. */
|
|
77
|
+
principalOffset: { x: number; y: number } | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* What the waterfall resolved, with the step that produced each value beside it — which is the
|
|
82
|
+
* point of it. `intrinsicsSource: 'fallback-28mm'` on an asset that looks zoomed out says more
|
|
83
|
+
* than any amount of staring at the picture.
|
|
84
|
+
*/
|
|
85
|
+
export interface ResolvedRig {
|
|
86
|
+
type: 'camera' | 'display';
|
|
87
|
+
typeSource: 'caller' | 'block' | 'block-present' | 'default';
|
|
88
|
+
rest: { position: number[]; rotation: number[] };
|
|
89
|
+
intrinsics: SogIntrinsics;
|
|
90
|
+
intrinsicsSource: 'block' | 'caller' | 'estimated' | 'fallback-28mm';
|
|
91
|
+
/** 35 mm-equivalent focal of whatever lens was resolved. */
|
|
92
|
+
focalEqMm: number;
|
|
93
|
+
/** The live focus, in the splat's own space. */
|
|
94
|
+
focus: number[];
|
|
95
|
+
focusSource:
|
|
96
|
+
| 'caller'
|
|
97
|
+
| 'caller-convergence'
|
|
98
|
+
| 'block'
|
|
99
|
+
| 'median-disparity'
|
|
100
|
+
| 'default'
|
|
101
|
+
| 'picked'
|
|
102
|
+
| 'set';
|
|
103
|
+
/** What Space returns to. */
|
|
104
|
+
focusDefault: number[];
|
|
105
|
+
focusDefaultSource: string;
|
|
106
|
+
/** The block's advisory distances, when it carried any. */
|
|
107
|
+
focusDistances: { subject_m: number | null; near_m: number | null; far_m: number | null } | null;
|
|
108
|
+
/** Focus distance along the rest camera's view axis — the zero-disparity PLANE. */
|
|
109
|
+
convergence: number;
|
|
110
|
+
ipdFactor: number;
|
|
111
|
+
parallaxFactor: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
6
114
|
export interface SplatOptions {
|
|
7
115
|
/** Metres of world the tile's height spans (default 0.24). */
|
|
8
116
|
virtualDisplayHeight?: number;
|
|
@@ -32,6 +140,41 @@ export interface SplatOptions {
|
|
|
32
140
|
feather?: number;
|
|
33
141
|
/** Minimum ms between splat sorts. Defaults to 16 so both eyes share one sort per frame. */
|
|
34
142
|
sortIntervalMs?: number;
|
|
143
|
+
/**
|
|
144
|
+
* Cut overdraw. UNSET changes nothing — every Spark default stays where Spark put it, so an
|
|
145
|
+
* existing page's pixels do not move.
|
|
146
|
+
*
|
|
147
|
+
* `'exact'` is the bit-exact pair — each quad shrunk to where its own alpha reaches 1/255
|
|
148
|
+
* (those fragments were already being discarded), plus the 1/255 peak-opacity cull. It buys
|
|
149
|
+
* little on a mostly-opaque capture, which is what a lifted photograph is. `'balanced'` (or
|
|
150
|
+
* `true`, −5…−20 % measured) and `'aggressive'` (−22 %) tighten the quad extent instead, which
|
|
151
|
+
* is the axis that actually pays on the web; both move pixels.
|
|
152
|
+
*/
|
|
153
|
+
perf?: true | 'exact' | 'balanced' | 'aggressive' | SplatPerfOptions;
|
|
154
|
+
/**
|
|
155
|
+
* Which view rig. `'auto'` (the default) reads it off the ASSET — a `.sog` carrying a `camera`
|
|
156
|
+
* block was lifted from a photograph and gets a camera rig that conserves the recording
|
|
157
|
+
* camera; anything else is an object and gets the display rig with the auto-frame. Only
|
|
158
|
+
* detectable when `src` is BYTES.
|
|
159
|
+
*/
|
|
160
|
+
rig?: 'auto' | 'display' | 'camera';
|
|
161
|
+
/** Camera rig only: the distance in world metres that sits ON the glass. */
|
|
162
|
+
convergence?: number;
|
|
163
|
+
/**
|
|
164
|
+
* The point to converge on and orbit about, in the splat's own space — the highest step of the
|
|
165
|
+
* focus waterfall. Wins over `convergence`, which is the straight-ahead shorthand for it.
|
|
166
|
+
*/
|
|
167
|
+
focus?: number[];
|
|
168
|
+
/** Override the lens, when the asset carries none and the estimate is wrong. */
|
|
169
|
+
intrinsics?: SogIntrinsics;
|
|
170
|
+
/** Camera rig scalars. ABSOLUTE, never normalised against the convergence. */
|
|
171
|
+
ipdFactor?: number;
|
|
172
|
+
parallaxFactor?: number;
|
|
173
|
+
/**
|
|
174
|
+
* Bind double-click (focus what was clicked) and Space (back to the resolved focus). Default
|
|
175
|
+
* true; pass false when the page owns those gestures itself.
|
|
176
|
+
*/
|
|
177
|
+
focusInput?: boolean;
|
|
35
178
|
/**
|
|
36
179
|
* Disambiguates .splat from .ksplat when passing BYTES — content-sniffing cannot separate
|
|
37
180
|
* those two. Unnecessary for .sog/.ply/.spz, which are identifiable by magic number.
|
|
@@ -55,11 +198,32 @@ export interface SplatHandle {
|
|
|
55
198
|
readonly spark: object;
|
|
56
199
|
/** Bounds actually used for framing; null until `ready` resolves. */
|
|
57
200
|
frame: SubjectBounds | null;
|
|
201
|
+
/**
|
|
202
|
+
* The `.sog`'s `camera` block — the recording camera, when the asset carries one. Null for a
|
|
203
|
+
* URL source, a non-`.sog`, or an object splat (which is most of them).
|
|
204
|
+
*/
|
|
205
|
+
camera: SogCamera | null;
|
|
206
|
+
/** What the waterfall resolved, sources included. Null until `ready` resolves. */
|
|
207
|
+
rig: ResolvedRig | null;
|
|
208
|
+
/** The view-rig descriptor sent to the runtime, on the camera path. */
|
|
209
|
+
viewRig?: object;
|
|
210
|
+
/** What `perf` actually applied, or null. */
|
|
211
|
+
perf: object | null;
|
|
58
212
|
/** Resolves once the asset has loaded and been framed; rejects if the load failed. */
|
|
59
213
|
readonly ready: Promise<SplatHandle>;
|
|
60
214
|
|
|
61
215
|
setPose(pose?: OrbitPose): void;
|
|
62
216
|
resetPose(): void;
|
|
217
|
+
/**
|
|
218
|
+
* Point the window at something, in the SPLAT's own space (the space the `camera` block's
|
|
219
|
+
* `focus.point` is in). Null returns to whatever the waterfall resolved. Eased unless `snap`.
|
|
220
|
+
*/
|
|
221
|
+
setFocus(
|
|
222
|
+
point: number[] | { x: number; y: number; z: number } | null,
|
|
223
|
+
opts?: { snap?: boolean },
|
|
224
|
+
): SplatHandle;
|
|
225
|
+
/** What is under a point on the canvas, in the splat's own space — the double-click's raycast. */
|
|
226
|
+
pick(clientX: number, clientY: number): number[] | null;
|
|
63
227
|
|
|
64
228
|
/** Close this window and release its GPU resources. */
|
|
65
229
|
remove(): void;
|
package/viewer.d.ts
CHANGED
|
@@ -105,6 +105,22 @@ export declare class SceneViewer {
|
|
|
105
105
|
|
|
106
106
|
/** Snap the pose. Writes the eased value and its target together. */
|
|
107
107
|
setPose(pose?: OrbitPose): void;
|
|
108
|
+
/**
|
|
109
|
+
* Point the viewer at something: the orbit centre, the pivot plane and (on a camera rig) the
|
|
110
|
+
* convergence distance are ONE point, and this is it. `recentre: true` (a display rig) brings
|
|
111
|
+
* that point to the middle of the tile; `recentre: false` (a camera rig) leaves the capture
|
|
112
|
+
* where it was placed and moves only the rotation centre. Eased 0.18/frame unless `snap`.
|
|
113
|
+
*/
|
|
114
|
+
setFocus(
|
|
115
|
+
point: { x: number; y: number; z: number } | number[] | null,
|
|
116
|
+
opts?: { snap?: boolean; recentre?: boolean },
|
|
117
|
+
): SceneViewer;
|
|
118
|
+
/** Where the viewer is pointed, in content space. */
|
|
119
|
+
getFocus(opts?: { target?: boolean }): { x: number; y: number; z: number };
|
|
120
|
+
/** Called after every focus ease step, with the live focus. */
|
|
121
|
+
onFocusChange: ((focus: { x: number; y: number; z: number }) => void) | null;
|
|
122
|
+
/** Called at the end of every tick, after the transform is applied. */
|
|
123
|
+
onTick: (() => void) | null;
|
|
108
124
|
|
|
109
125
|
/**
|
|
110
126
|
* The pose as it is right now. `target: true` reports what it is easing TOWARD, which differs
|