@preference-sl/pref-viewer 2.12.0-beta.4 → 2.12.0-beta.5
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/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Color3, HighlightLayer, Mesh, PickingInfo, Scene } from "@babylonjs/core";
|
|
1
|
+
import { AssetContainer, Color3, HighlightLayer, InstancedMesh, Mesh, PickingInfo, Quaternion, Scene, UtilityLayerRenderer, Vector3 } from "@babylonjs/core";
|
|
2
2
|
import { PrefViewerColors } from "./styles.js";
|
|
3
3
|
import OpeningAnimation from "./babylonjs-animation-opening.js";
|
|
4
4
|
|
|
@@ -18,27 +18,36 @@ import OpeningAnimation from "./babylonjs-animation-opening.js";
|
|
|
18
18
|
*
|
|
19
19
|
* Public Methods:
|
|
20
20
|
* - dispose(): Disposes all resources managed by the animation controller.
|
|
21
|
-
* -
|
|
21
|
+
* - highlightMeshes(pickingInfo): Highlights meshes that are children of an animated node when hovered.
|
|
22
22
|
* - hideMenu(): Hides and disposes the animation control menu if it exists.
|
|
23
23
|
* - showMenu(pickingInfo): Displays the animation control menu for the animated node under the pointer.
|
|
24
24
|
*
|
|
25
25
|
* @class
|
|
26
26
|
*/
|
|
27
27
|
export default class BabylonJSAnimationController {
|
|
28
|
-
#scene = null;
|
|
29
28
|
#canvas = null;
|
|
29
|
+
#scene = null;
|
|
30
|
+
#assetContainer = null;
|
|
31
|
+
|
|
30
32
|
#animatedNodes = [];
|
|
31
|
-
#highlightLayer = null;
|
|
32
|
-
#highlightColor = Color3.FromHexString(PrefViewerColors.primary);
|
|
33
33
|
#openingAnimations = [];
|
|
34
34
|
|
|
35
|
+
#highlightColor = Color3.FromHexString(PrefViewerColors.primary);
|
|
36
|
+
#highlightLayer = null;
|
|
37
|
+
#overlayLayer = null;
|
|
38
|
+
#useHighlightLayer = false; // Set to true to use HighlightLayer (better performance) and false to use overlay meshes (UtilityLayerRenderer - always on top)
|
|
39
|
+
#lastHighlightedMeshId = null; // Cache to avoid redundant highlight updates
|
|
40
|
+
|
|
35
41
|
/**
|
|
36
42
|
* Creates a new BabylonJSAnimationController for a Babylon.js scene.
|
|
37
|
-
* @param {Scene}
|
|
43
|
+
* @param {AssetContainer|Scene} assetContainer - The Babylon.js asset container or scene instance.
|
|
38
44
|
*/
|
|
39
|
-
constructor(
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
constructor(assetContainer) {
|
|
46
|
+
if (assetContainer instanceof AssetContainer || assetContainer instanceof Scene) {
|
|
47
|
+
this.#scene = assetContainer.scene ? assetContainer.scene : assetContainer;
|
|
48
|
+
this.#assetContainer = assetContainer;
|
|
49
|
+
this.#canvas = this.#scene._engine._renderingCanvas;
|
|
50
|
+
}
|
|
42
51
|
this.#initializeAnimations();
|
|
43
52
|
}
|
|
44
53
|
|
|
@@ -48,7 +57,7 @@ export default class BabylonJSAnimationController {
|
|
|
48
57
|
*/
|
|
49
58
|
#initializeAnimations() {
|
|
50
59
|
this.hideMenu(); // Clean up any existing menus
|
|
51
|
-
if (!this.#
|
|
60
|
+
if (!this.#assetContainer?.animationGroups?.length) {
|
|
52
61
|
return;
|
|
53
62
|
}
|
|
54
63
|
this.#getAnimatedNodes();
|
|
@@ -60,7 +69,7 @@ export default class BabylonJSAnimationController {
|
|
|
60
69
|
* @private
|
|
61
70
|
*/
|
|
62
71
|
#getAnimatedNodes() {
|
|
63
|
-
this.#
|
|
72
|
+
this.#assetContainer.animationGroups.forEach((animationGroup) => {
|
|
64
73
|
animationGroup.stop();
|
|
65
74
|
if (!animationGroup._targetedAnimations.length) {
|
|
66
75
|
return;
|
|
@@ -81,7 +90,7 @@ export default class BabylonJSAnimationController {
|
|
|
81
90
|
*/
|
|
82
91
|
#getOpeningAnimations() {
|
|
83
92
|
const openings = {};
|
|
84
|
-
this.#
|
|
93
|
+
this.#assetContainer.animationGroups.forEach((animationGroup) => {
|
|
85
94
|
const match = animationGroup.name.match(/^animation_(open|close)_(.+)$/);
|
|
86
95
|
if (!match) {
|
|
87
96
|
return;
|
|
@@ -118,7 +127,7 @@ export default class BabylonJSAnimationController {
|
|
|
118
127
|
* @param {Mesh} mesh - The mesh to check.
|
|
119
128
|
* @returns {Array<string>} Array of animated node IDs associated with the mesh.
|
|
120
129
|
*/
|
|
121
|
-
#getNodesAnimatedByMesh
|
|
130
|
+
#getNodesAnimatedByMesh(mesh) {
|
|
122
131
|
let nodeId = [];
|
|
123
132
|
let node = mesh;
|
|
124
133
|
while (node.parent !== null) {
|
|
@@ -128,7 +137,176 @@ export default class BabylonJSAnimationController {
|
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
return nodeId;
|
|
131
|
-
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creates overlay meshes with highlight rendering for a group of meshes.
|
|
144
|
+
* Handles both regular meshes and instanced meshes by creating clones in the utility layer.
|
|
145
|
+
* Synchronizes world matrix transformations between original and overlay meshes only when transforms change.
|
|
146
|
+
* @private
|
|
147
|
+
* @param {Mesh[]} meshes - Array of meshes to highlight with overlay outlines.
|
|
148
|
+
* @returns {Object} Cleanup API object with a dispose() method to release all overlay resources.
|
|
149
|
+
* @description
|
|
150
|
+
* - Uses UtilityLayerRenderer for rendering
|
|
151
|
+
* - Only syncs transforms when original mesh's world matrix changes (performance optimized)
|
|
152
|
+
* - Handles InstancedMeshes by creating instances of a shared overlay base
|
|
153
|
+
* - Removes overlay when mouse moves away from highlighted meshes
|
|
154
|
+
*/
|
|
155
|
+
#addGroupOutlineOverlayForInstances(meshes) {
|
|
156
|
+
// Configuration
|
|
157
|
+
const OUTLINE_ENABLED = false; // Draw outline on individual meshes
|
|
158
|
+
const OUTLINE_COLOR = this.#highlightColor;
|
|
159
|
+
const OUTLINE_WIDTH = 1.5;
|
|
160
|
+
const OVERLAY_ENABLED = true; // Draw semi-transparent fill
|
|
161
|
+
const OVERLAY_COLOR = this.#highlightColor;
|
|
162
|
+
const OVERLAY_ALPHA = 0.25;
|
|
163
|
+
const RENDERING_GROUP_ID = 0; // MUST be > 0 to render on top of main scene
|
|
164
|
+
|
|
165
|
+
// Use UtilityLayerRenderer for rendering
|
|
166
|
+
const uScene = UtilityLayerRenderer.DefaultKeepDepthUtilityLayer.utilityLayerScene;
|
|
167
|
+
|
|
168
|
+
// Cache map: baseMesh -> overlayBaseMesh to reuse for InstancedMeshes
|
|
169
|
+
const baseToOverlayBase = new Map();
|
|
170
|
+
|
|
171
|
+
// Pairs of { original mesh, overlay mesh } for tracking
|
|
172
|
+
const pairs = [];
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Gets the base mesh from either a regular mesh or an InstancedMesh
|
|
176
|
+
* @param {Mesh} mesh - The mesh to check
|
|
177
|
+
* @returns {Mesh} The base mesh or the mesh itself
|
|
178
|
+
*/
|
|
179
|
+
function getBaseMesh(mesh) {
|
|
180
|
+
return mesh instanceof InstancedMesh ? mesh.sourceMesh : mesh;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates or retrieves an overlay base mesh in the utility scene
|
|
185
|
+
* Reuses the same base for all instances of the same source mesh
|
|
186
|
+
* @param {Mesh} baseMesh - The base mesh to clone
|
|
187
|
+
* @returns {Mesh} The overlay base mesh
|
|
188
|
+
*/
|
|
189
|
+
function ensureOverlayBase(baseMesh) {
|
|
190
|
+
let overlayBase = baseToOverlayBase.get(baseMesh);
|
|
191
|
+
if (overlayBase) {
|
|
192
|
+
return overlayBase;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Clone the source mesh into the utility scene (deep clone materials/geometry)
|
|
196
|
+
overlayBase = baseMesh.clone(baseMesh.name + "_overlayBase", null, true, false);
|
|
197
|
+
overlayBase._scene = uScene;
|
|
198
|
+
overlayBase.isPickable = false;
|
|
199
|
+
overlayBase.setEnabled(true);
|
|
200
|
+
|
|
201
|
+
// Configure visual appearance for the overlay
|
|
202
|
+
overlayBase.renderOutline = OUTLINE_ENABLED; // Only draw outline if enabled
|
|
203
|
+
overlayBase.outlineColor = OUTLINE_COLOR;
|
|
204
|
+
overlayBase.outlineWidth = OUTLINE_WIDTH;
|
|
205
|
+
|
|
206
|
+
// Overlay color and transparency (semi-transparent colored fill)
|
|
207
|
+
overlayBase.renderOverlay = OVERLAY_ENABLED;
|
|
208
|
+
overlayBase.overlayColor = OVERLAY_COLOR;
|
|
209
|
+
overlayBase.overlayAlpha = OVERLAY_ALPHA;
|
|
210
|
+
|
|
211
|
+
overlayBase.visibility = 1;
|
|
212
|
+
|
|
213
|
+
// Ensure always-on-top rendering or not
|
|
214
|
+
overlayBase.renderingGroupId = RENDERING_GROUP_ID;
|
|
215
|
+
|
|
216
|
+
baseToOverlayBase.set(baseMesh, overlayBase);
|
|
217
|
+
return overlayBase;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Create overlay meshes for each input mesh
|
|
221
|
+
meshes.forEach((mesh) => {
|
|
222
|
+
// Skip meshes without geometry
|
|
223
|
+
if (!mesh.geometry && !mesh.getChildMeshes) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const baseMesh = getBaseMesh(mesh);
|
|
228
|
+
const overlayBase = ensureOverlayBase(baseMesh);
|
|
229
|
+
|
|
230
|
+
let overlay;
|
|
231
|
+
if (mesh instanceof InstancedMesh) {
|
|
232
|
+
// For instances, create a new instance of the overlay base
|
|
233
|
+
overlay = overlayBase.createInstance(mesh.name + "_overlayInst");
|
|
234
|
+
overlay._scene = uScene;
|
|
235
|
+
overlay.isPickable = false;
|
|
236
|
+
} else {
|
|
237
|
+
// For regular meshes, use the overlay base directly (avoids double cloning)
|
|
238
|
+
overlay = overlayBase;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Remove parent to allow independent world matrix control
|
|
242
|
+
overlay.parent = null;
|
|
243
|
+
overlay.renderingGroupId = RENDERING_GROUP_ID;
|
|
244
|
+
|
|
245
|
+
pairs.push({ original: mesh, overlay });
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Sync world matrix transformations
|
|
249
|
+
const pos = new Vector3();
|
|
250
|
+
const scl = new Vector3();
|
|
251
|
+
const rot = new Quaternion();
|
|
252
|
+
|
|
253
|
+
// Track all observers for cleanup
|
|
254
|
+
const observers = [];
|
|
255
|
+
|
|
256
|
+
// Subscribe to world matrix update events
|
|
257
|
+
// This is more efficient than updating every frame
|
|
258
|
+
pairs.forEach(({ original, overlay }) => {
|
|
259
|
+
// Skip if mesh has no geometry to sync
|
|
260
|
+
if (!original.getWorldMatrix) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Initial sync: copy the current world matrix
|
|
265
|
+
original.computeWorldMatrix(true);
|
|
266
|
+
original.getWorldMatrix().decompose(scl, rot, pos);
|
|
267
|
+
|
|
268
|
+
overlay.position.copyFrom(pos);
|
|
269
|
+
overlay.scaling.copyFrom(scl);
|
|
270
|
+
overlay.rotationQuaternion = overlay.rotationQuaternion || new Quaternion();
|
|
271
|
+
overlay.rotationQuaternion.copyFrom(rot);
|
|
272
|
+
overlay.computeWorldMatrix(true);
|
|
273
|
+
|
|
274
|
+
// Subscribe to world matrix changes
|
|
275
|
+
// Only updates when the original mesh's transformation actually changes
|
|
276
|
+
const obs = original.onAfterWorldMatrixUpdateObservable?.add(() => {
|
|
277
|
+
original.getWorldMatrix().decompose(scl, rot, pos);
|
|
278
|
+
|
|
279
|
+
overlay.position.copyFrom(pos);
|
|
280
|
+
overlay.scaling.copyFrom(scl);
|
|
281
|
+
overlay.rotationQuaternion.copyFrom(rot);
|
|
282
|
+
overlay.computeWorldMatrix(true);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (obs) {
|
|
286
|
+
observers.push({ mesh: original, observer: obs });
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Cleanup API
|
|
291
|
+
return {
|
|
292
|
+
dispose() {
|
|
293
|
+
// Remove world matrix update observers
|
|
294
|
+
observers.forEach(({ mesh, observer }) => {
|
|
295
|
+
mesh.onAfterWorldMatrixUpdateObservable?.remove(observer);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Dispose overlay instances and meshes
|
|
299
|
+
pairs.forEach(({ original, overlay }) => {
|
|
300
|
+
overlay.dispose();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Dispose overlay base meshes
|
|
304
|
+
baseToOverlayBase.forEach((overlayBase) => {
|
|
305
|
+
overlayBase.dispose();
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
132
310
|
|
|
133
311
|
/**
|
|
134
312
|
* ---------------------------
|
|
@@ -148,6 +326,10 @@ export default class BabylonJSAnimationController {
|
|
|
148
326
|
this.#highlightLayer.dispose();
|
|
149
327
|
this.#highlightLayer = null;
|
|
150
328
|
}
|
|
329
|
+
if (this.#overlayLayer) {
|
|
330
|
+
this.#overlayLayer.dispose();
|
|
331
|
+
this.#overlayLayer = null;
|
|
332
|
+
}
|
|
151
333
|
this.hideMenu();
|
|
152
334
|
this.#animatedNodes = [];
|
|
153
335
|
this.#openingAnimations.forEach((openingAnimation) => openingAnimation.dispose());
|
|
@@ -159,39 +341,74 @@ export default class BabylonJSAnimationController {
|
|
|
159
341
|
* @public
|
|
160
342
|
* @param {PickingInfo} pickingInfo - Raycast info from pointer position.
|
|
161
343
|
*/
|
|
162
|
-
|
|
163
|
-
if
|
|
164
|
-
|
|
344
|
+
highlightMeshes(pickingInfo) {
|
|
345
|
+
// Check if we're hovering the same mesh to avoid recreating overlays/highlights
|
|
346
|
+
const pickedMeshId = pickingInfo?.pickedMesh?.id;
|
|
347
|
+
if (this.#lastHighlightedMeshId === pickedMeshId) {
|
|
348
|
+
return; // No need to update if hovering the same mesh
|
|
165
349
|
}
|
|
350
|
+
this.#lastHighlightedMeshId = pickedMeshId;
|
|
166
351
|
|
|
167
|
-
this.#
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
352
|
+
if (this.#useHighlightLayer) {
|
|
353
|
+
if (!this.#highlightLayer) {
|
|
354
|
+
this.#highlightLayer = new HighlightLayer("hl_animations", this.#scene);
|
|
355
|
+
}
|
|
171
356
|
|
|
172
|
-
|
|
173
|
-
if (!nodeIds.length) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
357
|
+
this.#highlightLayer.removeAllMeshes();
|
|
176
358
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const transformNode = this.#scene.getTransformNodeByID(nodeId);
|
|
180
|
-
if (transformNode) {
|
|
181
|
-
transformNodes.push(transformNode);
|
|
359
|
+
if (!pickingInfo?.hit || !pickingInfo?.pickedMesh) {
|
|
360
|
+
return;
|
|
182
361
|
}
|
|
183
|
-
});
|
|
184
362
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
nodeMeshes.forEach((mesh) => {
|
|
189
|
-
if (!this.#highlightLayer.hasMesh(mesh)) {
|
|
190
|
-
this.#highlightLayer.addMesh(mesh, this.#highlightColor);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
363
|
+
const nodeIds = this.#getNodesAnimatedByMesh(pickingInfo.pickedMesh);
|
|
364
|
+
if (!nodeIds.length) {
|
|
365
|
+
return;
|
|
193
366
|
}
|
|
194
|
-
|
|
367
|
+
|
|
368
|
+
const transformNodes = [];
|
|
369
|
+
nodeIds.forEach((nodeId) => {
|
|
370
|
+
const transformNode = this.#scene.getTransformNodeByID(nodeId);
|
|
371
|
+
if (transformNode) {
|
|
372
|
+
transformNodes.push(transformNode);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
transformNodes.forEach((transformNode) => {
|
|
377
|
+
const transformNodeMeshes = transformNode.getChildMeshes();
|
|
378
|
+
if (transformNodeMeshes.length) {
|
|
379
|
+
transformNodeMeshes.forEach((mesh) => {
|
|
380
|
+
if (!this.#highlightLayer.hasMesh(mesh)) {
|
|
381
|
+
this.#highlightLayer.addMesh(mesh, this.#highlightColor);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
} else {
|
|
387
|
+
if (this.#overlayLayer) {
|
|
388
|
+
this.#overlayLayer.dispose();
|
|
389
|
+
this.#overlayLayer = null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!pickingInfo?.hit || !pickingInfo?.pickedMesh) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const nodeIds = this.#getNodesAnimatedByMesh(pickingInfo.pickedMesh);
|
|
397
|
+
if (!nodeIds.length) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const meshes = [];
|
|
402
|
+
nodeIds.forEach((nodeId) => {
|
|
403
|
+
const transformNode = this.#scene.getTransformNodeByID(nodeId);
|
|
404
|
+
if (transformNode) {
|
|
405
|
+
const transformNodeMeshes = transformNode.getChildMeshes(false);
|
|
406
|
+
meshes.push(...transformNodeMeshes);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
this.#overlayLayer = this.#addGroupOutlineOverlayForInstances(meshes);
|
|
411
|
+
}
|
|
195
412
|
}
|
|
196
413
|
|
|
197
414
|
/**
|
|
@@ -201,7 +418,7 @@ export default class BabylonJSAnimationController {
|
|
|
201
418
|
*/
|
|
202
419
|
hideMenu() {
|
|
203
420
|
this.#openingAnimations.forEach((openingAnimation) => openingAnimation.hideControls());
|
|
204
|
-
this.#canvas
|
|
421
|
+
this.#canvas?.parentElement?.querySelectorAll("div.pref-viewer-3d.animation-menu").forEach((menu) => menu.remove());
|
|
205
422
|
}
|
|
206
423
|
|
|
207
424
|
/**
|
|
@@ -95,6 +95,8 @@ export default class OpeningAnimation {
|
|
|
95
95
|
this.#bindHandlers();
|
|
96
96
|
this.#openAnimation.onAnimationGroupEndObservable.add(this.#handlers.onOpened);
|
|
97
97
|
this.#closeAnimation.onAnimationGroupEndObservable.add(this.#handlers.onClosed);
|
|
98
|
+
|
|
99
|
+
this.#goToClosed();
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
#bindHandlers() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArcRotateCamera, AssetContainer, Camera, Color4, DefaultRenderingPipeline, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PBRMaterial, PointerEventTypes, PointLight, Scene, ShadowGenerator, SpotLight, SSAORenderingPipeline, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager, WebXRState } from "@babylonjs/core";
|
|
1
|
+
import { ArcRotateCamera, AssetContainer, Camera, Color4, DefaultRenderingPipeline, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PBRMaterial, PointerEventTypes, PointLight, RenderTargetTexture, Scene, ShadowGenerator, SpotLight, SSAORenderingPipeline, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager, WebXRState } from "@babylonjs/core";
|
|
2
2
|
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression.js";
|
|
3
3
|
import "@babylonjs/loaders";
|
|
4
4
|
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression.js";
|
|
@@ -118,20 +118,25 @@ export default class BabylonJSController {
|
|
|
118
118
|
#shadowGen = [];
|
|
119
119
|
#XRExperience = null;
|
|
120
120
|
#canvasResizeObserver = null;
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
#containers = {};
|
|
123
123
|
#options = {};
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
#gltfResolver = null; // GLTFResolver instance
|
|
126
126
|
#babylonJSAnimationController = null; // AnimationController instance
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
#handlers = {
|
|
129
129
|
onKeyUp: null,
|
|
130
130
|
onPointerObservable: null,
|
|
131
131
|
renderLoop: null,
|
|
132
132
|
};
|
|
133
|
-
|
|
134
|
-
#
|
|
133
|
+
|
|
134
|
+
#settings = {
|
|
135
|
+
antiAliasingEnabled: true,
|
|
136
|
+
ambientOcclusionEnabled: true,
|
|
137
|
+
iblEnabled: true,
|
|
138
|
+
shadowsEnabled: false,
|
|
139
|
+
};
|
|
135
140
|
|
|
136
141
|
/**
|
|
137
142
|
* Constructs a new BabylonJSController instance.
|
|
@@ -298,7 +303,7 @@ export default class BabylonJSController {
|
|
|
298
303
|
* @returns {void}
|
|
299
304
|
*/
|
|
300
305
|
#createLights() {
|
|
301
|
-
if (this.#options.ibl && this.#options.ibl.url) {
|
|
306
|
+
if (this.#settings.iblEnabled && this.#options.ibl && this.#options.ibl.url) {
|
|
302
307
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
303
308
|
this.#initializeEnvironmentTexture();
|
|
304
309
|
}
|
|
@@ -334,6 +339,10 @@ export default class BabylonJSController {
|
|
|
334
339
|
return false;
|
|
335
340
|
}
|
|
336
341
|
|
|
342
|
+
if (!this.#settings.ambientOcclusionEnabled) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
337
346
|
const pipelineName = "PrefViewerSSAORenderingPipeline";
|
|
338
347
|
|
|
339
348
|
const supportedPipelines = this.#scene.postProcessRenderPipelineManager.supportedPipelines;
|
|
@@ -402,6 +411,10 @@ export default class BabylonJSController {
|
|
|
402
411
|
this.#scene.postProcessRenderPipelineManager.update();
|
|
403
412
|
}
|
|
404
413
|
|
|
414
|
+
if (!this.#settings.antiAliasingEnabled) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
405
418
|
const defaultPipeline = new DefaultRenderingPipeline(pipelineName, true, this.#scene, [this.#scene.activeCamera], true);
|
|
406
419
|
|
|
407
420
|
if (!defaultPipeline){
|
|
@@ -553,10 +566,14 @@ export default class BabylonJSController {
|
|
|
553
566
|
*/
|
|
554
567
|
#initializeDefaultLightShadows() {
|
|
555
568
|
this.#shadowGen = [];
|
|
569
|
+
this.#dirLight.autoUpdateExtends = false;
|
|
556
570
|
const shadowGenerator = new ShadowGenerator(1024, this.#dirLight);
|
|
557
571
|
shadowGenerator.useBlurExponentialShadowMap = true;
|
|
558
572
|
shadowGenerator.blurKernel = 16;
|
|
559
573
|
shadowGenerator.darkness = 0.5;
|
|
574
|
+
shadowGenerator.bias = 0.0005;
|
|
575
|
+
shadowGenerator.normalBias = 0.02;
|
|
576
|
+
shadowGenerator.getShadowMap().refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
|
|
560
577
|
this.#scene.meshes.forEach((mesh) => {
|
|
561
578
|
if (mesh.id.startsWith("__root__")) {
|
|
562
579
|
return false;
|
|
@@ -589,10 +606,14 @@ export default class BabylonJSController {
|
|
|
589
606
|
if (!(light instanceof DirectionalLight || light instanceof SpotLight)) {
|
|
590
607
|
return;
|
|
591
608
|
}
|
|
609
|
+
light.autoUpdateExtends = false;
|
|
592
610
|
const shadowGenerator = new ShadowGenerator(1024, light);
|
|
593
611
|
shadowGenerator.useBlurExponentialShadowMap = true;
|
|
594
612
|
shadowGenerator.blurKernel = 16;
|
|
595
613
|
shadowGenerator.darkness = 0.5;
|
|
614
|
+
shadowGenerator.bias = 0.0005;
|
|
615
|
+
shadowGenerator.normalBias = 0.02;
|
|
616
|
+
shadowGenerator.getShadowMap().refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
|
|
596
617
|
this.#scene.meshes.forEach((mesh) => {
|
|
597
618
|
if (mesh.id.startsWith("__root__")) {
|
|
598
619
|
return false;
|
|
@@ -614,7 +635,7 @@ export default class BabylonJSController {
|
|
|
614
635
|
* Otherwise, sets up shadow casting and receiving for all relevant meshes using the shadow generator.
|
|
615
636
|
*/
|
|
616
637
|
#initializeShadows() {
|
|
617
|
-
if (!this.#shadowsEnabled) {
|
|
638
|
+
if (!this.#settings.shadowsEnabled) {
|
|
618
639
|
return false;
|
|
619
640
|
}
|
|
620
641
|
if (this.#scene.environmentTexture) {
|
|
@@ -815,7 +836,7 @@ export default class BabylonJSController {
|
|
|
815
836
|
*/
|
|
816
837
|
#onPointerMove(event, pickInfo) {
|
|
817
838
|
if (this.#babylonJSAnimationController) {
|
|
818
|
-
this.#babylonJSAnimationController.
|
|
839
|
+
this.#babylonJSAnimationController.highlightMeshes(pickInfo);
|
|
819
840
|
}
|
|
820
841
|
}
|
|
821
842
|
|
|
@@ -954,11 +975,12 @@ export default class BabylonJSController {
|
|
|
954
975
|
* @returns {boolean} True when lights were refreshed due to pending IBL changes, otherwise false.
|
|
955
976
|
*/
|
|
956
977
|
#setOptions_IBL() {
|
|
957
|
-
if (this.#options.ibl.isPending) {
|
|
978
|
+
if (this.#options.ibl.isPending && this.#settings.iblEnabled) {
|
|
958
979
|
this.#options.ibl.setSuccess(true);
|
|
959
980
|
this.#createLights();
|
|
960
981
|
return true;
|
|
961
982
|
}
|
|
983
|
+
this.#createLights();
|
|
962
984
|
return false;
|
|
963
985
|
}
|
|
964
986
|
|
|
@@ -1224,7 +1246,8 @@ export default class BabylonJSController {
|
|
|
1224
1246
|
this.#checkModelMetadata(oldModelMetadata, newModelMetadata);
|
|
1225
1247
|
this.#setMaxSimultaneousLights();
|
|
1226
1248
|
this.#loadCameraDepentEffects();
|
|
1227
|
-
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#
|
|
1249
|
+
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#containers.model.assetContainer);
|
|
1250
|
+
this.#forceReflectionsInModelGlasses();
|
|
1228
1251
|
this.#startRender();
|
|
1229
1252
|
});
|
|
1230
1253
|
return detail;
|
|
@@ -1318,6 +1341,21 @@ export default class BabylonJSController {
|
|
|
1318
1341
|
}
|
|
1319
1342
|
}
|
|
1320
1343
|
|
|
1344
|
+
#forceReflectionsInModelGlasses(assetContainer) {
|
|
1345
|
+
if (assetContainer === undefined) {
|
|
1346
|
+
assetContainer = this.#containers.model.assetContainer;
|
|
1347
|
+
}
|
|
1348
|
+
if (!this.#scene || !assetContainer) {
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
assetContainer.materials?.forEach(material => {
|
|
1352
|
+
if (material && material.name && material.name.includes("Glass")) {
|
|
1353
|
+
material.metallic = 1.0;
|
|
1354
|
+
material.environmentIntensity = 1.0;
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1321
1359
|
/**
|
|
1322
1360
|
* Translates a node along the scene's vertical (Y) axis by the provided value.
|
|
1323
1361
|
* @private
|
|
@@ -1483,7 +1521,6 @@ export default class BabylonJSController {
|
|
|
1483
1521
|
|
|
1484
1522
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
1485
1523
|
this.#createCamera();
|
|
1486
|
-
this.#createLights();
|
|
1487
1524
|
this.#enableInteraction();
|
|
1488
1525
|
await this.#createXRExperience();
|
|
1489
1526
|
this.#startRender();
|
package/src/pref-viewer-3d.js
CHANGED
|
@@ -336,12 +336,14 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
336
336
|
let intensity = undefined;
|
|
337
337
|
|
|
338
338
|
if (options.ibl.url) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
339
|
+
url = options.ibl.url;
|
|
340
|
+
// TEMPORARY: Disable FileStorage usage due to efficiency problems
|
|
341
|
+
// const fileStorage = new FileStorage("PrefViewer", "Files");
|
|
342
|
+
// const newURL = await fileStorage.getURL(options.ibl.url);
|
|
343
|
+
// if (newURL) {
|
|
344
|
+
// url = newURL;
|
|
345
|
+
// timeStamp = await fileStorage.getTimeStamp(options.ibl.url);
|
|
346
|
+
// }
|
|
345
347
|
}
|
|
346
348
|
if (options.ibl.shadows !== undefined) {
|
|
347
349
|
shadows = options.ibl.shadows;
|