@preference-sl/pref-viewer 2.13.0-beta.0 → 2.13.0-beta.2
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 +5 -5
- package/src/babylonjs-animation-controller.js +2 -2
- package/src/babylonjs-controller.js +522 -274
- package/src/index.js +2 -0
- package/src/localization/i18n.js +94 -0
- package/src/localization/translations.js +104 -0
- package/src/pref-viewer-3d-data.js +28 -0
- package/src/pref-viewer-3d.js +46 -10
- package/src/pref-viewer-menu-3d.js +557 -0
- package/src/pref-viewer-task.js +1 -0
- package/src/pref-viewer.js +532 -200
- package/src/styles.js +302 -10
|
@@ -8,101 +8,71 @@ import JSZip from "jszip";
|
|
|
8
8
|
import GLTFResolver from "./gltf-resolver.js";
|
|
9
9
|
import { MaterialData } from "./pref-viewer-3d-data.js";
|
|
10
10
|
import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
11
|
+
import { translate } from "./localization/i18n.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* BabylonJSController
|
|
14
|
+
* BabylonJSController is the PrefViewer 3D engine coordinator: it bootstraps Babylon.js, manages asset containers,
|
|
15
|
+
* rebuilds post-process pipelines on demand, brokers XR/download interactions, and persists user render toggles so UI
|
|
16
|
+
* components can stay declarative. Higher layers hand over container + option state, while this class turns it into a
|
|
17
|
+
* fully interactive scene with deterministic reloads and exports.
|
|
14
18
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* Overview
|
|
20
|
+
* - Spins up the Babylon.js engine/scene/camera stack, configures Draco loaders, and wires resize + render loops.
|
|
21
|
+
* - Resolves GLTF/GLB sources through GLTFResolver, loads them into `AssetContainer`s, and toggles their visibility.
|
|
22
|
+
* - Applies render-setting switches (AA, SSAO, IBL, shadows), persists them in localStorage, and rehydrates on startup.
|
|
23
|
+
* - Rebuilds DefaultRenderingPipeline, SSAO, IBL, and shadow pipelines every time the active camera or assets change.
|
|
24
|
+
* - Connects keyboard, pointer, wheel, hover, and XR events so menus, animation controllers, and PrefViewer attributes stay in sync.
|
|
25
|
+
* - Generates GLB, glTF+ZIP, or USDZ exports with timestamped names and localized dialog copy.
|
|
26
|
+
* - Translates metadata (inner floor offsets, cast/receive shadows, camera locks) into scene adjustments after reloads.
|
|
18
27
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* - Applies metadata-driven scene adjustments (e.g., inner floor translation) after asset reloads.
|
|
28
|
-
* - Designed for integration with PrefViewer and GLTFResolver.
|
|
29
|
-
* - All resource management and rendering operations are performed asynchronously for performance.
|
|
28
|
+
* Runtime Flow
|
|
29
|
+
* 1. Instantiate with `new BabylonJSController(canvas, containers, options)`.
|
|
30
|
+
* 2. Call `enable()` to configure Draco, create the engine/scene, observers, and XR hooks.
|
|
31
|
+
* 3. Whenever PrefViewer marks containers/options pending, invoke `load()` to fetch sources and rebuild pipelines.
|
|
32
|
+
* 4. Use `applyRenderSettings` helpers (via menu events) to merge toggles, persist them, and trigger reloads when needed.
|
|
33
|
+
* 5. Respond to PrefViewer tasks by calling `showModel()/hideModel()` equivalents through container state changes.
|
|
34
|
+
* 6. Surface downloads through `downloadGLB/GLTF/USDZ` or the private `#openDownloadDialog()` triggered by keyboard shortcuts.
|
|
35
|
+
* 7. Invoke `disable()` when the element disconnects to teardown scenes, XR sessions, and listeners.
|
|
30
36
|
*
|
|
31
|
-
*
|
|
32
|
-
* -
|
|
33
|
-
* -
|
|
34
|
-
* -
|
|
35
|
-
* -
|
|
36
|
-
* -
|
|
37
|
-
* -
|
|
38
|
-
* - Disable rendering: controller.disable();
|
|
37
|
+
* Public API Highlights
|
|
38
|
+
* - constructor(canvas, containers, options)
|
|
39
|
+
* - enable() / disable()
|
|
40
|
+
* - load()
|
|
41
|
+
* - downloadGLB(content) / downloadGLTF(content) / downloadUSDZ(content)
|
|
42
|
+
* - getRenderSettings() / applyRenderSettings(partial)
|
|
43
|
+
* - setRenderSettings(settings) [via PrefViewer menu integration]
|
|
39
44
|
*
|
|
40
|
-
*
|
|
41
|
-
* -
|
|
42
|
-
* -
|
|
43
|
-
* -
|
|
44
|
-
*
|
|
45
|
-
* -
|
|
46
|
-
*
|
|
47
|
-
* -
|
|
48
|
-
*
|
|
49
|
-
* -
|
|
50
|
-
*
|
|
51
|
-
* - downloadUSDZ(content): Builds an Apple USDZ archive for the requested scope and downloads it via blob streaming.
|
|
45
|
+
* Key Subsystems
|
|
46
|
+
* - Persistence: #applyRenderSettings, #loadStoredRenderSettings, #saveRenderSettings keep AA/SSAO/IBL/shadow flags synced.
|
|
47
|
+
* - Loading pipeline: #markContainersForReload, #markOptionsForReload, #loadAssetContainer, #loadContainers orchestrate deterministic reloads.
|
|
48
|
+
* - Visual setup: #configureDracoCompression, #createCamera, #createLights, #initializeVisualImprovements, #initializeAmbientOcclussion,
|
|
49
|
+
* #initializeIBLShadows, #initializeDefaultLightShadows, #initializeEnvironmentShadows, #setMaxSimultaneousLights.
|
|
50
|
+
* - Interaction + XR: #bindHandlers, #enableInteraction, #onPointerObservable, #onMouseWheel, #onKeyUp, #createXRExperience,
|
|
51
|
+
* #addStylesToARButton, #disposeXRExperience.
|
|
52
|
+
* - Container helpers: #addContainer, #removeContainer, #replaceContainer, #setOptions_Materials, #setOptions_Camera,
|
|
53
|
+
* #setOptions_IBL, #setVisibilityOfWallAndFloorInModel, #getPrefViewerComponent.
|
|
54
|
+
* - Metadata + download utilities: #checkModelMetadata, #checkInnerFloorTranslation, #translateNodeY, #addDateToName,
|
|
55
|
+
* #downloadZip, #openDownloadDialog.
|
|
52
56
|
*
|
|
53
|
-
*
|
|
54
|
-
* -
|
|
55
|
-
* -
|
|
56
|
-
* -
|
|
57
|
-
* - #addStylesToARButton(): Styles AR button.
|
|
58
|
-
* - #createXRExperience(): Initializes WebXR AR experience.
|
|
59
|
-
* - #createCamera(): Creates and configures the main camera.
|
|
60
|
-
* - #createLights(): Creates and configures scene lights and shadows.
|
|
61
|
-
* - #initializeAmbientOcclussion(): Rebuilds the SSAO pipeline for the active camera.
|
|
62
|
-
* - #initializeVisualImprovements(): Reinstalls the default rendering pipeline (MSAA/FXAA/grain).
|
|
63
|
-
* - #initializeEnvironmentTexture(): Loads and sets the HDR environment texture.
|
|
64
|
-
* - #initializeIBLShadows(): Sets up IBL shadow pipeline and assigns meshes/materials.
|
|
65
|
-
* - #addMeshToShadowGenerator(shadowGenerator, mesh): Adds eligible meshes to the provided shadow generator while honoring GLTF metadata.
|
|
66
|
-
* - #initializeShadows(): Sets up standard or IBL shadows for meshes.
|
|
67
|
-
* - #initializeDefaultLightShadows(): Configures soft shadows when no HDR environment exists.
|
|
68
|
-
* - #initializeEnvironmentShadows(): Rebuilds environment-provided shadow generators.
|
|
69
|
-
* - #ensureMeshesReceiveShadows(): Ensures every non-root mesh receives shadows unless metadata opts out.
|
|
70
|
-
* - #setMaxSimultaneousLights(): Updates max simultaneous lights for all materials.
|
|
71
|
-
* - #onPointerObservable(info): Handles pointer events and dispatches to pointer/mouse handlers.
|
|
72
|
-
* - #onPointerUp(event, pickInfo): Handles pointer up events (e.g., right-click for animation menu).
|
|
73
|
-
* - #onPointerMove(event, pickInfo): Handles pointer move events (e.g., mesh highlighting).
|
|
74
|
-
* - #onMouseWheel(event, pickInfo): Handles mouse wheel events for camera zoom.
|
|
75
|
-
* - #onKeyUp(event): Handles keyup events for download dialog and shortcuts.
|
|
76
|
-
* - #enableInteraction(): Adds canvas and scene interaction event listeners.
|
|
77
|
-
* - #disableInteraction(): Removes canvas and scene interaction event listeners.
|
|
78
|
-
* - #disposeAnimationController(): Disposes the animation controller if it exists.
|
|
79
|
-
* - #disposeXRExperience(): Disposes the Babylon.js WebXR experience if it exists.
|
|
80
|
-
* - #disposeEngine(): Disposes engine and releases all resources.
|
|
81
|
-
* - #setOptionsMaterial(optionMaterial): Applies a material option to relevant meshes.
|
|
82
|
-
* - #setOptions_Materials(): Applies all material options from configuration.
|
|
83
|
-
* - #setOptions_Camera(): Applies camera options from configuration.
|
|
84
|
-
* - #setOptions_IBL(): Applies pending HDR/IBL option updates and refreshes lights.
|
|
85
|
-
* - #findContainerByName(name): Finds a container by its name.
|
|
86
|
-
* - #addContainer(container, updateVisibility): Adds a container to the scene and updates visibility.
|
|
87
|
-
* - #removeContainer(container, updateVisibility): Removes a container from the scene and updates visibility.
|
|
88
|
-
* - #replaceContainer(container, newAssetContainer): Replaces a container in the scene.
|
|
89
|
-
* - #getPrefViewer3DComponent(): Caches and retrieves the parent PREF-VIEWER-3D element.
|
|
90
|
-
* - #getPrefViewerComponent(): Caches and retrieves the parent PREF-VIEWER element.
|
|
91
|
-
* - #updateVisibilityAttributeInComponents(name, isVisible): Updates parent visibility attributes.
|
|
92
|
-
* - #setVisibilityOfWallAndFloorInModel(show): Controls wall/floor mesh visibility.
|
|
93
|
-
* - #stopRender(): Stops the Babylon.js render loop.
|
|
94
|
-
* - #startRender(): Starts the Babylon.js render loop.
|
|
95
|
-
* - #loadAssetContainer(container): Loads an asset container asynchronously.
|
|
96
|
-
* - #loadContainers(): Loads all asset containers and adds them to the scene.
|
|
97
|
-
* - #loadCameraDepentEffects(): Re-initializes camera-bound post-processes after reloads or option changes.
|
|
98
|
-
* - #checkModelMetadata(oldMetadata, newMetadata): Processes metadata changes after loading.
|
|
99
|
-
* - #checkInnerFloorTranslation(oldMetadata, newMetadata): Applies inner floor Y translations from metadata.
|
|
100
|
-
* - #translateNodeY(name, deltaY): Adjusts the Y position of a scene node.
|
|
101
|
-
* - #addDateToName(name): Appends the current date/time to a name string.
|
|
102
|
-
* - #downloadZip(files, name, comment, addDateInName): Generates and downloads a ZIP file.
|
|
103
|
-
* - #openDownloadDialog(): Opens the modal download dialog.
|
|
57
|
+
* Notes
|
|
58
|
+
* - Designed to be long-lived per PrefViewer instance; it caches parent components to reflect `show-model/show-scene` attributes.
|
|
59
|
+
* - All browser-only features guard against SSR/Node usage by checking `window` before touching localStorage or XR APIs.
|
|
60
|
+
* - Relies on PrefViewerMenu3D events to trigger render-setting updates, ensuring UI and persisted state never drift apart.
|
|
104
61
|
*/
|
|
105
62
|
export default class BabylonJSController {
|
|
63
|
+
|
|
64
|
+
#RENDER_SETTINGS_STORAGE_KEY = "pref-viewer/render-settings";
|
|
65
|
+
|
|
66
|
+
// Default render settings
|
|
67
|
+
// Add here new render toggles that require persistence here, and remember to update the matching labels
|
|
68
|
+
// under translations.prefViewer.menu3d.switches in ./localization/translations.js.
|
|
69
|
+
static DEFAULT_RENDER_SETTINGS = {
|
|
70
|
+
antiAliasingEnabled: true,
|
|
71
|
+
ambientOcclusionEnabled: true,
|
|
72
|
+
iblEnabled: true,
|
|
73
|
+
shadowsEnabled: false,
|
|
74
|
+
};
|
|
75
|
+
|
|
106
76
|
// Canvas HTML element
|
|
107
77
|
#canvas = null;
|
|
108
78
|
|
|
@@ -126,19 +96,20 @@ export default class BabylonJSController {
|
|
|
126
96
|
|
|
127
97
|
#gltfResolver = null; // GLTFResolver instance
|
|
128
98
|
#babylonJSAnimationController = null; // AnimationController instance
|
|
129
|
-
|
|
99
|
+
|
|
100
|
+
#renderPipelines = {
|
|
101
|
+
default: null,
|
|
102
|
+
ssao: null,
|
|
103
|
+
iblShadows: null,
|
|
104
|
+
};
|
|
105
|
+
|
|
130
106
|
#handlers = {
|
|
131
107
|
onKeyUp: null,
|
|
132
108
|
onPointerObservable: null,
|
|
133
109
|
renderLoop: null,
|
|
134
110
|
};
|
|
135
111
|
|
|
136
|
-
#settings = {
|
|
137
|
-
antiAliasingEnabled: true,
|
|
138
|
-
ambientOcclusionEnabled: true,
|
|
139
|
-
iblEnabled: true,
|
|
140
|
-
shadowsEnabled: false,
|
|
141
|
-
};
|
|
112
|
+
#settings = { ...BabylonJSController.DEFAULT_RENDER_SETTINGS };
|
|
142
113
|
|
|
143
114
|
/**
|
|
144
115
|
* Constructs a new BabylonJSController instance.
|
|
@@ -162,6 +133,7 @@ export default class BabylonJSController {
|
|
|
162
133
|
};
|
|
163
134
|
});
|
|
164
135
|
this.#options = options;
|
|
136
|
+
this.#loadStoredRenderSettings();
|
|
165
137
|
this.#bindHandlers();
|
|
166
138
|
}
|
|
167
139
|
|
|
@@ -194,6 +166,114 @@ export default class BabylonJSController {
|
|
|
194
166
|
};
|
|
195
167
|
}
|
|
196
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Merges boolean render toggles into the current configuration, clearing the environment texture when IBL turns off
|
|
171
|
+
* and persisting the updated state when at least one flag changed.
|
|
172
|
+
* @private
|
|
173
|
+
* @param {object} [settings={}] - Partial map of render settings (AA, SSAO, IBL, shadows, etc.).
|
|
174
|
+
* @returns {boolean} True when any setting changed and was saved.
|
|
175
|
+
*/
|
|
176
|
+
#applyRenderSettings(settings = {}) {
|
|
177
|
+
if (!settings) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let changed = false;
|
|
182
|
+
Object.keys(settings).forEach((key) => {
|
|
183
|
+
if (typeof settings[key] === "boolean" && this.#settings[key] !== settings[key]) {
|
|
184
|
+
this.#settings[key] = settings[key];
|
|
185
|
+
changed = true;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (changed && settings.iblEnabled === false && this.#scene) {
|
|
190
|
+
this.#scene.environmentTexture = null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (changed) {
|
|
194
|
+
this.#saveRenderSettings();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return changed;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Restores previously persisted render toggles from localStorage, falling back to defaults when parsing fails or
|
|
202
|
+
* the browser environment is unavailable.
|
|
203
|
+
* @private
|
|
204
|
+
* @returns {void}
|
|
205
|
+
*/
|
|
206
|
+
#loadStoredRenderSettings() {
|
|
207
|
+
if (typeof window === "undefined" || !window?.localStorage) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const serialized = window.localStorage.getItem(this.#RENDER_SETTINGS_STORAGE_KEY);
|
|
212
|
+
if (!serialized) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const parsed = JSON.parse(serialized);
|
|
216
|
+
Object.keys(BabylonJSController.DEFAULT_RENDER_SETTINGS).forEach((key) => {
|
|
217
|
+
if (typeof parsed?.[key] === "boolean") {
|
|
218
|
+
this.#settings[key] = parsed[key];
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.warn("PrefViewer: unable to load render settings", error);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Serializes the current render toggle object to localStorage, ignoring non-browser contexts and logging warnings
|
|
228
|
+
* if persistence fails.
|
|
229
|
+
* @private
|
|
230
|
+
* @returns {void}
|
|
231
|
+
*/
|
|
232
|
+
#saveRenderSettings() {
|
|
233
|
+
if (typeof window === "undefined" || !window?.localStorage) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
window.localStorage.setItem(this.#RENDER_SETTINGS_STORAGE_KEY, JSON.stringify(this.#settings));
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.warn("PrefViewer: unable to save render settings", error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Walks every container state and marks it as pending using `setPendingWithCurrentStorage` so asset sources are
|
|
245
|
+
* re-requested on the next load cycle.
|
|
246
|
+
* @private
|
|
247
|
+
* @returns {boolean} True when at least one container flipped to pending.
|
|
248
|
+
*/
|
|
249
|
+
#markContainersForReload() {
|
|
250
|
+
let marked = false;
|
|
251
|
+
Object.values(this.#containers).forEach((container) => {
|
|
252
|
+
if (container?.state?.setPendingWithCurrentStorage && container.state.setPendingWithCurrentStorage()) {
|
|
253
|
+
marked = true;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return marked;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Re-schedules option-driven resources (camera, materials, IBL) for application by calling their respective
|
|
261
|
+
* `setPendingWithCurrent`/`setPending` helpers when available.
|
|
262
|
+
* @private
|
|
263
|
+
* @returns {void}
|
|
264
|
+
*/
|
|
265
|
+
#markOptionsForReload() {
|
|
266
|
+
if (this.#options?.camera?.setPendingWithCurrent) {
|
|
267
|
+
this.#options.camera.setPendingWithCurrent();
|
|
268
|
+
}
|
|
269
|
+
if (this.#options?.materials) {
|
|
270
|
+
Object.values(this.#options.materials).forEach((material) => material?.setPendingWithCurrent?.());
|
|
271
|
+
}
|
|
272
|
+
if (this.#options?.ibl?.setPending) {
|
|
273
|
+
this.#options.ibl.setPending();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
197
277
|
/**
|
|
198
278
|
* Render loop callback for Babylon.js.
|
|
199
279
|
* @private
|
|
@@ -338,6 +418,43 @@ export default class BabylonJSController {
|
|
|
338
418
|
}
|
|
339
419
|
}
|
|
340
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Detaches and disposes the SSAO render pipeline from the active camera when it exists.
|
|
423
|
+
* Guards against missing scene resources or absent pipelines, returning false when no cleanup is needed.
|
|
424
|
+
* @private
|
|
425
|
+
* @returns {boolean} Returns true when the SSAO pipeline was disabled, false otherwise.
|
|
426
|
+
*/
|
|
427
|
+
#disableAmbientOcclusion() {
|
|
428
|
+
const pipelineManager = this.#scene?.postProcessRenderPipelineManager;
|
|
429
|
+
|
|
430
|
+
if (!this.#scene || !pipelineManager || this.#scene?.activeCamera === null) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const supportedPipelines = pipelineManager.supportedPipelines;
|
|
435
|
+
|
|
436
|
+
if (supportedPipelines === undefined) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!this.#renderPipelines.ssao) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const pipelineName = this.#renderPipelines.ssao.name;
|
|
445
|
+
const ssaoPipeline = supportedPipelines ? supportedPipelines.find((pipeline) => pipeline.name === pipelineName) : false;
|
|
446
|
+
|
|
447
|
+
if (ssaoPipeline) {
|
|
448
|
+
pipelineManager.detachCamerasFromRenderPipeline(pipelineName, [this.#scene.activeCamera]);
|
|
449
|
+
ssaoPipeline.dispose();
|
|
450
|
+
pipelineManager.removePipeline(pipelineName);
|
|
451
|
+
pipelineManager.update();
|
|
452
|
+
this.#renderPipelines.ssao = null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
|
|
341
458
|
/**
|
|
342
459
|
* Rebuilds the SSAO post-process pipeline to inject screenspace ambient occlusion on the active camera.
|
|
343
460
|
* Disposes previous SSAO pipelines, instantiates a tuned `SSAORenderingPipeline`, and attaches it to the
|
|
@@ -346,138 +463,166 @@ export default class BabylonJSController {
|
|
|
346
463
|
* @returns {boolean} True if the SSAO pipeline is supported and enabled, otherwise false.
|
|
347
464
|
*/
|
|
348
465
|
#initializeAmbientOcclussion() {
|
|
349
|
-
|
|
466
|
+
const pipelineManager = this.#scene?.postProcessRenderPipelineManager;
|
|
467
|
+
if (!this.#scene || !pipelineManager || this.#scene?.activeCamera === null) {
|
|
350
468
|
return false;
|
|
351
469
|
}
|
|
352
470
|
|
|
353
471
|
if (!this.#settings.ambientOcclusionEnabled) {
|
|
354
472
|
return false;
|
|
355
473
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const supportedPipelines = this.#scene.postProcessRenderPipelineManager.supportedPipelines;
|
|
360
|
-
|
|
361
|
-
if (!supportedPipelines) {
|
|
474
|
+
const supportedPipelines = pipelineManager.supportedPipelines;
|
|
475
|
+
|
|
476
|
+
if (supportedPipelines === undefined) {
|
|
362
477
|
return false;
|
|
363
478
|
}
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
if (oldSsaoPipeline) {
|
|
368
|
-
oldSsaoPipeline.dispose();
|
|
369
|
-
this.#scene.postProcessRenderPipelineManager.update();
|
|
370
|
-
}
|
|
479
|
+
|
|
480
|
+
const pipelineName = "PrefViewerSSAORenderingPipeline";
|
|
371
481
|
|
|
372
482
|
const ssaoRatio = {
|
|
373
483
|
ssaoRatio: 0.5,
|
|
374
484
|
combineRatio: 1.0
|
|
375
485
|
};
|
|
376
486
|
|
|
377
|
-
|
|
487
|
+
this.#renderPipelines.ssao = new SSAORenderingPipeline(pipelineName, this.#scene, ssaoRatio, [this.#scene.activeCamera]);
|
|
378
488
|
|
|
379
|
-
if (!
|
|
489
|
+
if (!this.#renderPipelines.ssao){
|
|
380
490
|
return false;
|
|
381
491
|
}
|
|
382
492
|
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
493
|
+
if (this.#renderPipelines.ssao.isSupported) {
|
|
494
|
+
this.#renderPipelines.ssao.fallOff = 0.000001;
|
|
495
|
+
this.#renderPipelines.ssao.area = 1;
|
|
496
|
+
this.#renderPipelines.ssao.radius = 0.0001;
|
|
497
|
+
this.#renderPipelines.ssao.totalStrength = 1;
|
|
498
|
+
this.#renderPipelines.ssao.base = 0.6;
|
|
389
499
|
|
|
390
500
|
// Configure SSAO to calculate only once instead of every frame for better performance
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
501
|
+
if (this.#renderPipelines.ssao._ssaoPostProcess) {
|
|
502
|
+
this.#renderPipelines.ssao._ssaoPostProcess.autoClear = false;
|
|
503
|
+
this.#renderPipelines.ssao._ssaoPostProcess.samples = 1;
|
|
504
|
+
}
|
|
505
|
+
if (this.#renderPipelines.ssao._combinePostProcess) {
|
|
506
|
+
this.#renderPipelines.ssao._combinePostProcess.autoClear = false;
|
|
507
|
+
this.#renderPipelines.ssao._combinePostProcess.samples = 1;
|
|
394
508
|
}
|
|
395
509
|
|
|
396
|
-
|
|
510
|
+
pipelineManager.update();
|
|
397
511
|
return true;
|
|
398
512
|
} else {
|
|
399
|
-
|
|
400
|
-
this.#
|
|
513
|
+
this.#renderPipelines.ssao.dispose();
|
|
514
|
+
this.#renderPipelines.ssao = null;
|
|
515
|
+
pipelineManager.update();
|
|
401
516
|
return false;
|
|
402
517
|
}
|
|
403
518
|
}
|
|
404
519
|
|
|
405
520
|
/**
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* `DefaultRenderingPipeline` with tuned settings for sharper anti-aliasing and subtle grain.
|
|
521
|
+
* Tears down the default rendering pipeline (MSAA/FXAA/grain) for the active camera when present.
|
|
522
|
+
* Ensures stale pipelines detach cleanly so a fresh one can be installed on the next load.
|
|
409
523
|
* @private
|
|
410
|
-
* @returns {boolean}
|
|
411
|
-
* @see {@link https://doc.babylonjs.com/features/featuresDeepDive/postProcesses/defaultRenderingPipeline|Using the Default Rendering Pipeline | Babylon.js Documentation}
|
|
524
|
+
* @returns {boolean} Returns true when the pipeline was removed, false otherwise.
|
|
412
525
|
*/
|
|
413
|
-
#
|
|
414
|
-
|
|
526
|
+
#disableVisualImprovements() {
|
|
527
|
+
const pipelineManager = this.#scene?.postProcessRenderPipelineManager;
|
|
528
|
+
|
|
529
|
+
if (!this.#scene || !pipelineManager || this.#scene?.activeCamera === null) {
|
|
415
530
|
return false;
|
|
416
531
|
}
|
|
417
532
|
|
|
418
|
-
const
|
|
419
|
-
|
|
533
|
+
const supportedPipelines = pipelineManager.supportedPipelines;
|
|
534
|
+
|
|
535
|
+
if (supportedPipelines === undefined) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
420
538
|
|
|
421
|
-
if (!
|
|
539
|
+
if (!this.#renderPipelines.default) {
|
|
422
540
|
return false;
|
|
423
541
|
}
|
|
424
542
|
|
|
425
|
-
const
|
|
543
|
+
const pipelineName = this.#renderPipelines.default.name;
|
|
544
|
+
const defaultPipeline = supportedPipelines ? supportedPipelines.find((pipeline) => pipeline.name === pipelineName) : false;
|
|
545
|
+
|
|
546
|
+
if (defaultPipeline) {
|
|
547
|
+
pipelineManager.detachCamerasFromRenderPipeline(pipelineName, [this.#scene.activeCamera]);
|
|
548
|
+
defaultPipeline.dispose();
|
|
549
|
+
pipelineManager.removePipeline(pipelineName);
|
|
550
|
+
pipelineManager.update();
|
|
551
|
+
this.#renderPipelines.default = null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
426
556
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
557
|
+
/**
|
|
558
|
+
* Rebuilds the custom default rendering pipeline (MSAA, FXAA, film grain) for the active camera.
|
|
559
|
+
* Disposes any previous pipeline instance to avoid duplicates, then attaches a fresh
|
|
560
|
+
* `DefaultRenderingPipeline` with tuned settings for sharper anti-aliasing and subtle grain.
|
|
561
|
+
* @private
|
|
562
|
+
* @returns {boolean} True when the pipeline is supported and active, otherwise false.
|
|
563
|
+
* @see {@link https://doc.babylonjs.com/features/featuresDeepDive/postProcesses/defaultRenderingPipeline|Using the Default Rendering Pipeline | Babylon.js Documentation}
|
|
564
|
+
*/
|
|
565
|
+
#initializeVisualImprovements() {
|
|
566
|
+
const pipelineManager = this.#scene?.postProcessRenderPipelineManager;
|
|
567
|
+
if (!this.#scene || !pipelineManager || this.#scene?.activeCamera === null) {
|
|
568
|
+
return false;
|
|
430
569
|
}
|
|
431
570
|
|
|
432
571
|
if (!this.#settings.antiAliasingEnabled) {
|
|
433
572
|
return false;
|
|
434
573
|
}
|
|
574
|
+
const supportedPipelines = pipelineManager.supportedPipelines;
|
|
575
|
+
|
|
576
|
+
if (supportedPipelines === undefined) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const pipelineName = "PrefViewerDefaultRenderingPipeline";
|
|
435
581
|
|
|
436
|
-
|
|
582
|
+
this.#renderPipelines.default = new DefaultRenderingPipeline(pipelineName, true, this.#scene, [this.#scene.activeCamera], true);
|
|
437
583
|
|
|
438
|
-
if (!
|
|
584
|
+
if (!this.#renderPipelines.default){
|
|
439
585
|
return false;
|
|
440
586
|
}
|
|
441
587
|
|
|
442
|
-
if (
|
|
588
|
+
if (this.#renderPipelines.default.isSupported) {
|
|
443
589
|
// MSAA - Multisample Anti-Aliasing
|
|
444
590
|
const caps = this.#scene.getEngine()?.getCaps?.() || {};
|
|
445
591
|
const maxSamples = typeof caps.maxMSAASamples === "number" ? caps.maxMSAASamples : 4;
|
|
446
|
-
|
|
447
|
-
|
|
592
|
+
this.#renderPipelines.default.samples = Math.max(1, Math.min(8, maxSamples));
|
|
448
593
|
// FXAA - Fast Approximate Anti-Aliasing
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if (
|
|
453
|
-
|
|
594
|
+
this.#renderPipelines.default.fxaaEnabled = true;
|
|
595
|
+
this.#renderPipelines.default.fxaa.samples = 8;
|
|
596
|
+
this.#renderPipelines.default.fxaa.adaptScaleToCurrentViewport = true;
|
|
597
|
+
if (this.#renderPipelines.default.fxaa.edgeThreshold !== undefined) {
|
|
598
|
+
this.#renderPipelines.default.fxaa.edgeThreshold = 0.125;
|
|
454
599
|
}
|
|
455
|
-
if (
|
|
456
|
-
|
|
600
|
+
if (this.#renderPipelines.default.fxaa.edgeThresholdMin !== undefined) {
|
|
601
|
+
this.#renderPipelines.default.fxaa.edgeThresholdMin = 0.0625;
|
|
457
602
|
}
|
|
458
|
-
if (
|
|
459
|
-
|
|
603
|
+
if (this.#renderPipelines.default.fxaa.subPixelQuality !== undefined) {
|
|
604
|
+
this.#renderPipelines.default.fxaa.subPixelQuality = 0.75;
|
|
460
605
|
}
|
|
461
606
|
|
|
462
607
|
// Grain
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
608
|
+
this.#renderPipelines.default.grainEnabled = true;
|
|
609
|
+
this.#renderPipelines.default.grain.adaptScaleToCurrentViewport = true;
|
|
610
|
+
this.#renderPipelines.default.grain.animated = false;
|
|
611
|
+
this.#renderPipelines.default.grain.intensity = 3;
|
|
467
612
|
|
|
468
613
|
// Configure post-processes to calculate only once instead of every frame for better performance
|
|
469
|
-
if (
|
|
470
|
-
|
|
614
|
+
if (this.#renderPipelines.default.fxaa?._postProcess) {
|
|
615
|
+
this.#renderPipelines.default.fxaa._postProcess.autoClear = false;
|
|
471
616
|
}
|
|
472
|
-
if (
|
|
473
|
-
|
|
617
|
+
if (this.#renderPipelines.default.grain?._postProcess) {
|
|
618
|
+
this.#renderPipelines.default.grain._postProcess.autoClear = false;
|
|
474
619
|
}
|
|
475
620
|
|
|
476
|
-
|
|
621
|
+
pipelineManager.update();
|
|
477
622
|
return true;
|
|
478
623
|
} else {
|
|
479
|
-
|
|
480
|
-
|
|
624
|
+
this.#renderPipelines.default.dispose();
|
|
625
|
+
pipelineManager.update();
|
|
481
626
|
return false;
|
|
482
627
|
}
|
|
483
628
|
}
|
|
@@ -497,6 +642,43 @@ export default class BabylonJSController {
|
|
|
497
642
|
return true;
|
|
498
643
|
}
|
|
499
644
|
|
|
645
|
+
/**
|
|
646
|
+
* Removes the IBL shadow render pipeline from the active camera when present.
|
|
647
|
+
* Ensures voxelized shadow data is disposed so reloading environments installs a clean pipeline.
|
|
648
|
+
* @private
|
|
649
|
+
* @returns {boolean} Returns true when the pipeline was removed, false otherwise.
|
|
650
|
+
*/
|
|
651
|
+
#disableIBLShadows() {
|
|
652
|
+
const pipelineManager = this.#scene?.postProcessRenderPipelineManager;
|
|
653
|
+
|
|
654
|
+
if (!this.#scene || !pipelineManager || this.#scene?.activeCamera === null) {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const supportedPipelines = pipelineManager.supportedPipelines;
|
|
659
|
+
|
|
660
|
+
if (supportedPipelines === undefined) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (!this.#renderPipelines.iblShadows) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const pipelineName = this.#renderPipelines.iblShadows.name;
|
|
669
|
+
const defaultPipeline = supportedPipelines ? supportedPipelines.find((pipeline) => pipeline.name === pipelineName) : false;
|
|
670
|
+
|
|
671
|
+
if (defaultPipeline) {
|
|
672
|
+
pipelineManager.detachCamerasFromRenderPipeline(pipelineName, [this.#scene.activeCamera]);
|
|
673
|
+
defaultPipeline.dispose();
|
|
674
|
+
pipelineManager.removePipeline(pipelineName);
|
|
675
|
+
pipelineManager.update();
|
|
676
|
+
this.#renderPipelines.iblShadows = null;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
|
|
500
682
|
/**
|
|
501
683
|
* Initializes the Image-Based Lighting (IBL) shadows for the Babylon.js scene.
|
|
502
684
|
* Creates an IBL shadow render pipeline and adds all relevant meshes and materials for shadow casting and receiving.
|
|
@@ -506,27 +688,22 @@ export default class BabylonJSController {
|
|
|
506
688
|
* @returns {void|false} Returns false if no environment texture is set; otherwise void.
|
|
507
689
|
*/
|
|
508
690
|
#initializeIBLShadows() {
|
|
509
|
-
|
|
691
|
+
const pipelineManager = this.#scene?.postProcessRenderPipelineManager;
|
|
692
|
+
|
|
693
|
+
if (!this.#scene || !pipelineManager || this.#scene?.activeCamera === null) {
|
|
510
694
|
return false;
|
|
511
695
|
}
|
|
512
696
|
|
|
513
|
-
// if (!this.#scene.environmentTexture || !this.#scene.environmentTexture.isReady()) {
|
|
514
697
|
if (!this.#scene.environmentTexture) {
|
|
515
698
|
return false;
|
|
516
699
|
}
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
700
|
+
const supportedPipelines = pipelineManager.supportedPipelines;
|
|
701
|
+
|
|
520
702
|
if (!supportedPipelines) {
|
|
521
703
|
return false;
|
|
522
704
|
}
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
if (oldIblShadowsRenderPipeline) {
|
|
527
|
-
oldIblShadowsRenderPipeline.dispose();
|
|
528
|
-
this.#scene.postProcessRenderPipelineManager.update();
|
|
529
|
-
}
|
|
705
|
+
|
|
706
|
+
const pipelineName = "PrefViewerIblShadowsRenderPipeline";
|
|
530
707
|
|
|
531
708
|
const pipelineOptions = {
|
|
532
709
|
resolutionExp: 1, // Higher resolution for better shadow quality (recomended 8)
|
|
@@ -537,13 +714,13 @@ export default class BabylonJSController {
|
|
|
537
714
|
shadowOpacity: 0.85,
|
|
538
715
|
};
|
|
539
716
|
|
|
540
|
-
|
|
717
|
+
this.#renderPipelines.iblShadows = new IblShadowsRenderPipeline(pipelineName, this.#scene, pipelineOptions, [this.#scene.activeCamera]);
|
|
541
718
|
|
|
542
|
-
if (!
|
|
719
|
+
if (!this.#renderPipelines.iblShadows) {
|
|
543
720
|
return false;
|
|
544
721
|
}
|
|
545
722
|
|
|
546
|
-
if (
|
|
723
|
+
if (this.#renderPipelines.iblShadows.isSupported) {
|
|
547
724
|
// Disable all debug passes for performance
|
|
548
725
|
const pipelineProps = {
|
|
549
726
|
allowDebugPasses: false,
|
|
@@ -557,11 +734,11 @@ export default class BabylonJSController {
|
|
|
557
734
|
accumulationPassDebugEnabled: false,
|
|
558
735
|
};
|
|
559
736
|
|
|
560
|
-
Object.assign(
|
|
737
|
+
Object.assign(this.#renderPipelines.iblShadows, pipelineProps);
|
|
561
738
|
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
|
|
739
|
+
if (this.#renderPipelines.iblShadows._ssaoPostProcess) {
|
|
740
|
+
this.#renderPipelines.iblShadows._ssaoPostProcess.autoClear = false;
|
|
741
|
+
this.#renderPipelines.iblShadows._ssaoPostProcess.samples = 1;
|
|
565
742
|
}
|
|
566
743
|
|
|
567
744
|
this.#scene.meshes.forEach((mesh) => {
|
|
@@ -575,8 +752,8 @@ export default class BabylonJSController {
|
|
|
575
752
|
const meshGenerateShadows = typeof extrasCastShadows === "boolean" ? extrasCastShadows : isHDRIMesh ? false : true;
|
|
576
753
|
|
|
577
754
|
if (meshGenerateShadows) {
|
|
578
|
-
|
|
579
|
-
|
|
755
|
+
this.#renderPipelines.iblShadows.addShadowCastingMesh(mesh);
|
|
756
|
+
this.#renderPipelines.iblShadows.updateSceneBounds();
|
|
580
757
|
}
|
|
581
758
|
});
|
|
582
759
|
|
|
@@ -584,15 +761,15 @@ export default class BabylonJSController {
|
|
|
584
761
|
if (material instanceof PBRMaterial) {
|
|
585
762
|
material.enableSpecularAntiAliasing = false;
|
|
586
763
|
}
|
|
587
|
-
|
|
764
|
+
this.#renderPipelines.iblShadows.addShadowReceivingMaterial(material);
|
|
588
765
|
});
|
|
589
766
|
|
|
590
|
-
|
|
591
|
-
|
|
767
|
+
this.#renderPipelines.iblShadows.updateVoxelization();
|
|
768
|
+
pipelineManager.update();
|
|
592
769
|
return true;
|
|
593
770
|
} else {
|
|
594
|
-
|
|
595
|
-
|
|
771
|
+
this.#renderPipelines.iblShadows.dispose();
|
|
772
|
+
pipelineManager.update();
|
|
596
773
|
return false;
|
|
597
774
|
}
|
|
598
775
|
}
|
|
@@ -626,7 +803,6 @@ export default class BabylonJSController {
|
|
|
626
803
|
* @returns {void}
|
|
627
804
|
*/
|
|
628
805
|
#initializeDefaultLightShadows() {
|
|
629
|
-
this.#shadowGen = [];
|
|
630
806
|
if (!this.#dirLight) {
|
|
631
807
|
return;
|
|
632
808
|
}
|
|
@@ -692,6 +868,20 @@ export default class BabylonJSController {
|
|
|
692
868
|
});
|
|
693
869
|
}
|
|
694
870
|
|
|
871
|
+
/**
|
|
872
|
+
* Disposes every active shadow generator plus the IBL shadow pipeline to avoid stale casters across reloads.
|
|
873
|
+
* Clears the cached `#shadowGen` array so subsequent loads can rebuild fresh generators.
|
|
874
|
+
* @private
|
|
875
|
+
* @returns {void}
|
|
876
|
+
*/
|
|
877
|
+
#disableShadows() {
|
|
878
|
+
this.#shadowGen.forEach((shadowGenerator) => {
|
|
879
|
+
shadowGenerator.dispose();
|
|
880
|
+
});
|
|
881
|
+
this.#shadowGen = [];
|
|
882
|
+
this.#disableIBLShadows();
|
|
883
|
+
}
|
|
884
|
+
|
|
695
885
|
/**
|
|
696
886
|
* Initializes shadows for the Babylon.js scene.
|
|
697
887
|
* @private
|
|
@@ -839,7 +1029,6 @@ export default class BabylonJSController {
|
|
|
839
1029
|
this.#engine.dispose();
|
|
840
1030
|
this.#engine = this.#scene = this.#camera = null;
|
|
841
1031
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
842
|
-
this.#shadowGen = [];
|
|
843
1032
|
}
|
|
844
1033
|
|
|
845
1034
|
/**
|
|
@@ -1207,6 +1396,7 @@ export default class BabylonJSController {
|
|
|
1207
1396
|
*/
|
|
1208
1397
|
#stopRender() {
|
|
1209
1398
|
this.#engine.stopRenderLoop(this.#handlers.renderLoop);
|
|
1399
|
+
this.#unloadCameraDependentEffects();
|
|
1210
1400
|
}
|
|
1211
1401
|
/**
|
|
1212
1402
|
* Starts the Babylon.js render loop for the current scene.
|
|
@@ -1215,6 +1405,7 @@ export default class BabylonJSController {
|
|
|
1215
1405
|
* @returns {Promise<void>}
|
|
1216
1406
|
*/
|
|
1217
1407
|
async #startRender() {
|
|
1408
|
+
this.#loadCameraDependentEffects();
|
|
1218
1409
|
await this.#scene.whenReadyAsync();
|
|
1219
1410
|
this.#engine.runRenderLoop(this.#handlers.renderLoop);
|
|
1220
1411
|
}
|
|
@@ -1285,7 +1476,6 @@ export default class BabylonJSController {
|
|
|
1285
1476
|
*/
|
|
1286
1477
|
async #loadContainers() {
|
|
1287
1478
|
this.#stopRender();
|
|
1288
|
-
this.#scene.postProcessRenderPipelineManager?.dispose();
|
|
1289
1479
|
|
|
1290
1480
|
let oldModelMetadata = { ...(this.#containers.model?.state?.metadata ?? {}) };
|
|
1291
1481
|
let newModelMetadata = {};
|
|
@@ -1311,6 +1501,13 @@ export default class BabylonJSController {
|
|
|
1311
1501
|
assetContainer.lights = [];
|
|
1312
1502
|
newModelMetadata = { ...(container.state.update.metadata ?? {}) };
|
|
1313
1503
|
}
|
|
1504
|
+
if (container.state.name === "model" || container.state.name === "environment") {
|
|
1505
|
+
assetContainer.cameras.forEach((camera) => {
|
|
1506
|
+
// To avoid conflicts when reloading the model we rename the id because Babylon.js caches the camera's SSAO effect by id.
|
|
1507
|
+
const sufix = "_" + Date.now();
|
|
1508
|
+
camera.id = `${camera.id || camera.name || "camera"}${sufix}`;
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1314
1511
|
this.#replaceContainer(container, assetContainer);
|
|
1315
1512
|
container.state.setSuccess(true);
|
|
1316
1513
|
} else {
|
|
@@ -1336,7 +1533,6 @@ export default class BabylonJSController {
|
|
|
1336
1533
|
.finally(async () => {
|
|
1337
1534
|
this.#checkModelMetadata(oldModelMetadata, newModelMetadata);
|
|
1338
1535
|
this.#setMaxSimultaneousLights();
|
|
1339
|
-
this.#loadCameraDepentEffects();
|
|
1340
1536
|
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#containers.model.assetContainer);
|
|
1341
1537
|
this.#startRender();
|
|
1342
1538
|
});
|
|
@@ -1349,12 +1545,24 @@ export default class BabylonJSController {
|
|
|
1349
1545
|
* @private
|
|
1350
1546
|
* @returns {void}
|
|
1351
1547
|
*/
|
|
1352
|
-
#
|
|
1548
|
+
#loadCameraDependentEffects() {
|
|
1353
1549
|
this.#initializeVisualImprovements();
|
|
1354
1550
|
this.#initializeAmbientOcclussion();
|
|
1355
1551
|
this.#initializeShadows();
|
|
1356
1552
|
}
|
|
1357
1553
|
|
|
1554
|
+
/**
|
|
1555
|
+
* Shuts down every post-process tied to the active camera before stopping the render loop.
|
|
1556
|
+
* Ensures AA, SSAO, and shadow resources detach cleanly so reloads rebuild from scratch.
|
|
1557
|
+
* @private
|
|
1558
|
+
* @returns {void}
|
|
1559
|
+
*/
|
|
1560
|
+
#unloadCameraDependentEffects() {
|
|
1561
|
+
this.#disableVisualImprovements();
|
|
1562
|
+
this.#disableAmbientOcclusion();
|
|
1563
|
+
this.#disableShadows();
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1358
1566
|
/**
|
|
1359
1567
|
* Checks and applies model metadata changes after asset loading.
|
|
1360
1568
|
* @private
|
|
@@ -1512,16 +1720,18 @@ export default class BabylonJSController {
|
|
|
1512
1720
|
return;
|
|
1513
1721
|
}
|
|
1514
1722
|
|
|
1515
|
-
const
|
|
1723
|
+
const locale = this.#prefViewer.culture;
|
|
1724
|
+
const texts = translate("prefViewer.downloadDialog", locale ? { locale } : undefined) || {};
|
|
1725
|
+
const header = texts.title || "Download 3D Scene";
|
|
1516
1726
|
const content = `
|
|
1517
1727
|
<form slot="content" id="download-dialog-form" style="display:flex;flex-direction:column;gap:16px;">
|
|
1518
|
-
<h4
|
|
1728
|
+
<h4>${texts.sections?.content || "Content"}</h4>
|
|
1519
1729
|
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
1520
|
-
<label><input type="radio" name="content" value="1"> Model</label>
|
|
1521
|
-
<label><input type="radio" name="content" value="2"> Scene</label>
|
|
1522
|
-
<label><input type="radio" name="content" value="0" checked> Both</label>
|
|
1730
|
+
<label><input type="radio" name="content" value="1"> ${texts.options?.model || "Model"}</label>
|
|
1731
|
+
<label><input type="radio" name="content" value="2"> ${texts.options?.scene || "Scene"}</label>
|
|
1732
|
+
<label><input type="radio" name="content" value="0" checked> ${texts.options?.both || "Both"}</label>
|
|
1523
1733
|
</div>
|
|
1524
|
-
<h4
|
|
1734
|
+
<h4>${texts.sections?.format || "Format"}</h4>
|
|
1525
1735
|
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
1526
1736
|
<label><input type="radio" name="format" value="gltf" checked> glTF (ZIP)</label>
|
|
1527
1737
|
<label><input type="radio" name="format" value="glb"> GLB</label>
|
|
@@ -1529,8 +1739,8 @@ export default class BabylonJSController {
|
|
|
1529
1739
|
</div>
|
|
1530
1740
|
</form>`;
|
|
1531
1741
|
const footer = `
|
|
1532
|
-
<button type="button" class="primary" id="download-dialog-download"
|
|
1533
|
-
<button type="button" id="download-dialog-cancel"
|
|
1742
|
+
<button type="button" class="primary" id="download-dialog-download">${texts.buttons?.download || "Download"}</button>
|
|
1743
|
+
<button type="button" id="download-dialog-cancel">${texts.buttons?.cancel || "Cancel"}</button>`;
|
|
1534
1744
|
|
|
1535
1745
|
const dialog = this.#prefViewer.openDialog(header, content, footer);
|
|
1536
1746
|
|
|
@@ -1614,84 +1824,10 @@ export default class BabylonJSController {
|
|
|
1614
1824
|
this.#disableInteraction();
|
|
1615
1825
|
this.#disposeAnimationController();
|
|
1616
1826
|
this.#disposeXRExperience();
|
|
1827
|
+
this.#unloadCameraDependentEffects();
|
|
1617
1828
|
this.#disposeEngine();
|
|
1618
1829
|
}
|
|
1619
1830
|
|
|
1620
|
-
/**
|
|
1621
|
-
* Loads all asset containers (model, environment, materials, etc.) and adds them to the scene.
|
|
1622
|
-
* Applies material and camera options, sets visibility, and initializes lights and shadows.
|
|
1623
|
-
* @public
|
|
1624
|
-
* @returns {Promise<boolean>} Resolves to true if loading succeeds, false otherwise.
|
|
1625
|
-
*/
|
|
1626
|
-
async load() {
|
|
1627
|
-
return await this.#loadContainers();
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
/**
|
|
1631
|
-
* Applies camera options from the configuration to the active camera.
|
|
1632
|
-
* Stops and restarts the render loop to apply changes.
|
|
1633
|
-
* @public
|
|
1634
|
-
* @returns {boolean} True if camera options were set successfully, false otherwise.
|
|
1635
|
-
*/
|
|
1636
|
-
setCameraOptions() {
|
|
1637
|
-
this.#stopRender();
|
|
1638
|
-
const cameraOptionsSetted = this.#setOptions_Camera();
|
|
1639
|
-
this.#loadCameraDepentEffects();
|
|
1640
|
-
this.#startRender();
|
|
1641
|
-
return cameraOptionsSetted;
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
/**
|
|
1645
|
-
* Applies material options from the configuration to the relevant meshes.
|
|
1646
|
-
* Stops and restarts the render loop to apply changes.
|
|
1647
|
-
* @public
|
|
1648
|
-
* @returns {boolean} True if material options were set successfully, false otherwise.
|
|
1649
|
-
*/
|
|
1650
|
-
setMaterialOptions() {
|
|
1651
|
-
this.#stopRender();
|
|
1652
|
-
const materialsOptionsSetted = this.#setOptions_Materials();
|
|
1653
|
-
this.#startRender();
|
|
1654
|
-
return materialsOptionsSetted;
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
/**
|
|
1658
|
-
* Reapplies image-based lighting configuration (HDR URL, intensity, shadow mode).
|
|
1659
|
-
* Stops rendering, pushes pending IBL state into the scene, rebuilds camera-dependent effects, then resumes rendering.
|
|
1660
|
-
* @public
|
|
1661
|
-
* @returns {void}
|
|
1662
|
-
*/
|
|
1663
|
-
setIBLOptions() {
|
|
1664
|
-
this.#stopRender();
|
|
1665
|
-
const IBLOptionsSetted = this.#setOptions_IBL();
|
|
1666
|
-
this.#loadCameraDepentEffects();
|
|
1667
|
-
this.#startRender();
|
|
1668
|
-
return IBLOptionsSetted;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
/**
|
|
1672
|
-
* Sets the visibility of a container (model, environment, etc.) by name.
|
|
1673
|
-
* Adds or removes the container from the scene and updates wall/floor visibility.
|
|
1674
|
-
* Restarts the render loop to apply changes.
|
|
1675
|
-
* @public
|
|
1676
|
-
* @param {string} name - The name of the container to show or hide.
|
|
1677
|
-
* @param {boolean} show - True to show the container, false to hide it.
|
|
1678
|
-
* @returns {void}
|
|
1679
|
-
*/
|
|
1680
|
-
setContainerVisibility(name, show) {
|
|
1681
|
-
const container = this.#findContainerByName(name);
|
|
1682
|
-
if (!container) {
|
|
1683
|
-
return;
|
|
1684
|
-
}
|
|
1685
|
-
if (container.state.show === show && container.state.visible === show) {
|
|
1686
|
-
return;
|
|
1687
|
-
}
|
|
1688
|
-
container.state.show = show;
|
|
1689
|
-
this.#stopRender();
|
|
1690
|
-
show ? this.#addContainer(container) : this.#removeContainer(container);
|
|
1691
|
-
this.#setVisibilityOfWallAndFloorInModel();
|
|
1692
|
-
this.#startRender();
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
1831
|
/**
|
|
1696
1832
|
* Downloads the current scene, model, or environment as a GLB file.
|
|
1697
1833
|
* @public
|
|
@@ -1794,4 +1930,116 @@ export default class BabylonJSController {
|
|
|
1794
1930
|
}
|
|
1795
1931
|
});
|
|
1796
1932
|
}
|
|
1933
|
+
|
|
1934
|
+
/**
|
|
1935
|
+
* Returns a shallow copy of the current render settings so callers can read the active state without mutating the controller internals.
|
|
1936
|
+
* @public
|
|
1937
|
+
* @returns {{antiAliasingEnabled:boolean, ambientOcclusionEnabled:boolean, iblEnabled:boolean, shadowsEnabled:boolean}}
|
|
1938
|
+
*/
|
|
1939
|
+
getRenderSettings() {
|
|
1940
|
+
return { ...this.#settings };
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/**
|
|
1944
|
+
* Loads all asset containers (model, environment, materials, etc.) and adds them to the scene.
|
|
1945
|
+
* Applies material and camera options, sets visibility, and initializes lights and shadows.
|
|
1946
|
+
* @public
|
|
1947
|
+
* @returns {Promise<boolean>} Resolves to true if loading succeeds, false otherwise.
|
|
1948
|
+
*/
|
|
1949
|
+
async load() {
|
|
1950
|
+
return await this.#loadContainers();
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
/**
|
|
1954
|
+
* Merges incoming render flags with the current configuration, persists them, and marks
|
|
1955
|
+
* all dependent loaders/options as pending when something actually changed.
|
|
1956
|
+
* @public
|
|
1957
|
+
* @param {{antiAliasingEnabled?:boolean, ambientOcclusionEnabled?:boolean, iblEnabled?:boolean, shadowsEnabled?:boolean}} settings Partial set of render toggles to apply.
|
|
1958
|
+
* @returns {{changed:boolean, settings:{antiAliasingEnabled:boolean, ambientOcclusionEnabled:boolean, iblEnabled:boolean, shadowsEnabled:boolean}}}
|
|
1959
|
+
* @description
|
|
1960
|
+
* Callers can inspect the `changed` flag to decide whether to trigger a reload with
|
|
1961
|
+
* `reloadWithCurrentSettings()` or simply reuse the returned snapshot.
|
|
1962
|
+
*/
|
|
1963
|
+
scheduleRenderSettingsReload(settings = {}) {
|
|
1964
|
+
const changed = this.#applyRenderSettings(settings);
|
|
1965
|
+
if (!changed) {
|
|
1966
|
+
return { changed: false, settings: this.getRenderSettings() };
|
|
1967
|
+
}
|
|
1968
|
+
this.#markContainersForReload();
|
|
1969
|
+
this.#markOptionsForReload();
|
|
1970
|
+
return { changed: true, settings: this.getRenderSettings() };
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/**
|
|
1974
|
+
* Applies camera options from the configuration to the active camera.
|
|
1975
|
+
* Stops and restarts the render loop to apply changes.
|
|
1976
|
+
* @public
|
|
1977
|
+
* @returns {boolean} True if camera options were set successfully, false otherwise.
|
|
1978
|
+
*/
|
|
1979
|
+
setCameraOptions() {
|
|
1980
|
+
this.#stopRender();
|
|
1981
|
+
const cameraOptionsSetted = this.#setOptions_Camera();
|
|
1982
|
+
this.#startRender();
|
|
1983
|
+
return cameraOptionsSetted;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/**
|
|
1987
|
+
* Applies material options from the configuration to the relevant meshes.
|
|
1988
|
+
* Stops and restarts the render loop to apply changes.
|
|
1989
|
+
* @public
|
|
1990
|
+
* @returns {boolean} True if material options were set successfully, false otherwise.
|
|
1991
|
+
*/
|
|
1992
|
+
setMaterialOptions() {
|
|
1993
|
+
this.#stopRender();
|
|
1994
|
+
const materialsOptionsSetted = this.#setOptions_Materials();
|
|
1995
|
+
this.#startRender();
|
|
1996
|
+
return materialsOptionsSetted;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
/**
|
|
2000
|
+
* Reapplies image-based lighting configuration (HDR URL, intensity, shadow mode).
|
|
2001
|
+
* Stops rendering, pushes pending IBL state into the scene, rebuilds camera-dependent effects, then resumes rendering.
|
|
2002
|
+
* @public
|
|
2003
|
+
* @returns {void}
|
|
2004
|
+
*/
|
|
2005
|
+
setIBLOptions() {
|
|
2006
|
+
this.#stopRender();
|
|
2007
|
+
const IBLOptionsSetted = this.#setOptions_IBL();
|
|
2008
|
+
this.#startRender();
|
|
2009
|
+
return IBLOptionsSetted;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
/**
|
|
2013
|
+
* Sets the visibility of a container (model, environment, etc.) by name.
|
|
2014
|
+
* Adds or removes the container from the scene and updates wall/floor visibility.
|
|
2015
|
+
* Restarts the render loop to apply changes.
|
|
2016
|
+
* @public
|
|
2017
|
+
* @param {string} name - The name of the container to show or hide.
|
|
2018
|
+
* @param {boolean} show - True to show the container, false to hide it.
|
|
2019
|
+
* @returns {void}
|
|
2020
|
+
*/
|
|
2021
|
+
setContainerVisibility(name, show) {
|
|
2022
|
+
const container = this.#findContainerByName(name);
|
|
2023
|
+
if (!container) {
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
if (container.state.show === show && container.state.visible === show) {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
container.state.show = show;
|
|
2030
|
+
this.#stopRender();
|
|
2031
|
+
show ? this.#addContainer(container) : this.#removeContainer(container);
|
|
2032
|
+
this.#setVisibilityOfWallAndFloorInModel();
|
|
2033
|
+
this.#startRender();
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
/**
|
|
2037
|
+
* Reloads every asset container using the latest staged render settings.
|
|
2038
|
+
* Intended to be called after `scheduleRenderSettingsReload()` marks data as pending.
|
|
2039
|
+
* @public
|
|
2040
|
+
* @returns {Promise<{success:boolean,error:any}>} Resolves with the same status object returned by `load()`.
|
|
2041
|
+
*/
|
|
2042
|
+
async reloadWithCurrentSettings() {
|
|
2043
|
+
return await this.#loadContainers();
|
|
2044
|
+
}
|
|
1797
2045
|
}
|