@preference-sl/pref-viewer 2.17.2-beta.2 → 2.18.0-beta.1
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/README.md +4 -2
- package/package.json +1 -1
- package/src/babylonjs-camera-wheel-motion.js +71 -0
- package/src/babylonjs-controller.js +38 -0
- package/src/material-utils.js +30 -0
package/README.md
CHANGED
|
@@ -309,7 +309,7 @@ Increasing the factor makes the fitted model smaller. The other axis can have mo
|
|
|
309
309
|
|
|
310
310
|
The default orbital camera starts without `lowerBetaLimit` or `upperBetaLimit` and disables upside-down orbiting, whether or not the cube is enabled. ViewCube navigation keeps the camera up direction on world **+Y**, including top/bottom views and subsequent orbiting. Automatic loading preserves authored camera composition and navigation limits; an explicit unlocked cube action may relax incompatible orbit limits and adjust clipping/radius ranges to reach and fit the selected view. Locked cameras retain their view and limits.
|
|
311
311
|
|
|
312
|
-
Use the mouse wheel over the 3D canvas to zoom an unlocked orbital camera. The default camera uses Babylon's native `zoomToMouseLocation
|
|
312
|
+
Use the mouse wheel over the 3D canvas to zoom an unlocked orbital camera. The default camera uses Babylon's native `zoomToMouseLocation` focused on the visible model surface under the pointer: the picked model point stays at the same screen pixel as you zoom, and the target moves toward it, with the ViewCube shown or hidden. Only visible, enabled, pickable model meshes supply the focus; environment geometry never does. When the pointer is not over the model, the focus falls back to Babylon's native plane through the current target. Selecting any cube preset, including the current view, cancels pending wheel motion and recenters the target on the visible model's world-bounds center using the existing fit.
|
|
313
313
|
|
|
314
314
|
Babylon's native input applies a radius-proportional increment once, so the same wheel input moves less at a smaller radius. Input wakes idle rendering, which continues until native wheel inertia settles. Named imported orbital cameras retain their existing input settings, and Free/Universal wheel navigation remains unchanged. The internal orbital wheel sensitivity (`wheelDeltaPercentage`) is 0.015. Wheel sensitivity has no public attribute or config option.
|
|
315
315
|
|
|
@@ -521,6 +521,8 @@ Render settings are persisted in `localStorage` under the `pref-viewer/render-se
|
|
|
521
521
|
|
|
522
522
|
The showroom daylight background uses a shared linear HDR color calibrated with exposure and ACES tone mapping for the current light studio gray target, `#d9d9d9`, in a scene-less view. Both the static look and time-of-day cycle use this same daylight value; the cycle retains its dark night endpoint. Scene geometry can cover the background. The internal `BabylonJSController.config.look.showroom.backgroundDay` value is not a public background-color option and must be recalibrated if tone mapping or exposure changes.
|
|
523
523
|
|
|
524
|
+
The model's transmissive glass (`KHR_materials_transmission`, as authored by the SDK) also carries a base color alpha of `0.15`, which would multiply its reflection away and leave it flat grey. The viewer sets that alpha to `1` and `metallicF0Factor` to `3` (`BabylonJSController.config.look.glass`, internal like the showroom look) so the environment reflection reads while the glass stays see-through through transmission. Glass in the environment glTF is unaffected: it is simplified to alpha blending instead.
|
|
525
|
+
|
|
524
526
|
## Environment map and scene lights
|
|
525
527
|
|
|
526
528
|
`options.ibl.url` accepts two formats, chosen by the URL's extension (query and hash ignored):
|
|
@@ -612,7 +614,7 @@ Focused regressions for this work live in `tests/babylonjs-selection-controller.
|
|
|
612
614
|
- `src/view-cube-orbit-math.js`: pure helpers for orbit angles, the 26 view directions, drag deltas and the cube size rule, without Babylon or DOM dependencies.
|
|
613
615
|
- `src/babylonjs-camera-framing.js`: stateless `BabylonJSCameraFraming` with static `getModelBounds`, `calculateCameraFit` and `isCameraSupported` utilities. Bounds use only visible, enabled geometry in the supplied model container; fit calculations preserve camera state and projection, use the shared camera margin and return a destination for the caller to apply. Default-camera automatic fitting uses these same utilities, preserving the current orientation and applying the destination immediately.
|
|
614
616
|
- `src/babylonjs-camera-zoom-controller.js`: independent `BabylonJSCameraZoomController` for model-only dynamic radius protection and authored-floor restoration. It works without cube resources.
|
|
615
|
-
- `src/babylonjs-camera-wheel-motion.js`: internal boundary for inspecting and cancelling the coupled native wheel inertia during rendering, geometry protection and camera lifecycle changes. Babylon computes the zoom; this boundary isolates version-sensitive native input state.
|
|
617
|
+
- `src/babylonjs-camera-wheel-motion.js`: internal boundary for inspecting and cancelling the coupled native wheel inertia during rendering, geometry protection and camera lifecycle changes, and for focusing the native mouse-location zoom on the model surface under the pointer. Babylon computes the zoom; this boundary isolates version-sensitive native input state.
|
|
616
618
|
- `src/babylonjs-controller.js`: owns the scene, active camera, containers, native wheel settings and render scheduler; coordinates both controllers.
|
|
617
619
|
- `src/pref-viewer-3d.js` and `src/pref-viewer.js`: bridge lazy visibility, queued config, localization and measured menu layout. The root stores the layout callback in `#handlers` and removes its listener during teardown. Shared styles place dialogs above viewer overlays.
|
|
618
620
|
|
package/package.json
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
* camera offsets. Keep that version-sensitive access here; Babylon still computes all zoom.
|
|
5
5
|
*/
|
|
6
6
|
export default class BabylonJSCameraWheelMotion {
|
|
7
|
+
/**
|
|
8
|
+
* Native focal lookups replaced by surface focus, keyed by the wheel input that owns them.
|
|
9
|
+
* @type {WeakMap<object, {original: (function(): object), hadOwn: boolean, getMeshes: function(): (Array<object>|null|undefined)}>}
|
|
10
|
+
*/
|
|
11
|
+
static #patched = new WeakMap();
|
|
12
|
+
|
|
7
13
|
/**
|
|
8
14
|
* Returns the active native mouse-location input, without creating or replacing inputs.
|
|
9
15
|
* @param {object|null|undefined} camera - Camera to inspect.
|
|
@@ -51,4 +57,69 @@ export default class BabylonJSCameraWheelMotion {
|
|
|
51
57
|
input._inertialPanning?.setAll(0);
|
|
52
58
|
input._hitPlane = null;
|
|
53
59
|
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Makes native mouse-location zoom focus on the model surface under the pointer.
|
|
63
|
+
* Babylon otherwise intersects the pointer ray with a plane through the camera target, so the
|
|
64
|
+
* visible surface drifts. Returning the picked point keeps Babylon's coupled pan an exact homothety
|
|
65
|
+
* around it. Falls back to the native lookup on a miss, without eligible meshes or with a screen offset.
|
|
66
|
+
* Idempotent: repeated calls only replace the mesh provider.
|
|
67
|
+
* @param {object|null|undefined} camera - Camera whose native wheel input is patched.
|
|
68
|
+
* @param {function(): (Array<object>|null|undefined)} getMeshes - Returns the current model meshes; the caller must exclude environment meshes.
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
71
|
+
static focusOnSurface(camera, getMeshes) {
|
|
72
|
+
const input = this.#input(camera);
|
|
73
|
+
if (!input || typeof getMeshes !== "function") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const state = this.#patched.get(input);
|
|
77
|
+
if (state) {
|
|
78
|
+
state.getMeshes = getMeshes;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const entry = { original: input._getPosition, hadOwn: Object.prototype.hasOwnProperty.call(input, "_getPosition"), getMeshes };
|
|
82
|
+
this.#patched.set(input, entry);
|
|
83
|
+
input._getPosition = () => this.#surfacePoint(input, entry) ?? entry.original.call(input);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Restores the native focal lookup replaced by focusOnSurface.
|
|
88
|
+
* @param {object|null|undefined} camera - Camera whose wheel input was patched.
|
|
89
|
+
* @returns {void}
|
|
90
|
+
*/
|
|
91
|
+
static releaseSurfaceFocus(camera) {
|
|
92
|
+
const input = camera?.inputs?.attached?.mousewheel;
|
|
93
|
+
const entry = input && this.#patched.get(input);
|
|
94
|
+
if (!entry) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (entry.hadOwn) {
|
|
98
|
+
input._getPosition = entry.original;
|
|
99
|
+
} else {
|
|
100
|
+
delete input._getPosition;
|
|
101
|
+
}
|
|
102
|
+
this.#patched.delete(input);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Picks the eligible model mesh under the pointer.
|
|
107
|
+
* @param {object} input - Patched native wheel input.
|
|
108
|
+
* @param {{getMeshes: function(): (Array<object>|null|undefined)}} entry - Patch state.
|
|
109
|
+
* @returns {object|null} World-space surface point, or null when native behavior must apply.
|
|
110
|
+
*/
|
|
111
|
+
static #surfacePoint(input, entry) {
|
|
112
|
+
const camera = input.camera;
|
|
113
|
+
const scene = camera?.getScene?.();
|
|
114
|
+
if (!scene || camera.targetScreenOffset?.x || camera.targetScreenOffset?.y) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const meshes = new Set((entry.getMeshes() ?? []).filter((mesh) =>
|
|
118
|
+
!mesh.isDisposed() && mesh.isPickable && mesh.isEnabled() && mesh.isVisible && mesh.visibility > 0));
|
|
119
|
+
if (meshes.size === 0) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const point = scene.pick(scene.pointerX, scene.pointerY, (mesh) => meshes.has(mesh), false, camera)?.pickedPoint;
|
|
123
|
+
return point && Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z) ? point.clone() : null;
|
|
124
|
+
}
|
|
54
125
|
}
|
|
@@ -216,6 +216,14 @@ export default class BabylonJSController {
|
|
|
216
216
|
// Re-solve these if exposure, contrast or the tonemapping curve change.
|
|
217
217
|
backgroundDay: [1.424, 1.424, 1.424],
|
|
218
218
|
},
|
|
219
|
+
// The model's physical glass (KHR_materials_transmission). The SDK also authors it with a
|
|
220
|
+
// baseColor alpha of 0.15, which multiplies the reflection away; the viewer sets the alpha
|
|
221
|
+
// to 1 and raises the dielectric F0 (MaterialUtils.tunePhysicalGlassMaterial) so the
|
|
222
|
+
// environment reads on the glass while transmission keeps it see-through.
|
|
223
|
+
glass: {
|
|
224
|
+
alpha: 1,
|
|
225
|
+
metallicF0Factor: 3,
|
|
226
|
+
},
|
|
219
227
|
},
|
|
220
228
|
pointerPicking: {
|
|
221
229
|
movePickIntervalMs: 50, // Cap expensive scene.pick calls to ~20 Hz while moving the pointer
|
|
@@ -1084,6 +1092,19 @@ export default class BabylonJSController {
|
|
|
1084
1092
|
this.#applyShowroomCameraFraming(this.#camera);
|
|
1085
1093
|
this.#camera.attachControl(this.#canvas, true);
|
|
1086
1094
|
this.#scene.activeCamera = this.#camera;
|
|
1095
|
+
this.#focusWheelZoomOnModel(this.#camera);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Makes native mouse-location wheel zoom focus on the visible model surface under the pointer.
|
|
1100
|
+
* Only meshes of the current visible model container are eligible; environment geometry never is.
|
|
1101
|
+
* Falls back to Babylon's target-plane focus when the pointer is not over the model.
|
|
1102
|
+
* @private
|
|
1103
|
+
* @param {import("@babylonjs/core").ArcRotateCamera} camera - Camera with native mouse-location zoom.
|
|
1104
|
+
* @returns {void}
|
|
1105
|
+
*/
|
|
1106
|
+
#focusWheelZoomOnModel(camera) {
|
|
1107
|
+
BabylonJSCameraWheelMotion.focusOnSurface(camera, () => this.#containers.model?.state?.isVisible ? this.#containers.model.assetContainer?.meshes : null);
|
|
1087
1108
|
}
|
|
1088
1109
|
|
|
1089
1110
|
/**
|
|
@@ -2863,6 +2884,21 @@ export default class BabylonJSController {
|
|
|
2863
2884
|
assetContainer.lights = [];
|
|
2864
2885
|
}
|
|
2865
2886
|
|
|
2887
|
+
/**
|
|
2888
|
+
* Gives the model container's physical glass (KHR_materials_transmission) a visible reflection.
|
|
2889
|
+
* Model container only: environment glass is simplified instead (see
|
|
2890
|
+
* #assetContainer_simplifyTransmissiveMaterials), so it is never tuned here.
|
|
2891
|
+
* @private
|
|
2892
|
+
* @param {AssetContainer} assetContainer - Model container whose glass materials should be tuned.
|
|
2893
|
+
* @returns {void}
|
|
2894
|
+
*/
|
|
2895
|
+
#assetContainer_tuneModelGlass(assetContainer) {
|
|
2896
|
+
const look = BabylonJSController.config.look.glass;
|
|
2897
|
+
assetContainer.materials?.forEach((mat) => {
|
|
2898
|
+
MaterialUtils.tunePhysicalGlassMaterial(mat, look);
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2866
2902
|
/**
|
|
2867
2903
|
* Strips KHR_materials_transmission, KHR_materials_ior and KHR_materials_volume from
|
|
2868
2904
|
* environment-container (scene) materials, converting them to simple alpha blending.
|
|
@@ -3078,6 +3114,7 @@ export default class BabylonJSController {
|
|
|
3078
3114
|
if (container.state.name === "model") {
|
|
3079
3115
|
this.#assetContainer_deleteLights(assetContainer);
|
|
3080
3116
|
this.#assetContainer_stopAnimations(assetContainer);
|
|
3117
|
+
this.#assetContainer_tuneModelGlass(assetContainer);
|
|
3081
3118
|
newModelMetadata = { ...(container.state.update.metadata ?? {}) };
|
|
3082
3119
|
}
|
|
3083
3120
|
if (container.state.name === "model" || container.state.name === "environment") {
|
|
@@ -3472,6 +3509,7 @@ export default class BabylonJSController {
|
|
|
3472
3509
|
async disable() {
|
|
3473
3510
|
BabylonJSCameraWheelMotion.cancel(this.#scene?.activeCamera);
|
|
3474
3511
|
BabylonJSCameraWheelMotion.cancel(this.#camera);
|
|
3512
|
+
BabylonJSCameraWheelMotion.releaseSurfaceFocus(this.#camera);
|
|
3475
3513
|
this.#cameraZoom?.dispose();
|
|
3476
3514
|
this.#cameraZoom = null;
|
|
3477
3515
|
this.#viewCubeSuspended = true;
|
package/src/material-utils.js
CHANGED
|
@@ -47,6 +47,36 @@ export default class MaterialUtils {
|
|
|
47
47
|
return Boolean(mat?.subSurface?.isRefractionEnabled || mat?.subSurface?.isTranslucencyEnabled);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Gives the model's physical glass a visible reflection.
|
|
52
|
+
*
|
|
53
|
+
* The SDK authors window glass with KHR_materials_transmission AND a baseColor alpha of 0.15 in
|
|
54
|
+
* alpha-blend. Babylon's transmission extension turns on `transparencyAsCoverage` for the whole
|
|
55
|
+
* GLB, which makes `useSpecularOverAlpha` / `useRadianceOverAlpha` false, so the ~4 % Fresnel
|
|
56
|
+
* reflection is multiplied by that alpha and the glass reads as flat grey. Setting the alpha to
|
|
57
|
+
* 1, raising the dielectric F0 and re-enabling the over-alpha reflections lets the environment
|
|
58
|
+
* show up on the glass while transmission keeps it see-through.
|
|
59
|
+
*
|
|
60
|
+
* Only physical glass (see `isPrefViewerPhysicalGlassMaterial`) is touched; `transparencyMode`
|
|
61
|
+
* and `subSurface` are left as authored so the glass stays in the transparent pass.
|
|
62
|
+
* @public
|
|
63
|
+
* @static
|
|
64
|
+
* @param {import("@babylonjs/core").PBRMaterial|null|undefined} mat - Material to tune.
|
|
65
|
+
* @param {{ alpha?: number, metallicF0Factor?: number }} [look] - Glass look; defaults to alpha 1 and F0 factor 3.
|
|
66
|
+
* @returns {boolean} True when the material was physical glass and has been changed.
|
|
67
|
+
*/
|
|
68
|
+
static tunePhysicalGlassMaterial(mat, look = {}) {
|
|
69
|
+
if (!MaterialUtils.isPrefViewerPhysicalGlassMaterial(mat)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
mat.alpha = look?.alpha ?? 1;
|
|
74
|
+
mat.metallicF0Factor = look?.metallicF0Factor ?? 3;
|
|
75
|
+
mat.useSpecularOverAlpha = true;
|
|
76
|
+
mat.useRadianceOverAlpha = true;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
50
80
|
/**
|
|
51
81
|
* Returns true when a material should be placed in the transparent rendering group (renderingGroupId=1).
|
|
52
82
|
*
|