@preference-sl/pref-viewer 2.18.0-beta.1 → 3.0.0-beta.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.
Files changed (35) hide show
  1. package/README.md +267 -31
  2. package/index.d.ts +75 -6
  3. package/package.json +8 -2
  4. package/src/babylonjs-animation-controller.js +18 -10
  5. package/src/babylonjs-animation-opening-menu.js +80 -14
  6. package/src/babylonjs-animation-opening.js +3 -3
  7. package/src/babylonjs-ar-floor-bounds.js +169 -0
  8. package/src/babylonjs-ar-glass.js +128 -0
  9. package/src/babylonjs-ar-placement-feedback.js +449 -0
  10. package/src/babylonjs-ar-placement.js +1038 -0
  11. package/src/babylonjs-camera-wheel-motion.js +35 -2
  12. package/src/babylonjs-controller.js +507 -125
  13. package/src/babylonjs-orbital-pointers-input.js +186 -0
  14. package/src/{render-math.js → babylonjs-render-math.js} +11 -11
  15. package/src/babylonjs-selection-controller.js +4 -4
  16. package/src/babylonjs-view-cube-canvas-controller.js +1287 -0
  17. package/src/babylonjs-view-cube-canvas-orbit-math.js +220 -0
  18. package/src/babylonjs-view-cube-canvas-renderer.js +775 -0
  19. package/src/babylonjs-view-cube-controller.js +74 -1103
  20. package/src/babylonjs-view-cube-svg-controller.js +1012 -0
  21. package/src/file-storage.js +7 -4
  22. package/src/gltf-resolver.js +13 -8
  23. package/src/localization/translations.js +12 -0
  24. package/src/panzoom-controller.js +4 -4
  25. package/src/pref-viewer-2d.js +2 -2
  26. package/src/pref-viewer-3d.js +55 -9
  27. package/src/pref-viewer-controls-presentation.js +292 -0
  28. package/src/pref-viewer-dialog.js +86 -7
  29. package/src/pref-viewer-menu-3d.js +134 -17
  30. package/src/pref-viewer-style-controller.js +320 -0
  31. package/src/pref-viewer.js +133 -109
  32. package/src/quick-look-action.js +280 -0
  33. package/src/styles.js +702 -98
  34. package/src/babylonjs-view-cube-renderer.js +0 -548
  35. package/src/view-cube-orbit-math.js +0 -199
