@displayxr/inline3d 1.49.3 → 1.50.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 +41 -0
- package/README.md +3 -0
- package/dist/call.js +6 -6
- package/hands.d.ts +198 -0
- package/js/camera/capture.js +9 -0
- package/js/camera/half-sbs.js +83 -0
- package/js/inline3d-hands.js +618 -0
- package/js/inline3d-model-entry.js +3 -1
- package/js/inline3d-splat-playcanvas.js +61 -14
- package/js/inline3d-splat-shared.js +12 -1
- package/js/inline3d-viewer.js +39 -8
- package/model.d.ts +10 -0
- package/package.json +7 -1
- package/splat.d.ts +16 -0
- package/viewer.d.ts +12 -0
|
@@ -380,13 +380,15 @@ const FLIP_Q = [1, 0, 0, 0];
|
|
|
380
380
|
/**
|
|
381
381
|
* SceneViewer's pivot as ONE matrix: content space → display space.
|
|
382
382
|
*
|
|
383
|
-
* M = T(orbitCentre + (0,0,depthOffset)) · Rx(pitch) · Ry(yaw) · S(fit × zoom) · T(−focus)
|
|
383
|
+
* M = T(orbitCentre + (0,0,depthOffset)) · Rz(roll) · Rx(pitch) · Ry(yaw) · S(fit × zoom) · T(−focus)
|
|
384
384
|
*
|
|
385
|
-
* which is exactly the product of its `_pivot` (position, '
|
|
385
|
+
* which is exactly the product of its `_pivot` (position, 'ZXY' rotation, scale) and
|
|
386
386
|
* `_centering` (−focus) groups. Kept as a pure function so a test can hold the two together.
|
|
387
|
+
* Roll (the two-hand twist) is about the VIEW axis, so it sits outside yaw and pitch; at roll 0
|
|
388
|
+
* this is the old Rx · Ry product bit for bit.
|
|
387
389
|
*/
|
|
388
|
-
export function pivotMatrix({ yaw = 0, pitch = 0, scale = 1, focus = [0, 0, 0], orbitCentre = [0, 0, 0], depthOffset = 0 }) {
|
|
389
|
-
const q = pivotQuat(yaw, pitch);
|
|
390
|
+
export function pivotMatrix({ yaw = 0, pitch = 0, roll = 0, scale = 1, focus = [0, 0, 0], orbitCentre = [0, 0, 0], depthOffset = 0 }) {
|
|
391
|
+
const q = pivotQuat(yaw, pitch, roll);
|
|
390
392
|
const R = poseMatrix([0, 0, 0], q);
|
|
391
393
|
const out = new Float64Array(16);
|
|
392
394
|
// columns of R·S
|
|
@@ -400,19 +402,25 @@ export function pivotMatrix({ yaw = 0, pitch = 0, scale = 1, focus = [0, 0, 0],
|
|
|
400
402
|
return out;
|
|
401
403
|
}
|
|
402
404
|
|
|
403
|
-
/**
|
|
404
|
-
|
|
405
|
+
/**
|
|
406
|
+
* R = Rz(roll)·Rx(pitch)·Ry(yaw) — three's Euler order 'ZXY', SceneViewer's — as a quaternion.
|
|
407
|
+
* With roll 0 the Rz factor is the identity quaternion and the product is exactly Rx·Ry.
|
|
408
|
+
*/
|
|
409
|
+
function pivotQuat(yawDeg, pitchDeg, rollDeg = 0) {
|
|
405
410
|
const a = (pitchDeg * DEG) / 2;
|
|
406
411
|
const b = (yawDeg * DEG) / 2;
|
|
407
|
-
|
|
412
|
+
const q = qmul([Math.sin(a), 0, 0, Math.cos(a)], [0, Math.sin(b), 0, Math.cos(b)]);
|
|
413
|
+
if (!rollDeg) return q;
|
|
414
|
+
const c = (rollDeg * DEG) / 2;
|
|
415
|
+
return qmul([0, 0, Math.sin(c), Math.cos(c)], q);
|
|
408
416
|
}
|
|
409
417
|
|
|
410
418
|
/**
|
|
411
419
|
* The INVERSE pivot, as the TRS the engine's rig node takes. M⁻¹ = T(f)·S(1/s)·Rᵀ·T(−t), and a
|
|
412
420
|
* uniform scale commutes with a rotation, so it is position f − Rᵀt/s, rotation Rᵀ, scale 1/s.
|
|
413
421
|
*/
|
|
414
|
-
export function pivotInverseTRS({ yaw = 0, pitch = 0, scale = 1, focus = [0, 0, 0], orbitCentre = [0, 0, 0], depthOffset = 0 }) {
|
|
415
|
-
const q = pivotQuat(yaw, pitch);
|
|
422
|
+
export function pivotInverseTRS({ yaw = 0, pitch = 0, roll = 0, scale = 1, focus = [0, 0, 0], orbitCentre = [0, 0, 0], depthOffset = 0 }) {
|
|
423
|
+
const q = pivotQuat(yaw, pitch, roll);
|
|
416
424
|
const qi = [-q[0], -q[1], -q[2], q[3]];
|
|
417
425
|
const t = [orbitCentre[0], orbitCentre[1], orbitCentre[2] + depthOffset];
|
|
418
426
|
const rt = qrot(qi, t);
|
|
@@ -952,6 +960,11 @@ export class PlayCanvasSplatViewer {
|
|
|
952
960
|
this._pitch = 0;
|
|
953
961
|
this._targetYaw = 0;
|
|
954
962
|
this._targetPitch = 0;
|
|
963
|
+
// Roll about the VIEW axis, degrees — SceneViewer's. Only the two-hand gesture
|
|
964
|
+
// (./inline3d-hands.js) and an explicit setPose({ roll }) write it, so every existing page is
|
|
965
|
+
// bit-identical at 0.
|
|
966
|
+
this._roll = 0;
|
|
967
|
+
this._targetRoll = 0;
|
|
955
968
|
this._focus = { x: 0, y: 0, z: 0 };
|
|
956
969
|
this._targetFocus = { x: 0, y: 0, z: 0 };
|
|
957
970
|
this._orbitCentre = { x: 0, y: 0, z: 0 };
|
|
@@ -1066,7 +1079,7 @@ export class PlayCanvasSplatViewer {
|
|
|
1066
1079
|
this._placeMonoForFit();
|
|
1067
1080
|
}
|
|
1068
1081
|
|
|
1069
|
-
setPose({ yaw, pitch, zoom, depthOffset } = {}) {
|
|
1082
|
+
setPose({ yaw, pitch, zoom, roll, depthOffset } = {}) {
|
|
1070
1083
|
if (this.pageCamera) throw new Error(PAGE_POSE_ERROR);
|
|
1071
1084
|
// A snap, as on SceneViewer; it also ends an orbit relax (the page is driving now).
|
|
1072
1085
|
if (yaw !== undefined || pitch !== undefined) this._orbitMode = null;
|
|
@@ -1074,6 +1087,7 @@ export class PlayCanvasSplatViewer {
|
|
|
1074
1087
|
if (pitch !== undefined) {
|
|
1075
1088
|
this._targetPitch = this._pitch = clamp(pitch, this.pitchLimit[0], this.pitchLimit[1]);
|
|
1076
1089
|
}
|
|
1090
|
+
if (roll !== undefined) this._targetRoll = this._roll = roll;
|
|
1077
1091
|
if (zoom !== undefined) {
|
|
1078
1092
|
// A snap, and the new rest: a relax comes home HERE, not to 1×.
|
|
1079
1093
|
this._targetZoom = this._zoom = this._restZoom = clamp(zoom, this.zoomOpts.min, this.zoomOpts.max);
|
|
@@ -1088,13 +1102,35 @@ export class PlayCanvasSplatViewer {
|
|
|
1088
1102
|
yaw: target ? this._targetYaw : this._yaw,
|
|
1089
1103
|
pitch: target ? this._targetPitch : this._pitch,
|
|
1090
1104
|
zoom: target ? this._targetZoom : this._zoom,
|
|
1105
|
+
roll: target ? this._targetRoll : this._roll,
|
|
1091
1106
|
depthOffset: this._depthOffset,
|
|
1092
1107
|
};
|
|
1093
1108
|
}
|
|
1094
1109
|
|
|
1110
|
+
/**
|
|
1111
|
+
* Move the pose by a delta, eased — SceneViewer.nudgePose's twin, what the hands
|
|
1112
|
+
* (`@displayxr/inline3d/hands`) drive. Degrees to add, zoom a ratio to multiply (within
|
|
1113
|
+
* `zoomOpts`, and it becomes the new REST, as a setPose zoom does, so a relax does not undo a
|
|
1114
|
+
* two-hand zoom). Roll (the two-hand twist) is degrees about the view axis, as on SceneViewer.
|
|
1115
|
+
* Counts as interaction for the idle turntable and ends an orbit relax, like a drag.
|
|
1116
|
+
*/
|
|
1117
|
+
nudgePose({ yaw = 0, pitch = 0, zoom = 1, roll = 0 } = {}) {
|
|
1118
|
+
if (this.pageCamera) throw new Error(PAGE_POSE_ERROR);
|
|
1119
|
+
if (yaw || pitch) this._orbitMode = null;
|
|
1120
|
+
if (yaw) this._targetYaw += yaw;
|
|
1121
|
+
if (pitch) this._targetPitch = clamp(this._targetPitch + pitch, this.pitchLimit[0], this.pitchLimit[1]);
|
|
1122
|
+
if (roll) this._targetRoll += roll;
|
|
1123
|
+
if (zoom && zoom !== 1) {
|
|
1124
|
+
const from = this._zoomMode === 'rest' ? this._zoom : this._targetZoom;
|
|
1125
|
+
this._targetZoom = this._restZoom = clamp(from * zoom, this.zoomOpts.min, this.zoomOpts.max);
|
|
1126
|
+
this._zoomMode = null;
|
|
1127
|
+
}
|
|
1128
|
+
this._lastInput = now();
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1095
1131
|
resetPose() {
|
|
1096
1132
|
if (this.pageCamera) throw new Error(PAGE_POSE_ERROR);
|
|
1097
|
-
this.setPose({ yaw: 0, pitch: 0, zoom: 1, depthOffset: 0 });
|
|
1133
|
+
this.setPose({ yaw: 0, pitch: 0, zoom: 1, roll: 0, depthOffset: 0 });
|
|
1098
1134
|
this._lastInput = now();
|
|
1099
1135
|
}
|
|
1100
1136
|
|
|
@@ -1103,12 +1139,19 @@ export class PlayCanvasSplatViewer {
|
|
|
1103
1139
|
const s = this._fitScale * this._zoom;
|
|
1104
1140
|
const [hx, hy, hz] = this._subjectHalf;
|
|
1105
1141
|
// SceneViewer's exact arithmetic (degrees → radians as `(d * Math.PI) / 180`), so the two
|
|
1106
|
-
// viewers report bit-identical boxes (pinned by the behavioural-trace test).
|
|
1142
|
+
// viewers report bit-identical boxes (pinned by the behavioural-trace test). Rows of
|
|
1143
|
+
// R = Rz(roll) · Rx(pitch) · Ry(yaw), |row| · half = the AABB half-extent along that axis;
|
|
1144
|
+
// at roll 0 the first two rows are the old Rx · Ry rows exactly.
|
|
1107
1145
|
const p = (this._pitch * Math.PI) / 180;
|
|
1108
1146
|
const y = (this._yaw * Math.PI) / 180;
|
|
1147
|
+
const r = (this._roll * Math.PI) / 180;
|
|
1109
1148
|
const cp = Math.cos(p), sp = Math.sin(p), cy = Math.cos(y), sy = Math.sin(y);
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1149
|
+
const cr = Math.cos(r), sr = Math.sin(r);
|
|
1150
|
+
// Rx . Ry rows
|
|
1151
|
+
const a0 = cy, a1 = 0, a2 = sy;
|
|
1152
|
+
const b0 = sp * sy, b1 = cp, b2 = -sp * cy;
|
|
1153
|
+
const ex = s * (Math.abs(cr * a0 - sr * b0) * hx + Math.abs(cr * a1 - sr * b1) * hy + Math.abs(cr * a2 - sr * b2) * hz);
|
|
1154
|
+
const ey = s * (Math.abs(sr * a0 + cr * b0) * hx + Math.abs(sr * a1 + cr * b1) * hy + Math.abs(sr * a2 + cr * b2) * hz);
|
|
1112
1155
|
const ez = s * (Math.abs(cp * sy) * hx + Math.abs(sp) * hy + Math.abs(cp * cy) * hz);
|
|
1113
1156
|
const cz = this._depthOffset;
|
|
1114
1157
|
return {
|
|
@@ -2472,6 +2515,8 @@ export class PlayCanvasSplatViewer {
|
|
|
2472
2515
|
const ko = tau > 0 ? (dt > 0 ? 1 - Math.exp(-dt / tau) : 0) : k;
|
|
2473
2516
|
this._yaw += (this._targetYaw - this._yaw) * ko;
|
|
2474
2517
|
this._pitch += (this._targetPitch - this._pitch) * ko;
|
|
2518
|
+
// Roll has no drag / relax phase (only the hands and setPose write it): the ordinary damping.
|
|
2519
|
+
if (this._roll !== this._targetRoll) this._roll += (this._targetRoll - this._roll) * k;
|
|
2475
2520
|
if (
|
|
2476
2521
|
this._orbitMode === 'rest' &&
|
|
2477
2522
|
Math.abs(this._targetYaw - this._yaw) < 0.01 &&
|
|
@@ -2608,6 +2653,7 @@ export class PlayCanvasSplatViewer {
|
|
|
2608
2653
|
return {
|
|
2609
2654
|
yaw: this._yaw,
|
|
2610
2655
|
pitch: this._pitch,
|
|
2656
|
+
roll: this._roll,
|
|
2611
2657
|
scale: this._fitScale * this._zoom,
|
|
2612
2658
|
focus: [f.x, f.y, f.z],
|
|
2613
2659
|
orbitCentre: [o.x, o.y, o.z],
|
|
@@ -4922,6 +4968,7 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
|
|
|
4922
4968
|
mono: { fov: v.mono.fov, near: v.mono.near, far: v.mono.far, pose: Float64Array.from(v.mono.pose), capture: v.mono.capture },
|
|
4923
4969
|
pose: {
|
|
4924
4970
|
_yaw: v._yaw, _pitch: v._pitch, _targetYaw: v._targetYaw, _targetPitch: v._targetPitch,
|
|
4971
|
+
_roll: v._roll, _targetRoll: v._targetRoll,
|
|
4925
4972
|
_zoom: v._zoom, _targetZoom: v._targetZoom, _depthOffset: v._depthOffset,
|
|
4926
4973
|
_focusSettled: v._focusSettled, _focusRecentres: v._focusRecentres, _orbitMode: v._orbitMode, _lastInput: v._lastInput,
|
|
4927
4974
|
},
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// test/splat-playcanvas.test.mjs (values, and a behavioural trace of both viewers side by side).
|
|
14
14
|
|
|
15
15
|
import { comfortDepthFactor, isComfortZone, DEPTH_DEFAULT_VIEWER_M } from './inline3d-depth-budget.js';
|
|
16
|
+
import { attachHandsOption } from './inline3d-hands.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Double-attach detection around a scene subpath's public entry (`addSplat`, `addModel`).
|
|
@@ -25,9 +26,19 @@ import { comfortDepthFactor, isComfortZone, DEPTH_DEFAULT_VIEWER_M } from './inl
|
|
|
25
26
|
*/
|
|
26
27
|
export function guardedAttach(wall, canvas, method, opts, attach) {
|
|
27
28
|
const g = wall && typeof wall._attachGuard === 'function' ? wall._attachGuard(canvas, method, opts) : null;
|
|
28
|
-
if (!g) return attach(opts);
|
|
29
|
+
if (!g) return withHands(wall, attach(opts), opts);
|
|
29
30
|
const out = attach(g.opts);
|
|
30
31
|
g.own(out);
|
|
32
|
+
return withHands(wall, out, opts);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The `hands` option (`./inline3d-hands.js`): pinch to orbit, two pinches to zoom. Wired here so
|
|
37
|
+
* every addModel / addSplat entry, on either engine, gets it from one place, after the core has
|
|
38
|
+
* claimed the handle (the hands' stop rides the same `remove` accessor).
|
|
39
|
+
*/
|
|
40
|
+
function withHands(wall, out, opts) {
|
|
41
|
+
if (opts && opts.hands) attachHandsOption(wall, out, opts);
|
|
31
42
|
return out;
|
|
32
43
|
}
|
|
33
44
|
|
package/js/inline3d-viewer.js
CHANGED
|
@@ -379,6 +379,10 @@ export class SceneViewer {
|
|
|
379
379
|
this._pitch = 0;
|
|
380
380
|
this._targetYaw = 0;
|
|
381
381
|
this._targetPitch = 0;
|
|
382
|
+
// Roll about the VIEW axis, degrees. Only the two-hand gesture (./inline3d-hands.js) and an
|
|
383
|
+
// explicit setPose({ roll }) ever write it, so every existing page is bit-identical at 0.
|
|
384
|
+
this._roll = 0;
|
|
385
|
+
this._targetRoll = 0;
|
|
382
386
|
this._lastInput = now(); // so the turntable waits out the load-in rather than starting mid-pop
|
|
383
387
|
this._lastTick = 0;
|
|
384
388
|
this._monoRaf = 0;
|
|
@@ -524,12 +528,13 @@ export class SceneViewer {
|
|
|
524
528
|
* This SNAPS — it writes the eased value and its target together. The easing in _tick exists
|
|
525
529
|
* for input, not for programmatic placement.
|
|
526
530
|
*/
|
|
527
|
-
setPose({ yaw, pitch, zoom, depthOffset } = {}) {
|
|
531
|
+
setPose({ yaw, pitch, zoom, roll, depthOffset } = {}) {
|
|
528
532
|
if (yaw !== undefined) this._targetYaw = this._yaw = yaw;
|
|
529
533
|
if (pitch !== undefined) {
|
|
530
534
|
this._targetPitch = this._pitch = clamp(pitch, this.pitchLimit[0], this.pitchLimit[1]);
|
|
531
535
|
}
|
|
532
536
|
if (zoom !== undefined) this._targetZoom = this._zoom = clamp(zoom, ZOOM_MIN, ZOOM_MAX);
|
|
537
|
+
if (roll !== undefined) this._targetRoll = this._roll = roll;
|
|
533
538
|
if (depthOffset !== undefined) this._depthOffset = finite(depthOffset, this._depthOffset);
|
|
534
539
|
this._applyTransform();
|
|
535
540
|
}
|
|
@@ -553,6 +558,7 @@ export class SceneViewer {
|
|
|
553
558
|
yaw: target ? this._targetYaw : this._yaw,
|
|
554
559
|
pitch: target ? this._targetPitch : this._pitch,
|
|
555
560
|
zoom: target ? this._targetZoom : this._zoom,
|
|
561
|
+
roll: target ? this._targetRoll : this._roll,
|
|
556
562
|
depthOffset: this._depthOffset,
|
|
557
563
|
};
|
|
558
564
|
}
|
|
@@ -588,15 +594,22 @@ export class SceneViewer {
|
|
|
588
594
|
const [hx, hy, hz] = this._subjectHalf;
|
|
589
595
|
const p = (this._pitch * Math.PI) / 180;
|
|
590
596
|
const y = (this._yaw * Math.PI) / 180;
|
|
591
|
-
|
|
592
|
-
//
|
|
593
|
-
//
|
|
597
|
+
const r = (this._roll * Math.PI) / 180;
|
|
598
|
+
// Rows of R = Rz(roll) . Rx(pitch) . Ry(yaw) — the same product _applyTransform builds, and
|
|
599
|
+
// the same order, which is the part that matters (see its comment on why pitch is outside
|
|
600
|
+
// yaw, and roll outside both). |row| . half gives the AABB half-extent along that world axis.
|
|
601
|
+
// At roll 0 the first two rows reduce to the old Rx . Ry rows exactly.
|
|
594
602
|
const cp = Math.cos(p);
|
|
595
603
|
const sp = Math.sin(p);
|
|
596
604
|
const cy = Math.cos(y);
|
|
597
605
|
const sy = Math.sin(y);
|
|
598
|
-
const
|
|
599
|
-
const
|
|
606
|
+
const cr = Math.cos(r);
|
|
607
|
+
const sr = Math.sin(r);
|
|
608
|
+
// Rx . Ry rows
|
|
609
|
+
const a0 = cy, a1 = 0, a2 = sy;
|
|
610
|
+
const b0 = sp * sy, b1 = cp, b2 = -sp * cy;
|
|
611
|
+
const ex = s * (Math.abs(cr * a0 - sr * b0) * hx + Math.abs(cr * a1 - sr * b1) * hy + Math.abs(cr * a2 - sr * b2) * hz);
|
|
612
|
+
const ey = s * (Math.abs(sr * a0 + cr * b0) * hx + Math.abs(sr * a1 + cr * b1) * hy + Math.abs(sr * a2 + cr * b2) * hz);
|
|
600
613
|
const ez = s * (Math.abs(cp * sy) * hx + Math.abs(sp) * hy + Math.abs(cp * cy) * hz);
|
|
601
614
|
const cz = this._depthOffset;
|
|
602
615
|
return {
|
|
@@ -675,8 +688,22 @@ export class SceneViewer {
|
|
|
675
688
|
}
|
|
676
689
|
|
|
677
690
|
/** Return to the framed default pose, depth slide included. */
|
|
691
|
+
/**
|
|
692
|
+
* Move the pose by a delta, EASED — what an input device does, as opposed to `setPose`'s snap.
|
|
693
|
+
* Angles in degrees to add, `zoom` a ratio to multiply; pitch is clamped, zoom bounded. Counts
|
|
694
|
+
* as interaction for the idle turntable, exactly like a pointer drag. The hands
|
|
695
|
+
* (`@displayxr/inline3d/hands`) drive the viewer through this; a page may too.
|
|
696
|
+
*/
|
|
697
|
+
nudgePose({ yaw = 0, pitch = 0, zoom = 1, roll = 0 } = {}) {
|
|
698
|
+
if (yaw) this._targetYaw += yaw;
|
|
699
|
+
if (pitch) this._targetPitch = clamp(this._targetPitch + pitch, this.pitchLimit[0], this.pitchLimit[1]);
|
|
700
|
+
if (zoom && zoom !== 1) this._targetZoom = clamp(this._targetZoom * zoom, ZOOM_MIN, ZOOM_MAX);
|
|
701
|
+
if (roll) this._targetRoll += roll;
|
|
702
|
+
this._lastInput = now();
|
|
703
|
+
}
|
|
704
|
+
|
|
678
705
|
resetPose() {
|
|
679
|
-
this.setPose({ yaw: 0, pitch: 0, zoom: 1, depthOffset: 0 });
|
|
706
|
+
this.setPose({ yaw: 0, pitch: 0, zoom: 1, roll: 0, depthOffset: 0 });
|
|
680
707
|
this._lastInput = now();
|
|
681
708
|
}
|
|
682
709
|
|
|
@@ -1035,7 +1062,10 @@ export class SceneViewer {
|
|
|
1035
1062
|
// axis is itself yawed. At yaw 90° that axis has swung onto world Z and dragging up/down
|
|
1036
1063
|
// rolls the subject instead of tilting it. Correct head-on, wrong the moment you turn it —
|
|
1037
1064
|
// which is why it survived review and only showed up when two controls were combined.
|
|
1038
|
-
|
|
1065
|
+
//
|
|
1066
|
+
// Roll (the two-hand twist) is about the VIEW axis, so it goes OUTSIDE both: 'ZXY' == R =
|
|
1067
|
+
// Rz(roll) · Rx(pitch) · Ry(yaw). At roll 0 that is the 'XYZ' product above, bit for bit.
|
|
1068
|
+
this._pivot.rotation.set((this._pitch * Math.PI) / 180, (this._yaw * Math.PI) / 180, (this._roll * Math.PI) / 180, 'ZXY');
|
|
1039
1069
|
}
|
|
1040
1070
|
|
|
1041
1071
|
/**
|
|
@@ -1136,6 +1166,7 @@ export class SceneViewer {
|
|
|
1136
1166
|
const k = dt > 0 ? 1 - Math.pow(DAMP_BASE, dt) : 1;
|
|
1137
1167
|
this._yaw += (this._targetYaw - this._yaw) * k;
|
|
1138
1168
|
this._pitch += (this._targetPitch - this._pitch) * k;
|
|
1169
|
+
if (this._roll !== this._targetRoll) this._roll += (this._targetRoll - this._roll) * k;
|
|
1139
1170
|
// Zoom eases on the same curve. Multiplicatively, because zoom is a ratio: approaching 2x
|
|
1140
1171
|
// linearly spends most of its time near the start and then lurches, while a ratio approach
|
|
1141
1172
|
// covers equal PERCEPTUAL steps per frame.
|
package/model.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type { CursorOptions } from './cursor-depth.js';
|
|
5
5
|
import type { SceneViewer, SubjectBounds, OrbitPose } from './viewer.js';
|
|
6
|
+
import type { HandControlOptions, HandControls } from './hands.js';
|
|
6
7
|
import type { FirstWovenResult, RectCoverOption, ViewerEaseOption } from './index.js';
|
|
7
8
|
import type { PlayCanvasSplatViewer, SplatCameraPose, SplatFrameInfo } from './splat.js';
|
|
8
9
|
|
|
@@ -25,6 +26,13 @@ export interface ModelOptions {
|
|
|
25
26
|
/** Degrees/second of turntable once idle (default 8). */
|
|
26
27
|
idleSpin?: number;
|
|
27
28
|
orbit?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Tracked-hand controls (`@displayxr/inline3d/hands`): one pinch dragged turns the model, two
|
|
31
|
+
* pinches spread / dragged / twisted zoom / turn / roll it (either engine). Needs the
|
|
32
|
+
* DisplayXR Browser 1.8.0+ with a hand provider; inert elsewhere, and pointer / wheel keep
|
|
33
|
+
* working alongside. `true` or the controller options; exposed as `handle.hands`. Default off.
|
|
34
|
+
*/
|
|
35
|
+
hands?: boolean | HandControlOptions;
|
|
28
36
|
fit?: 'contain' | 'height' | 'cover' | 'none';
|
|
29
37
|
/** Fraction of the tile the subject may occupy (default 0.8) — width AND height. */
|
|
30
38
|
margin?: number;
|
|
@@ -212,6 +220,8 @@ export interface ModelHandle {
|
|
|
212
220
|
* returns (1.11 returned it synchronously; `@displayxr/inline3d/model/three` still does).
|
|
213
221
|
*/
|
|
214
222
|
readonly viewer: SceneViewer | PlayCanvasSplatViewer | null;
|
|
223
|
+
/** The hand controls, when the `hands` option asked for them (`stop()`, `state.gate`, `controller`). */
|
|
224
|
+
readonly hands?: HandControls;
|
|
215
225
|
/**
|
|
216
226
|
* The loaded glTF root — a three `Object3D`, or on PlayCanvas the render `pc.Entity`
|
|
217
227
|
* (`instantiateRenderEntity()`). Null until `ready` resolves.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@displayxr/inline3d",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.50.0",
|
|
4
4
|
"description": "Turn any HTML <canvas> into a 3D window on a spatial display, inside an ordinary web page, for the DisplayXR Browser. Dependency-free; progressive enhancement (falls back to plain 2D everywhere else).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"types": "./viewer.d.ts",
|
|
28
28
|
"import": "./js/inline3d-viewer.js"
|
|
29
29
|
},
|
|
30
|
+
"./hands": {
|
|
31
|
+
"types": "./hands.d.ts",
|
|
32
|
+
"import": "./js/inline3d-hands.js"
|
|
33
|
+
},
|
|
30
34
|
"./splat": {
|
|
31
35
|
"types": "./splat.d.ts",
|
|
32
36
|
"import": "./js/inline3d-splat.js"
|
|
@@ -91,6 +95,7 @@
|
|
|
91
95
|
"js/inline3d-cursor-shape.js",
|
|
92
96
|
"js/vendor/",
|
|
93
97
|
"js/inline3d-viewer.js",
|
|
98
|
+
"js/inline3d-hands.js",
|
|
94
99
|
"js/inline3d-splat.js",
|
|
95
100
|
"js/inline3d-splat-pc-entry.js",
|
|
96
101
|
"js/inline3d-splat-deferred.js",
|
|
@@ -133,6 +138,7 @@
|
|
|
133
138
|
"depth.d.ts",
|
|
134
139
|
"cursor-depth.d.ts",
|
|
135
140
|
"viewer.d.ts",
|
|
141
|
+
"hands.d.ts",
|
|
136
142
|
"splat.d.ts",
|
|
137
143
|
"splat-playcanvas.d.ts",
|
|
138
144
|
"model.d.ts",
|
package/splat.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type { CursorOptions } from './cursor-depth.js';
|
|
5
5
|
import type { SceneViewer, SubjectBounds, OrbitPose } from './viewer.js';
|
|
6
|
+
import type { HandControlOptions, HandControls } from './hands.js';
|
|
6
7
|
import type { FirstWovenResult, RectCoverOption, ViewerEaseOption, DisplayMetrics } from './index.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -78,6 +79,12 @@ export interface PlayCanvasSplatViewer {
|
|
|
78
79
|
fitTo(center: number[], extent: number[]): void;
|
|
79
80
|
setPose(pose?: OrbitPose): void;
|
|
80
81
|
getPose(opts?: { target?: boolean }): Required<OrbitPose>;
|
|
82
|
+
/**
|
|
83
|
+
* Move the pose by a delta, eased (SceneViewer.nudgePose's twin): degrees to add (`roll` about
|
|
84
|
+
* the view axis), zoom a ratio to multiply within `zoomOpts` (it becomes the new rest, as a
|
|
85
|
+
* `setPose` zoom does). Counts as interaction for the idle turntable and ends an orbit relax.
|
|
86
|
+
*/
|
|
87
|
+
nudgePose(delta?: { yaw?: number; pitch?: number; zoom?: number; roll?: number }): void;
|
|
81
88
|
resetPose(): void;
|
|
82
89
|
getSubjectBounds(): SubjectBounds & { front: number; back: number; scale: number };
|
|
83
90
|
setFocus(point: number[] | { x: number; y: number; z: number } | null, opts?: { snap?: boolean; recentre?: boolean }): PlayCanvasSplatViewer;
|
|
@@ -391,6 +398,13 @@ export interface SplatOptions {
|
|
|
391
398
|
* it is still the cumulative turntable (a full-width drag = 180°).
|
|
392
399
|
*/
|
|
393
400
|
orbit?: boolean;
|
|
401
|
+
/**
|
|
402
|
+
* Tracked-hand controls (`@displayxr/inline3d/hands`): one pinch dragged turns the splat, two
|
|
403
|
+
* pinches spread / dragged zoom / turn it (and twist rolls it on Spark). Needs the DisplayXR
|
|
404
|
+
* Browser 1.8.0+ with a hand provider; inert elsewhere. `true` or the controller options;
|
|
405
|
+
* exposed as `handle.hands`. Default off.
|
|
406
|
+
*/
|
|
407
|
+
hands?: boolean | HandControlOptions;
|
|
394
408
|
/** PlayCanvas: the largest drag tilt, degrees, either axis (default 15). */
|
|
395
409
|
orbitMaxDeg?: number;
|
|
396
410
|
/** PlayCanvas: easing time constants, seconds — `drag` while held (0.2), `rest` after release (0.6). */
|
|
@@ -1007,6 +1021,8 @@ export interface SplatHandle {
|
|
|
1007
1021
|
* there until the backend module has loaded — one module fetch after addSplat returns).
|
|
1008
1022
|
*/
|
|
1009
1023
|
readonly viewer: SceneViewer | PlayCanvasSplatViewer;
|
|
1024
|
+
/** The hand controls, when the `hands` option asked for them (`stop()`, `state.gate`, `controller`). */
|
|
1025
|
+
readonly hands?: HandControls;
|
|
1010
1026
|
/**
|
|
1011
1027
|
* Spark's SplatMesh; on `engine: 'playcanvas'` a `{ numSplats, entity, asset, resource }`
|
|
1012
1028
|
* record of the engine objects. Null until the asset is loaded.
|
package/viewer.d.ts
CHANGED
|
@@ -66,6 +66,12 @@ export interface OrbitPose {
|
|
|
66
66
|
pitch?: number;
|
|
67
67
|
/** Multiplier on the fit scale. */
|
|
68
68
|
zoom?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Degrees about the VIEW axis (counter-clockwise as the viewer sees it), applied outside yaw and
|
|
71
|
+
* pitch. Written only by the two-hand gesture (`@displayxr/inline3d/hands`) or an explicit call;
|
|
72
|
+
* 0 for every page that never touches it. Both viewers (three.js and PlayCanvas) carry it.
|
|
73
|
+
*/
|
|
74
|
+
roll?: number;
|
|
69
75
|
/** Display metres along the depth axis, `+` toward the viewer (out of the glass). */
|
|
70
76
|
depthOffset?: number;
|
|
71
77
|
}
|
|
@@ -136,6 +142,12 @@ export declare class SceneViewer {
|
|
|
136
142
|
|
|
137
143
|
/** Snap the pose. Writes the eased value and its target together. */
|
|
138
144
|
setPose(pose?: OrbitPose): void;
|
|
145
|
+
/**
|
|
146
|
+
* Move the pose by a delta, EASED — what an input device does. Degrees to add, `zoom` a ratio
|
|
147
|
+
* to multiply (pitch clamped, zoom bounded). Counts as interaction for the idle turntable, like
|
|
148
|
+
* a pointer drag. `@displayxr/inline3d/hands` drives the viewer through this.
|
|
149
|
+
*/
|
|
150
|
+
nudgePose(delta?: { yaw?: number; pitch?: number; zoom?: number; roll?: number }): void;
|
|
139
151
|
/**
|
|
140
152
|
* Point the viewer at something: the orbit centre, the pivot plane and (on a camera rig) the
|
|
141
153
|
* convergence distance are ONE point, and this is it. `recentre: true` (a display rig) brings
|