@displayxr/inline3d 1.18.0 → 1.19.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 +22 -0
- package/README.md +2 -1
- package/js/inline3d-model-entry.js +1 -1
- package/js/inline3d-model-playcanvas.js +1 -0
- package/js/inline3d-splat-playcanvas.js +108 -13
- package/js/inline3d-splat-shared.js +44 -0
- package/model.d.ts +2 -0
- package/package.json +1 -1
- package/splat.d.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ 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
|
+
## 1.19.0 — 2026-09-24
|
|
9
|
+
|
|
10
|
+
Touches the **preview tier** (`./splat` and `./model`, `engine: 'playcanvas'` only). The defaults
|
|
11
|
+
keep the wheel's pixels. One default does change: a two-finger touch now pinch-zooms (within the
|
|
12
|
+
old 0.2–6 range, with no relax) where it used to act as an erratic one-finger drag.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`zoom: { min, max, relax, ease }`** (#36), bounded zoom that relaxes back to rest.
|
|
17
|
+
- Wheel, pinch and `setPose` clamp to `[min, max]`. Defaults are 0.2 and 6, as before.
|
|
18
|
+
- `relax: true` eases back to rest (1×, or the last `setPose` zoom) once the wheel has been idle
|
|
19
|
+
150 ms or the pinch ends. It uses the orbit's τ 0.6 s, with a landing floor so 2× is exactly
|
|
20
|
+
home in about 2.9 s, and it never fights a live gesture.
|
|
21
|
+
- The zoom scales about the focus, so the focus keeps its place and its disparity in both eyes.
|
|
22
|
+
- The gallery's Spatial View passes `{ min: 1, max: 2, relax: true }`: you can't zoom out past
|
|
23
|
+
the frame's edges, and a peek comes back on its own.
|
|
24
|
+
- **Two-finger pinch zoom** on the PlayCanvas viewer, with Pointer Events and the same bounds.
|
|
25
|
+
- Gates, on a real GPU (1280×720):
|
|
26
|
+
- Zooming out is refused: MAE 0 against the rest render.
|
|
27
|
+
- Wheel and pinch reach the 2× cap and return to **MAE 0** against rest 3 s after release.
|
|
28
|
+
- In fake stereo, the focus's disparity is unchanged at 1×, 1.5× and 2×.
|
|
29
|
+
|
|
8
30
|
## 1.18.0 — 2026-09-24
|
|
9
31
|
|
|
10
32
|
Touches the **preview tier** (`./splat`, `engine: 'playcanvas'` only). Additive: a page that never
|
package/README.md
CHANGED
|
@@ -45,7 +45,8 @@ API is not yet covered by the semver promise below.
|
|
|
45
45
|
renders the same splat window with the [PlayCanvas](https://playcanvas.com/) engine instead of
|
|
46
46
|
Spark — same handle, same rig/focus/camera-block behaviour, same `perf` presets (mapped onto the
|
|
47
47
|
engine's knobs) — and adds `setSource(src, { fadeMs })` (crossfading asset swaps), `feather`, a
|
|
48
|
-
tilt-and-relax orbit,
|
|
48
|
+
tilt-and-relax orbit, two-finger pinch plus bounded zoom that relaxes back to rest
|
|
49
|
+
(`zoom: { min, max, relax }`, [§Zoom bounds and relax](docs/playcanvas-adapter.md#zoom-bounds-and-relax)), `handle.engine` (the engine objects, for advanced pages) and
|
|
49
50
|
`handle.setRig('display' | 'camera' | 'auto')`: a live, reversible rig switch, so one persistent tile
|
|
50
51
|
can show a glTF under `handle.engine.root` exactly as `addModel` would and then go back to the photo's
|
|
51
52
|
capture rig ([§setRig](docs/playcanvas-adapter.md#setrig--switching-between-the-display-rig-and-the-camera-rig-36)), and
|
|
@@ -54,7 +54,7 @@ export function resolveModelEngine(opts = {}) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/** Options that exist only on the PlayCanvas backend: an error on an explicit engine:'three'. */
|
|
57
|
-
const PLAYCANVAS_ONLY = ['playcanvas', 'environmentRotation', 'controls', 'comfortDepth', 'onBeforeFrame', 'antialias', 'preserveDrawingBuffer', 'nearClip', 'farClip', 'orbitMaxDeg', 'orbitEase'];
|
|
57
|
+
const PLAYCANVAS_ONLY = ['playcanvas', 'environmentRotation', 'controls', 'comfortDepth', 'onBeforeFrame', 'antialias', 'preserveDrawingBuffer', 'nearClip', 'farClip', 'orbitMaxDeg', 'orbitEase', 'zoom'];
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Everything that can be checked before a module loads. Throws at CALL time — a page bug is true
|
|
@@ -903,6 +903,7 @@ export function attachPlayCanvasModel(out, wall, canvas, src, opts, pending = []
|
|
|
903
903
|
flipY: false, // glTF is already Y-up
|
|
904
904
|
orbitMaxDeg: opts.orbitMaxDeg,
|
|
905
905
|
orbitEase: opts.orbitEase,
|
|
906
|
+
zoom: opts.zoom,
|
|
906
907
|
feather,
|
|
907
908
|
nearClip: opts.nearClip,
|
|
908
909
|
farClip: opts.farClip,
|
|
@@ -84,8 +84,9 @@ import {
|
|
|
84
84
|
WHEEL_PAGE_PX,
|
|
85
85
|
WHEEL_MAX_PX,
|
|
86
86
|
ZOOM_PER_PX,
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
ZOOM_WHEEL_IDLE_MS,
|
|
88
|
+
ZOOM_RELAX_MIN_RATE,
|
|
89
|
+
resolveZoomOption,
|
|
89
90
|
MONO_FOV,
|
|
90
91
|
MONO_NEAR,
|
|
91
92
|
MONO_FAR,
|
|
@@ -628,6 +629,7 @@ export class PlayCanvasSplatViewer {
|
|
|
628
629
|
pitchLimit = PITCH_LIMIT,
|
|
629
630
|
orbitMaxDeg = ORBIT_MAX_DEG,
|
|
630
631
|
orbitEase = {},
|
|
632
|
+
zoom,
|
|
631
633
|
feather = 0,
|
|
632
634
|
captureFit = 'height',
|
|
633
635
|
nearClip,
|
|
@@ -665,6 +667,15 @@ export class PlayCanvasSplatViewer {
|
|
|
665
667
|
this.orbitMaxDeg = orbitMaxDeg;
|
|
666
668
|
this.orbitEase = { drag: orbitEase.drag ?? ORBIT_TAU_DRAG_S, rest: orbitEase.rest ?? ORBIT_TAU_REST_S };
|
|
667
669
|
this._orbitMode = null;
|
|
670
|
+
// Zoom bounds + relax (./inline3d-splat-shared.js §ZOOM). Wheel and pinch clamp to
|
|
671
|
+
// [zoomOpts.min, zoomOpts.max]; with relax, the zoom eases back to `_restZoom` (1×, or the
|
|
672
|
+
// last setPose zoom) once the wheel is idle or the pinch ends. `_zoomMode` is 'rest' while
|
|
673
|
+
// relaxing, else null (the ordinary damping). `_pinching` holds the relax off.
|
|
674
|
+
this.zoomOpts = resolveZoomOption(zoom);
|
|
675
|
+
this._zoomMode = null;
|
|
676
|
+
this._restZoom = clamp(1, this.zoomOpts.min, this.zoomOpts.max);
|
|
677
|
+
this._lastWheel = 0;
|
|
678
|
+
this._pinching = false;
|
|
668
679
|
/** handle.setVideo: orbit/zoom input ignored while a video plane is on. */
|
|
669
680
|
this.inputLocked = false;
|
|
670
681
|
/** handle.setVideo's plane (./inline3d-splat-video.js) while a video is on, else null. */
|
|
@@ -693,8 +704,8 @@ export class PlayCanvasSplatViewer {
|
|
|
693
704
|
this.flipY = opts.flipY !== false;
|
|
694
705
|
|
|
695
706
|
this._fitScale = 1;
|
|
696
|
-
this._zoom =
|
|
697
|
-
this._targetZoom =
|
|
707
|
+
this._zoom = this._restZoom;
|
|
708
|
+
this._targetZoom = this._restZoom;
|
|
698
709
|
this._depthOffset = 0;
|
|
699
710
|
this._subjectHalf = [0, 0, 0];
|
|
700
711
|
this._yaw = 0;
|
|
@@ -815,7 +826,11 @@ export class PlayCanvasSplatViewer {
|
|
|
815
826
|
if (pitch !== undefined) {
|
|
816
827
|
this._targetPitch = this._pitch = clamp(pitch, this.pitchLimit[0], this.pitchLimit[1]);
|
|
817
828
|
}
|
|
818
|
-
if (zoom !== undefined)
|
|
829
|
+
if (zoom !== undefined) {
|
|
830
|
+
// A snap, and the new rest: a relax comes home HERE, not to 1×.
|
|
831
|
+
this._targetZoom = this._zoom = this._restZoom = clamp(zoom, this.zoomOpts.min, this.zoomOpts.max);
|
|
832
|
+
this._zoomMode = null;
|
|
833
|
+
}
|
|
819
834
|
if (depthOffset !== undefined) this._depthOffset = finite(depthOffset, this._depthOffset);
|
|
820
835
|
this._applyTransform();
|
|
821
836
|
}
|
|
@@ -1748,7 +1763,33 @@ export class PlayCanvasSplatViewer {
|
|
|
1748
1763
|
) {
|
|
1749
1764
|
this._orbitMode = null; // at rest: the ordinary damping (and the idle turntable) take over
|
|
1750
1765
|
}
|
|
1751
|
-
|
|
1766
|
+
// Zoom relax: the wheel has gone idle (or the pinch ended) away from rest — head home.
|
|
1767
|
+
if (
|
|
1768
|
+
this.zoomOpts.relax &&
|
|
1769
|
+
!this._zoomMode &&
|
|
1770
|
+
!this._pinching &&
|
|
1771
|
+
t - this._lastWheel > ZOOM_WHEEL_IDLE_MS &&
|
|
1772
|
+
Math.abs(this._targetZoom - this._restZoom) > 1e-6
|
|
1773
|
+
) {
|
|
1774
|
+
this._targetZoom = this._restZoom;
|
|
1775
|
+
this._zoomMode = 'rest';
|
|
1776
|
+
}
|
|
1777
|
+
// Relaxing eases with τ = zoomOpts.ease (the orbit's 0.6 s by default); the wheel's own
|
|
1778
|
+
// damping otherwise. Both in log space, so 2× → 1× reads like 1× → ½×.
|
|
1779
|
+
if (this._zoomMode === 'rest') {
|
|
1780
|
+
// The exponential (τ = zoomOpts.ease) with a landing floor: it never moves slower than
|
|
1781
|
+
// ZOOM_RELAX_MIN_RATE (log-zoom per second), so it ARRIVES — 2× is home in ≈3 s — instead of
|
|
1782
|
+
// creeping in forever or popping at a snap. Above ~1 % from home the floor never binds.
|
|
1783
|
+
const gap = Math.log(this._targetZoom / this._zoom);
|
|
1784
|
+
const ko = dt > 0 ? 1 - Math.exp(-dt / this.zoomOpts.ease) : 0;
|
|
1785
|
+
const step = Math.min(Math.abs(gap), Math.max(Math.abs(gap) * ko, ZOOM_RELAX_MIN_RATE * dt));
|
|
1786
|
+
if (Math.abs(gap) - step < 1e-6) {
|
|
1787
|
+
this._zoom = this._targetZoom;
|
|
1788
|
+
this._zoomMode = null;
|
|
1789
|
+
} else {
|
|
1790
|
+
this._zoom *= Math.exp(Math.sign(gap) * step);
|
|
1791
|
+
}
|
|
1792
|
+
} else if (Math.abs(this._targetZoom - this._zoom) > 1e-4) {
|
|
1752
1793
|
this._zoom *= Math.pow(this._targetZoom / this._zoom, k);
|
|
1753
1794
|
} else {
|
|
1754
1795
|
this._zoom = this._targetZoom;
|
|
@@ -1869,8 +1910,41 @@ export class PlayCanvasSplatViewer {
|
|
|
1869
1910
|
let startY = 0;
|
|
1870
1911
|
let restYaw = 0;
|
|
1871
1912
|
let restPitch = 0;
|
|
1913
|
+
// Pinch: two pointers down zoom by the ratio of their spread, about the focus (the pivot's
|
|
1914
|
+
// scale is S(fit × zoom) about it), within zoomOpts' bounds. The drag ends when the second
|
|
1915
|
+
// finger lands (its tilt relaxes); the finger left after a pinch does not orbit.
|
|
1916
|
+
const pointers = new Map(); // pointerId → [clientX, clientY]
|
|
1917
|
+
let pinchD0 = 0;
|
|
1918
|
+
let pinchZ0 = 1;
|
|
1919
|
+
const spread = () => {
|
|
1920
|
+
const [a, b] = [...pointers.values()];
|
|
1921
|
+
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
1922
|
+
};
|
|
1923
|
+
const endDrag = () => {
|
|
1924
|
+
dragging = false;
|
|
1925
|
+
this._targetYaw = restYaw;
|
|
1926
|
+
this._targetPitch = clamp(restPitch, this.pitchLimit[0], this.pitchLimit[1]);
|
|
1927
|
+
this._orbitMode = 'rest';
|
|
1928
|
+
};
|
|
1872
1929
|
this._onDown = (ev) => {
|
|
1873
1930
|
if (this.inputLocked) return; // handle.setVideo: a screen-locked plane has nothing to orbit
|
|
1931
|
+
if (ev.pointerId !== undefined) pointers.set(ev.pointerId, [ev.clientX, ev.clientY]);
|
|
1932
|
+
try {
|
|
1933
|
+
el.setPointerCapture?.(ev.pointerId);
|
|
1934
|
+
} catch {
|
|
1935
|
+
// An inactive pointer id (a synthetic event) throws; the gesture works without capture.
|
|
1936
|
+
}
|
|
1937
|
+
if (pointers.size === 2) {
|
|
1938
|
+
if (dragging) endDrag();
|
|
1939
|
+
this._pinching = true;
|
|
1940
|
+
this._zoomMode = null; // a new gesture takes over from a relax
|
|
1941
|
+
pinchD0 = Math.max(spread(), 1);
|
|
1942
|
+
pinchZ0 = this._zoom;
|
|
1943
|
+
this._targetZoom = this._zoom;
|
|
1944
|
+
this._lastInput = now();
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
if (pointers.size > 2) return;
|
|
1874
1948
|
dragging = true;
|
|
1875
1949
|
startX = ev.clientX;
|
|
1876
1950
|
startY = ev.clientY;
|
|
@@ -1879,9 +1953,16 @@ export class PlayCanvasSplatViewer {
|
|
|
1879
1953
|
restPitch = this._orbitMode ? restPitch : this._targetPitch;
|
|
1880
1954
|
this._orbitMode = 'drag';
|
|
1881
1955
|
this._lastInput = now();
|
|
1882
|
-
el.setPointerCapture?.(ev.pointerId);
|
|
1883
1956
|
};
|
|
1884
1957
|
this._onMove = (ev) => {
|
|
1958
|
+
if (pointers.has(ev.pointerId)) pointers.set(ev.pointerId, [ev.clientX, ev.clientY]);
|
|
1959
|
+
if (this._pinching) {
|
|
1960
|
+
if (pointers.size < 2) return;
|
|
1961
|
+
const { min, max } = this.zoomOpts;
|
|
1962
|
+
this._targetZoom = clamp(pinchZ0 * (spread() / pinchD0), min, max);
|
|
1963
|
+
this._lastInput = now();
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1885
1966
|
if (!dragging) return;
|
|
1886
1967
|
const box = el.getBoundingClientRect();
|
|
1887
1968
|
const dx = (ev.clientX - startX) / Math.max(box.width, 1);
|
|
@@ -1896,13 +1977,21 @@ export class PlayCanvasSplatViewer {
|
|
|
1896
1977
|
this._lastInput = now();
|
|
1897
1978
|
};
|
|
1898
1979
|
this._onUp = (ev) => {
|
|
1980
|
+
const had = pointers.delete(ev.pointerId);
|
|
1981
|
+
if (this._pinching && pointers.size < 2) {
|
|
1982
|
+
// The pinch ends when either finger lifts: the zoom relaxes (zoomOpts.relax) from here.
|
|
1983
|
+
this._pinching = false;
|
|
1984
|
+
this._lastWheel = now() - ZOOM_WHEEL_IDLE_MS - 1;
|
|
1985
|
+
this._lastInput = now();
|
|
1986
|
+
}
|
|
1987
|
+
try {
|
|
1988
|
+
if (had || ev.pointerId === undefined) el.releasePointerCapture?.(ev.pointerId);
|
|
1989
|
+
} catch {
|
|
1990
|
+
// see pointerdown
|
|
1991
|
+
}
|
|
1899
1992
|
if (!dragging) return;
|
|
1900
|
-
|
|
1901
|
-
this._targetYaw = restYaw;
|
|
1902
|
-
this._targetPitch = clamp(restPitch, this.pitchLimit[0], this.pitchLimit[1]);
|
|
1903
|
-
this._orbitMode = 'rest';
|
|
1993
|
+
endDrag();
|
|
1904
1994
|
this._lastInput = now();
|
|
1905
|
-
el.releasePointerCapture?.(ev.pointerId);
|
|
1906
1995
|
};
|
|
1907
1996
|
this._onWheel = (ev) => {
|
|
1908
1997
|
if (this.inputLocked) return;
|
|
@@ -1911,7 +2000,11 @@ export class PlayCanvasSplatViewer {
|
|
|
1911
2000
|
if (ev.deltaMode === 1) px *= WHEEL_LINE_PX;
|
|
1912
2001
|
else if (ev.deltaMode === 2) px *= WHEEL_PAGE_PX;
|
|
1913
2002
|
px = clamp(px, -WHEEL_MAX_PX, WHEEL_MAX_PX);
|
|
1914
|
-
|
|
2003
|
+
// A wheel tick during a relax restarts from where the zoom IS, not from the rest target.
|
|
2004
|
+
const from = this._zoomMode === 'rest' ? this._zoom : this._targetZoom;
|
|
2005
|
+
this._zoomMode = null;
|
|
2006
|
+
this._targetZoom = clamp(from * Math.exp(-px * ZOOM_PER_PX), this.zoomOpts.min, this.zoomOpts.max);
|
|
2007
|
+
this._lastWheel = now();
|
|
1915
2008
|
this._lastInput = now();
|
|
1916
2009
|
};
|
|
1917
2010
|
if (el.style) el.style.touchAction = 'none';
|
|
@@ -1933,6 +2026,7 @@ export class PlayCanvasSplatViewer {
|
|
|
1933
2026
|
el.removeEventListener('pointerleave', this._onUp);
|
|
1934
2027
|
el.removeEventListener('wheel', this._onWheel);
|
|
1935
2028
|
this._onDown = null;
|
|
2029
|
+
this._pinching = false;
|
|
1936
2030
|
}
|
|
1937
2031
|
|
|
1938
2032
|
/** The camera currently on screen: [proj, pose] of the first eye in 3D, the mono camera else. */
|
|
@@ -2425,6 +2519,7 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
|
|
|
2425
2519
|
flipY,
|
|
2426
2520
|
orbitMaxDeg: opts.orbitMaxDeg,
|
|
2427
2521
|
orbitEase: opts.orbitEase,
|
|
2522
|
+
zoom: opts.zoom,
|
|
2428
2523
|
feather,
|
|
2429
2524
|
captureFit,
|
|
2430
2525
|
nearClip: opts.nearClip,
|
|
@@ -238,6 +238,49 @@ export const ORBIT_TAU_DRAG_S = 0.2;
|
|
|
238
238
|
/** Time constant of the relax back to rest after release, seconds. */
|
|
239
239
|
export const ORBIT_TAU_REST_S = 0.6;
|
|
240
240
|
|
|
241
|
+
// ── ZOOM: bounds + relax (the PlayCanvas backend's `zoom` option) ───────────────────────────
|
|
242
|
+
|
|
243
|
+
/** Wheel "release": this long with no wheel event ends the gesture and starts the relax, ms. */
|
|
244
|
+
export const ZOOM_WHEEL_IDLE_MS = 150;
|
|
245
|
+
/**
|
|
246
|
+
* The zoom relax's landing floor, log-zoom per second. The relax is the orbit's exponential
|
|
247
|
+
* (τ = 0.6 s), except it never moves slower than this, so it lands in finite time: from 2× the
|
|
248
|
+
* exponential carries it to 1.5 % from home in ≈2.3 s and the floor finishes in ≈0.6 s more.
|
|
249
|
+
*/
|
|
250
|
+
export const ZOOM_RELAX_MIN_RATE = 0.025;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Validate + default the `zoom` option: `{ min, max, relax, ease }`.
|
|
254
|
+
*
|
|
255
|
+
* Defaults are today's behaviour — ZOOM_MIN..ZOOM_MAX, no relax. `relax: true` eases the zoom
|
|
256
|
+
* back to its rest (1×, or the last setPose zoom) once the wheel has been idle
|
|
257
|
+
* ZOOM_WHEEL_IDLE_MS or a pinch ends, with τ = `ease` (default ORBIT_TAU_REST_S, the orbit's
|
|
258
|
+
* relax — the two gestures come home together).
|
|
259
|
+
*
|
|
260
|
+
* @returns {{min:number, max:number, relax:boolean, ease:number}}
|
|
261
|
+
*/
|
|
262
|
+
export function resolveZoomOption(zoom) {
|
|
263
|
+
if (zoom === undefined || zoom === null) return { min: ZOOM_MIN, max: ZOOM_MAX, relax: false, ease: ORBIT_TAU_REST_S };
|
|
264
|
+
if (typeof zoom !== 'object') {
|
|
265
|
+
throw new Error(`@displayxr/inline3d/splat: zoom ${zoom} — expected { min, max, relax, ease }.`);
|
|
266
|
+
}
|
|
267
|
+
const num = (k, v, d) => {
|
|
268
|
+
if (v === undefined) return d;
|
|
269
|
+
if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) {
|
|
270
|
+
throw new Error(`@displayxr/inline3d/splat: zoom.${k} ${v} — expected a positive number.`);
|
|
271
|
+
}
|
|
272
|
+
return v;
|
|
273
|
+
};
|
|
274
|
+
const min = num('min', zoom.min, ZOOM_MIN);
|
|
275
|
+
const max = num('max', zoom.max, ZOOM_MAX);
|
|
276
|
+
if (max < min) throw new Error(`@displayxr/inline3d/splat: zoom.max ${max} is below zoom.min ${min}.`);
|
|
277
|
+
const ease = num('ease', zoom.ease, ORBIT_TAU_REST_S);
|
|
278
|
+
if (zoom.relax !== undefined && typeof zoom.relax !== 'boolean') {
|
|
279
|
+
throw new Error(`@displayxr/inline3d/splat: zoom.relax ${zoom.relax} — expected true or false.`);
|
|
280
|
+
}
|
|
281
|
+
return { min, max, relax: zoom.relax === true, ease };
|
|
282
|
+
}
|
|
283
|
+
|
|
241
284
|
/**
|
|
242
285
|
* The capture camera's off-axis WINDOW at the near plane — the one projection both backends'
|
|
243
286
|
* camera rigs draw the mono (flat) view through. Principal point honoured, so a deconverged
|
|
@@ -346,6 +389,7 @@ export const PAGE_IGNORED_OPTIONS = Object.freeze([
|
|
|
346
389
|
'depthLimit',
|
|
347
390
|
'orbitMaxDeg',
|
|
348
391
|
'orbitEase',
|
|
392
|
+
'zoom',
|
|
349
393
|
'captureFit',
|
|
350
394
|
]);
|
|
351
395
|
|
package/model.d.ts
CHANGED
|
@@ -117,6 +117,8 @@ export interface ModelOptions {
|
|
|
117
117
|
orbitMaxDeg?: number;
|
|
118
118
|
/** PlayCanvas only: the orbit's easing time constants, seconds. */
|
|
119
119
|
orbitEase?: { drag?: number; rest?: number };
|
|
120
|
+
/** PlayCanvas only: zoom bounds + relax (wheel, pinch, setPose) — see the splat option. */
|
|
121
|
+
zoom?: { min?: number; max?: number; relax?: boolean; ease?: number };
|
|
120
122
|
/**
|
|
121
123
|
* PlayCanvas only: who owns the camera — the SDK's viewer (default) or the page, through
|
|
122
124
|
* {@link ModelHandle.setCameraPose} every frame (the attach rig; converges on the model's bounds
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@displayxr/inline3d",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.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",
|
package/splat.d.ts
CHANGED
|
@@ -259,6 +259,15 @@ export interface SplatOptions {
|
|
|
259
259
|
orbitMaxDeg?: number;
|
|
260
260
|
/** PlayCanvas: easing time constants, seconds — `drag` while held (0.2), `rest` after release (0.6). */
|
|
261
261
|
orbitEase?: { drag?: number; rest?: number };
|
|
262
|
+
/**
|
|
263
|
+
* PlayCanvas: zoom bounds + relax, for the wheel, the two-finger pinch and `setPose`. `min` /
|
|
264
|
+
* `max` clamp the zoom (defaults 0.2 / 6, the pre-1.19 range); `relax: true` eases it back to
|
|
265
|
+
* rest — 1×, or the last `setPose` zoom — once the wheel has been idle 150 ms or the pinch ends,
|
|
266
|
+
* with the orbit's time constant (`ease`, default 0.6 s) and a landing floor so it arrives (2×
|
|
267
|
+
* is home in ≈3 s). The zoom scales the subject about the focus, which keeps its screen position
|
|
268
|
+
* and its disparity. `{ min: 1, max: 2, relax: true }` is a peek that never shows the splat's edges.
|
|
269
|
+
*/
|
|
270
|
+
zoom?: { min?: number; max?: number; relax?: boolean; ease?: number };
|
|
262
271
|
fit?: 'contain' | 'height' | 'cover' | 'none';
|
|
263
272
|
/** Fraction of the tile the subject may occupy (default 0.8) — width AND height. */
|
|
264
273
|
margin?: number;
|