@@ -0,0 +1,186 @@
1
+ import BabylonJSCameraWheelMotion from "./babylonjs-camera-wheel-motion.js";
2
+ import { ArcRotateCameraPointersInput } from "@babylonjs/core/Cameras/Inputs/arcRotateCameraPointersInput.js";
3
+
4
+ /**
5
+ * Separates orbital pinch and pan using accumulated CSS-pixel motion instead of a per-event threshold.
6
+ * Batches pointer updates until camera input processing so separately delivered finger events are compared together.
7
+ * Defers ambiguous input, locks the recognized mode until release, and delegates movement to Babylon's native maths.
8
+ * Focal-enabled cameras share wheel surface/plane focus at the touch midpoint and coupled inertia.
9
+ * The camera input manager owns attachment/disposal; no independent DOM listeners or timers are installed.
10
+ */
11
+ export default class BabylonJSOrbitalPointersInput extends ArcRotateCameraPointersInput {
12
+ static #thresholds = { pinch: 3, pan: 6, dominance: 0.8, confirmationMs: 32 };
13
+ #origin = null;
14
+ #applied = null;
15
+ #pending = null;
16
+ #mode = null;
17
+ #candidate = null;
18
+ #candidateSince = 0;
19
+
20
+ /**
21
+ * Replaces a detached native pointers input once, preserving its public sensitivity/interaction settings.
22
+ * Uses the same pointers slot and inherited input class name so camera attachment remains native.
23
+ * @param {import("@babylonjs/core").ArcRotateCamera} camera - Camera before controls are attached.
24
+ * @returns {ArcRotateCameraPointersInput|undefined} Installed input, or undefined when no pointers input exists.
25
+ */
26
+ static install(camera) {
27
+ const original = camera.inputs.attached.pointers;
28
+ if (!original || original instanceof BabylonJSOrbitalPointersInput) {
29
+ return original;
30
+ }
31
+ const replacement = new BabylonJSOrbitalPointersInput();
32
+ for (const key of Object.keys(original)) {
33
+ if (key !== "camera" && !key.startsWith("_")) {
34
+ replacement[key] = original[key];
35
+ }
36
+ }
37
+ camera.inputs.remove(original);
38
+ camera.inputs.add(replacement);
39
+ return replacement;
40
+ }
41
+
42
+ /**
43
+ * Captures the latest pair geometry; movement is applied at most once per camera-input frame.
44
+ * Pending gestures do not mutate the camera, and loss of either finger ends the current classification.
45
+ * @param {object|null} pointA - First native pointer position.
46
+ * @param {object|null} pointB - Second native pointer position.
47
+ * @param {number} previousSquared - Previous squared finger separation in CSS pixels.
48
+ * @param {number} squared - Current squared finger separation; zero marks gesture end.
49
+ * @param {{x:number, y:number}|null} previousCenter - Previous pair midpoint.
50
+ * @param {{x:number, y:number}|null} center - Current pair midpoint in client CSS pixels; null marks gesture end.
51
+ * @returns {void}
52
+ */
53
+ onMultiTouch(pointA, pointB, previousSquared, squared, previousCenter, center) {
54
+ if (!squared || !center) {
55
+ this.#finish();
56
+ return;
57
+ }
58
+ if (!Number.isFinite(squared) || squared < 0 || !Number.isFinite(center.x + center.y)) {
59
+ return;
60
+ }
61
+ if (!this.#origin) {
62
+ this.#origin = {
63
+ squared: previousSquared > 0 ? previousSquared : squared,
64
+ center: { ...(previousCenter ?? center) },
65
+ };
66
+ this.#applied = this.#origin;
67
+ }
68
+ this.#pending = { squared, center: { ...center } };
69
+ }
70
+
71
+ /**
72
+ * Classifies accumulated movement after both pointer updates can arrive, then applies only the selected mode.
73
+ * A short confirmation window avoids interpreting the first finger's asynchronous pan event as a pinch.
74
+ * Recognizing a new pan cancels prior coupled focal inertia without changing the zoom preference.
75
+ * @returns {void}
76
+ */
77
+ checkInputs() {
78
+ if (!this.#pending) {
79
+ return;
80
+ }
81
+ if (!this.#mode) {
82
+ const pinch = Math.abs(Math.sqrt(this.#pending.squared) - Math.sqrt(this.#origin.squared));
83
+ const pan = Math.hypot(this.#pending.center.x - this.#origin.center.x,
84
+ this.#pending.center.y - this.#origin.center.y);
85
+ const thresholds = BabylonJSOrbitalPointersInput.#thresholds;
86
+ let candidate = null;
87
+ if (this.pinchZoom && pinch >= thresholds.pinch && pinch >= pan * thresholds.dominance) {
88
+ candidate = "zoom";
89
+ } else if (this.multiTouchPanning && pan >= thresholds.pan && pinch < pan * 0.35) {
90
+ candidate = "pan";
91
+ }
92
+ if (candidate !== this.#candidate) {
93
+ this.#candidate = candidate;
94
+ this.#candidateSince = Date.now();
95
+ }
96
+ if (!candidate || Date.now() - this.#candidateSince < thresholds.confirmationMs) {
97
+ return;
98
+ }
99
+ this.#mode = candidate;
100
+ if (candidate === "zoom") {
101
+ this.camera.inertialPanningX = this.camera.inertialPanningY = 0;
102
+ this.camera.movement.panAccumulatedPixels.setAll(0);
103
+ } else {
104
+ BabylonJSCameraWheelMotion.cancel(this.camera);
105
+ this.camera.inertialRadiusOffset = 0;
106
+ this.camera.movement.zoomAccumulatedPixels = 0;
107
+ }
108
+ }
109
+ this.#applyPending();
110
+ }
111
+
112
+ /**
113
+ * Delegates pair motion to native pan or proportional pinch. Focal-enabled pinch transfers only its new
114
+ * radial increment to wheel-owned coupled inertia at the current client midpoint, avoiding duplicate zoom.
115
+ * @private
116
+ * @returns {void}
117
+ */
118
+ #applyPending() {
119
+ if (!this.#pending || !this.#mode) {
120
+ return;
121
+ }
122
+ if (this.#mode === "zoom") {
123
+ const accumulated = this.camera.movement.zoomAccumulatedPixels;
124
+ this._computePinchZoom(this.#applied.squared, this.#pending.squared);
125
+ const delta = this.camera.movement.zoomAccumulatedPixels - accumulated;
126
+ if (BabylonJSCameraWheelMotion.zoomAtClientPoint(this.camera, delta, this.#pending.center)) {
127
+ this.camera.movement.zoomAccumulatedPixels = accumulated;
128
+ }
129
+ } else {
130
+ this._computeMultiTouchPanning(this.#applied.center, this.#pending.center);
131
+ }
132
+ this.#applied = this.#pending;
133
+ this.#pending = null;
134
+ }
135
+
136
+ /**
137
+ * Flushes recognized motion and resets the gesture; unrecognized motion is discarded.
138
+ * @private
139
+ * @returns {void}
140
+ */
141
+ #finish() {
142
+ this.#applyPending();
143
+ this.#reset();
144
+ }
145
+
146
+ /**
147
+ * Clears pair state without moving the camera or changing input settings.
148
+ * @private
149
+ * @returns {void}
150
+ */
151
+ #reset() {
152
+ this.#origin = this.#applied = this.#pending = null;
153
+ this.#mode = this.#candidate = null;
154
+ this.#candidateSince = 0;
155
+ }
156
+
157
+ /**
158
+ * Ends pair ownership when a finger is lifted and preserves native mouse/pointer cleanup.
159
+ * @param {PointerEvent} event - Released pointer event.
160
+ * @returns {void}
161
+ */
162
+ onButtonUp(event) {
163
+ this.#finish();
164
+ super.onButtonUp(event);
165
+ }
166
+
167
+ /**
168
+ * Discards pending gesture motion and cancels coupled focal inertia on focus loss before native pointer cleanup.
169
+ * @returns {void}
170
+ */
171
+ onLostFocus() {
172
+ BabylonJSCameraWheelMotion.cancel(this.camera);
173
+ this.#reset();
174
+ super.onLostFocus();
175
+ }
176
+
177
+ /**
178
+ * Discards pending pair motion and coupled focal inertia before the input manager detaches native event listeners.
179
+ * @returns {void}
180
+ */
181
+ detachControl() {
182
+ BabylonJSCameraWheelMotion.cancel(this.camera);
183
+ this.#reset();
184
+ super.detachControl();
185
+ }
186
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Pure lighting and hardware-scaling calculations without Babylon.js dependencies.
3
- * @class RenderMath
3
+ * @class BabylonJSRenderMath
4
4
  */
5
- export default class RenderMath {
5
+ export default class BabylonJSRenderMath {
6
6
  /**
7
7
  * Clamps a number to the [0, 1] range.
8
8
  * Returns 0.5 for non-finite inputs (NaN, ±Infinity) so callers get a safe mid-point fallback.
@@ -29,7 +29,7 @@ export default class RenderMath {
29
29
  * @returns {number} Interpolated value between `from` and `to`.
30
30
  */
31
31
  static lerp(from, to, amount) {
32
- const t = RenderMath.clamp01(amount);
32
+ const t = BabylonJSRenderMath.clamp01(amount);
33
33
  return from + (to - from) * t;
34
34
  }
35
35
 
@@ -43,7 +43,7 @@ export default class RenderMath {
43
43
  * @returns {number} Bell-curve output in [0, 1].
44
44
  */
45
45
  static #bellCurve(x) {
46
- const clamped = RenderMath.clamp01(x);
46
+ const clamped = BabylonJSRenderMath.clamp01(x);
47
47
  return Math.sin(clamped * Math.PI);
48
48
  }
49
49
 
@@ -111,19 +111,19 @@ export default class RenderMath {
111
111
  * }} Lighting profile snapshot for the requested time.
112
112
  */
113
113
  static getLightingCycleProfile(timeOfDay = 0.5) {
114
- const normalizedTime = RenderMath.clamp01(timeOfDay);
114
+ const normalizedTime = BabylonJSRenderMath.clamp01(timeOfDay);
115
115
  const timeHours = normalizedTime * 24;
116
116
  const sunriseHour = 6.25;
117
117
  const sunsetHour = 20.25;
118
- const daylightProgress = RenderMath.clamp01((timeHours - sunriseHour) / (sunsetHour - sunriseHour));
119
- const dayFactor = timeHours >= sunriseHour && timeHours <= sunsetHour ? Math.pow(RenderMath.#bellCurve(daylightProgress), 0.88) : 0;
118
+ const daylightProgress = BabylonJSRenderMath.clamp01((timeHours - sunriseHour) / (sunsetHour - sunriseHour));
119
+ const dayFactor = timeHours >= sunriseHour && timeHours <= sunsetHour ? Math.pow(BabylonJSRenderMath.#bellCurve(daylightProgress), 0.88) : 0;
120
120
  const nightFactor = 1 - dayFactor;
121
121
  const solarArc = dayFactor > 0 ? Math.pow(dayFactor, 0.72) : 0;
122
- const solarAzimuth = RenderMath.lerp(-Math.PI * 0.82, Math.PI * 0.82, daylightProgress);
123
- const solarElevation = RenderMath.lerp(-0.18, 1.18, solarArc);
122
+ const solarAzimuth = BabylonJSRenderMath.lerp(-Math.PI * 0.82, Math.PI * 0.82, daylightProgress);
123
+ const solarElevation = BabylonJSRenderMath.lerp(-0.18, 1.18, solarArc);
124
124
  const sunPhase = normalizedTime * Math.PI * 2 - Math.PI / 2;
125
- const sunDirection = RenderMath.#normalizeDirection(-Math.cos(sunPhase), -Math.sin(sunPhase), -0.35);
126
- const moonDirection = RenderMath.#normalizeDirection(-sunDirection.x, -sunDirection.y, -sunDirection.z);
125
+ const sunDirection = BabylonJSRenderMath.#normalizeDirection(-Math.cos(sunPhase), -Math.sin(sunPhase), -0.35);
126
+ const moonDirection = BabylonJSRenderMath.#normalizeDirection(-sunDirection.x, -sunDirection.y, -sunDirection.z);
127
127
 
128
128
  return {
129
129
  timeOfDay: normalizedTime,
@@ -58,7 +58,7 @@ export default class BabylonJSSelectionController {
58
58
  * Builds the set of model meshes accepted by picking, including Babylon instances owned by them.
59
59
  * @public
60
60
  * @static
61
- * @param {Array<object>} meshes - Meshes from the model AssetContainer.
61
+ * @param {Array<object>} [meshes=[]] - Meshes from the model AssetContainer.
62
62
  * @returns {Set<object>} Accepted model mesh references.
63
63
  */
64
64
  static getModelMeshSet(meshes = []) {
@@ -79,8 +79,8 @@ export default class BabylonJSSelectionController {
79
79
  * Converts ordered Babylon ray hits into the public semantic mesh-id list.
80
80
  * @public
81
81
  * @static
82
- * @param {Array<object>} picks - Babylon PickingInfo results.
83
- * @param {Array<object>} modelMeshes - Meshes belonging to the model container.
82
+ * @param {Array<object>} [picks=[]] - Babylon PickingInfo results.
83
+ * @param {Array<object>} [modelMeshes=[]] - Meshes belonging to the model container.
84
84
  * @returns {string[]} Stable, distance-ordered, deduplicated semantic ids.
85
85
  */
86
86
  static collectPickedMeshIds(picks = [], modelMeshes = []) {
@@ -117,7 +117,7 @@ export default class BabylonJSSelectionController {
117
117
  /**
118
118
  * Applies a complete selection snapshot to every matching model mesh.
119
119
  * @public
120
- * @param {{selectableIds?:string[],selectedIds?:string[],selectedItems?:Array<{id:string,highlights?:string[]}>}} state - Canonical selection state.
120
+ * @param {{selectableIds?:string[],selectedIds?:string[],selectedItems?:Array<{id:string,highlights?:string[]}>}} [state={}] - Canonical selection state.
121
121
  * @returns {void}
122
122
  */
123
123
  applySelectionState(state = {}) {