@preference-sl/pref-viewer 2.12.0 → 2.13.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preference-sl/pref-viewer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0-beta.0",
|
|
4
4
|
"description": "Web Component to preview GLTF models with Babylon.js",
|
|
5
5
|
"author": "Alex Moreno Palacio <amoreno@preference.es>",
|
|
6
6
|
"scripts": {
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"index.d.ts"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@babylonjs/core": "^8.
|
|
39
|
-
"@babylonjs/loaders": "^8.
|
|
40
|
-
"@babylonjs/serializers": "^8.
|
|
38
|
+
"@babylonjs/core": "^8.46.2",
|
|
39
|
+
"@babylonjs/loaders": "^8.46.2",
|
|
40
|
+
"@babylonjs/serializers": "^8.46.2",
|
|
41
41
|
"@panzoom/panzoom": "^4.6.0",
|
|
42
|
-
"babylonjs-gltf2interface": "^8.
|
|
42
|
+
"babylonjs-gltf2interface": "^8.46.2",
|
|
43
43
|
"buffer": "^6.0.3",
|
|
44
44
|
"idb": "^8.0.3",
|
|
45
45
|
"is-svg": "^6.1.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
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";
|
|
1
|
+
import { ArcRotateCamera, AssetContainer, Camera, Color4, DefaultRenderingPipeline, DirectionalLight, Engine, FreeCamera, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PBRMaterial, PointerEventTypes, PointLight, RenderTargetTexture, Scene, ShadowGenerator, SpotLight, SSAORenderingPipeline, Tools, UniversalCamera, 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";
|
|
@@ -44,7 +44,7 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
|
44
44
|
* - load(): Reloads every pending asset container, re-applies options, and resolves with the loading summary { success, error }.
|
|
45
45
|
* - setCameraOptions(): Applies the pending camera selection, reinstalls dependent pipelines, and restarts rendering safely.
|
|
46
46
|
* - setMaterialOptions(): Re-applies all configured material overrides across visible containers and restarts rendering.
|
|
47
|
-
* - setIBLOptions(): Pushes pending HDR/IBL updates, refreshes dependent effects,
|
|
47
|
+
* - setIBLOptions(): Pushes pending HDR/IBL updates, refreshes dependent effects, resumes the render loop, and reports whether anything changed.
|
|
48
48
|
* - setContainerVisibility(name, show): Toggles model/environment containers, syncing wall/floor helpers and component attributes.
|
|
49
49
|
* - downloadGLB(content): Exports the selected scope (scene/model/environment) into a time-stamped GLB and triggers the download.
|
|
50
50
|
* - downloadGLTF(content): Generates a glTF + BIN + textures ZIP for the requested scope, adding metadata comments for traceability.
|
|
@@ -62,9 +62,11 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
|
62
62
|
* - #initializeVisualImprovements(): Reinstalls the default rendering pipeline (MSAA/FXAA/grain).
|
|
63
63
|
* - #initializeEnvironmentTexture(): Loads and sets the HDR environment texture.
|
|
64
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.
|
|
65
66
|
* - #initializeShadows(): Sets up standard or IBL shadows for meshes.
|
|
66
67
|
* - #initializeDefaultLightShadows(): Configures soft shadows when no HDR environment exists.
|
|
67
68
|
* - #initializeEnvironmentShadows(): Rebuilds environment-provided shadow generators.
|
|
69
|
+
* - #ensureMeshesReceiveShadows(): Ensures every non-root mesh receives shadows unless metadata opts out.
|
|
68
70
|
* - #setMaxSimultaneousLights(): Updates max simultaneous lights for all materials.
|
|
69
71
|
* - #onPointerObservable(info): Handles pointer events and dispatches to pointer/mouse handlers.
|
|
70
72
|
* - #onPointerUp(event, pickInfo): Handles pointer up events (e.g., right-click for animation menu).
|
|
@@ -284,7 +286,7 @@ export default class BabylonJSController {
|
|
|
284
286
|
* @returns {void}
|
|
285
287
|
*/
|
|
286
288
|
#createCamera() {
|
|
287
|
-
this.#camera = new ArcRotateCamera("
|
|
289
|
+
this.#camera = new ArcRotateCamera("defaultCamera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
|
|
288
290
|
this.#camera.upperBetaLimit = Math.PI * 0.48;
|
|
289
291
|
this.#camera.lowerBetaLimit = Math.PI * 0.25;
|
|
290
292
|
this.#camera.lowerRadiusLimit = 5;
|
|
@@ -564,13 +566,18 @@ export default class BabylonJSController {
|
|
|
564
566
|
|
|
565
567
|
this.#scene.meshes.forEach((mesh) => {
|
|
566
568
|
const isRootMesh = mesh.id.startsWith("__root__");
|
|
567
|
-
|
|
568
|
-
if (isRootMesh || isHDRIMesh) {
|
|
569
|
+
if (isRootMesh) {
|
|
569
570
|
return false;
|
|
570
571
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
572
|
+
|
|
573
|
+
const isHDRIMesh = mesh.name?.toLowerCase() === "hdri";
|
|
574
|
+
const extrasCastShadows = mesh.metadata?.gltf?.extras?.castShadows;
|
|
575
|
+
const meshGenerateShadows = typeof extrasCastShadows === "boolean" ? extrasCastShadows : isHDRIMesh ? false : true;
|
|
576
|
+
|
|
577
|
+
if (meshGenerateShadows) {
|
|
578
|
+
iblShadowsRenderPipeline.addShadowCastingMesh(mesh);
|
|
579
|
+
iblShadowsRenderPipeline.updateSceneBounds();
|
|
580
|
+
}
|
|
574
581
|
});
|
|
575
582
|
|
|
576
583
|
this.#scene.materials.forEach((material) => {
|
|
@@ -590,6 +597,29 @@ export default class BabylonJSController {
|
|
|
590
597
|
}
|
|
591
598
|
}
|
|
592
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Adds the mesh to the provided shadow generator when it is eligible to cast shadows.
|
|
602
|
+
* Skips hidden Babylon root nodes, ignores HDRI domes, and honors the `castShadows` GLTF extra flag.
|
|
603
|
+
* @private
|
|
604
|
+
* @param {ShadowGenerator} shadowGenerator - Target generator that should receive the mesh.
|
|
605
|
+
* @param {AbstractMesh} mesh - Mesh to evaluate for shadow casting.
|
|
606
|
+
* @returns {boolean} True when the mesh is registered as a caster, otherwise false.
|
|
607
|
+
*/
|
|
608
|
+
#addMeshToShadowGenerator(shadowGenerator, mesh) {
|
|
609
|
+
const isRootMesh = mesh.id.startsWith("__root__");
|
|
610
|
+
if (isRootMesh) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
const isHDRIMesh = mesh.name?.toLowerCase() === "hdri";
|
|
614
|
+
const extrasCastShadows = mesh.metadata?.gltf?.extras?.castShadows;
|
|
615
|
+
const meshGenerateShadows = typeof extrasCastShadows === "boolean" ? extrasCastShadows : isHDRIMesh ? false : true;
|
|
616
|
+
if (meshGenerateShadows) {
|
|
617
|
+
shadowGenerator.addShadowCaster(mesh, true);
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
|
|
593
623
|
/**
|
|
594
624
|
* Configures soft shadows for the built-in directional light used when no HDR environment is present.
|
|
595
625
|
* @private
|
|
@@ -608,15 +638,7 @@ export default class BabylonJSController {
|
|
|
608
638
|
shadowGenerator.bias = 0.0005;
|
|
609
639
|
shadowGenerator.normalBias = 0.02;
|
|
610
640
|
shadowGenerator.getShadowMap().refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
|
|
611
|
-
this.#scene.meshes.forEach((mesh) =>
|
|
612
|
-
if (mesh.id.startsWith("__root__")) {
|
|
613
|
-
return false;
|
|
614
|
-
}
|
|
615
|
-
if (mesh.name?.toLowerCase() !== "hdri") {
|
|
616
|
-
shadowGenerator.addShadowCaster(mesh, true);
|
|
617
|
-
}
|
|
618
|
-
mesh.receiveShadows = true;
|
|
619
|
-
});
|
|
641
|
+
this.#scene.meshes.forEach((mesh) => this.#addMeshToShadowGenerator(shadowGenerator, mesh));
|
|
620
642
|
this.#shadowGen.push(shadowGenerator);
|
|
621
643
|
}
|
|
622
644
|
|
|
@@ -648,18 +670,28 @@ export default class BabylonJSController {
|
|
|
648
670
|
shadowGenerator.bias = 0.0005;
|
|
649
671
|
shadowGenerator.normalBias = 0.02;
|
|
650
672
|
shadowGenerator.getShadowMap().refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
|
|
651
|
-
this.#scene.meshes.forEach((mesh) =>
|
|
652
|
-
if (mesh.id.startsWith("__root__")) {
|
|
653
|
-
return false;
|
|
654
|
-
}
|
|
655
|
-
if (mesh.name?.toLowerCase() !== "hdri") {
|
|
656
|
-
shadowGenerator.addShadowCaster(mesh, true);
|
|
657
|
-
}
|
|
658
|
-
});
|
|
673
|
+
this.#scene.meshes.forEach((mesh) => this.#addMeshToShadowGenerator(shadowGenerator, mesh));
|
|
659
674
|
this.#shadowGen.push(shadowGenerator);
|
|
660
675
|
});
|
|
661
676
|
}
|
|
662
677
|
|
|
678
|
+
/**
|
|
679
|
+
* Marks every non-root mesh as shadow receiving, unless GLTF metadata explicitly disables it.
|
|
680
|
+
* Ensures standard shadow generators work even when extras are missing, while still honoring `receiveShadows` overrides.
|
|
681
|
+
* @private
|
|
682
|
+
* @returns {void}
|
|
683
|
+
*/
|
|
684
|
+
#ensureMeshesReceiveShadows() {
|
|
685
|
+
this.#scene.meshes.forEach((mesh) => {
|
|
686
|
+
const isRootMesh = mesh.id.startsWith("__root__");
|
|
687
|
+
if (isRootMesh) {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
const extrasReceiveShadows = mesh.metadata?.gltf?.extras?.receiveShadows;
|
|
691
|
+
mesh.receiveShadows = typeof extrasReceiveShadows === "boolean" ? extrasReceiveShadows : true; // Not necessary for IBL shadows, but yes for standard shadows
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
663
695
|
/**
|
|
664
696
|
* Initializes shadows for the Babylon.js scene.
|
|
665
697
|
* @private
|
|
@@ -672,6 +704,9 @@ export default class BabylonJSController {
|
|
|
672
704
|
if (!this.#settings.shadowsEnabled) {
|
|
673
705
|
return false;
|
|
674
706
|
}
|
|
707
|
+
|
|
708
|
+
this.#ensureMeshesReceiveShadows();
|
|
709
|
+
|
|
675
710
|
if (this.#scene.environmentTexture) {
|
|
676
711
|
if (this.#options.ibl.shadows) {
|
|
677
712
|
if (this.#scene.environmentTexture.isReady()) {
|
|
@@ -834,12 +869,23 @@ export default class BabylonJSController {
|
|
|
834
869
|
* @returns {void|false} Returns false if there is no active camera; otherwise, void.
|
|
835
870
|
*/
|
|
836
871
|
#onMouseWheel(event, pickInfo) {
|
|
837
|
-
|
|
872
|
+
const camera = this.#scene?.activeCamera;
|
|
873
|
+
if (!camera) {
|
|
838
874
|
return false;
|
|
839
875
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
876
|
+
if (!camera.metadata?.locked) {
|
|
877
|
+
if (camera instanceof ArcRotateCamera) {
|
|
878
|
+
camera.wheelPrecision = camera.wheelPrecision || 3.0;
|
|
879
|
+
camera.inertialRadiusOffset -= event.deltaY * camera.wheelPrecision * 0.001;
|
|
880
|
+
} else if (camera instanceof FreeCamera || camera instanceof UniversalCamera) {
|
|
881
|
+
camera.wheelPrecision = camera.wheelPrecision || 3.0;
|
|
882
|
+
const zoomSpeed = -event.deltaY * camera.wheelPrecision * 0.001;
|
|
883
|
+
|
|
884
|
+
const target = camera.target || Vector3.Zero();
|
|
885
|
+
const direction = target.subtract(camera.position).normalize();
|
|
886
|
+
const movementVector = direction.scale(zoomSpeed);
|
|
887
|
+
camera.position = camera.position.add(movementVector);
|
|
888
|
+
}
|
|
843
889
|
}
|
|
844
890
|
event.preventDefault();
|
|
845
891
|
}
|
|
@@ -995,7 +1041,8 @@ export default class BabylonJSController {
|
|
|
995
1041
|
}
|
|
996
1042
|
}
|
|
997
1043
|
this.#scene.activeCamera?.detachControl();
|
|
998
|
-
|
|
1044
|
+
camera.detachControl();
|
|
1045
|
+
if (!cameraState.locked) {
|
|
999
1046
|
camera.attachControl(this.#canvas, true);
|
|
1000
1047
|
}
|
|
1001
1048
|
this.#scene.activeCamera = camera;
|
|
@@ -1210,6 +1257,9 @@ export default class BabylonJSController {
|
|
|
1210
1257
|
compileMaterials: true,
|
|
1211
1258
|
loadAllMaterials: true,
|
|
1212
1259
|
loadOnlyMaterials: container.state.name === "materials",
|
|
1260
|
+
extensionOptions: {
|
|
1261
|
+
ExtrasAsMetadata: { enabled: true },
|
|
1262
|
+
},
|
|
1213
1263
|
},
|
|
1214
1264
|
},
|
|
1215
1265
|
};
|
|
@@ -1288,7 +1338,6 @@ export default class BabylonJSController {
|
|
|
1288
1338
|
this.#setMaxSimultaneousLights();
|
|
1289
1339
|
this.#loadCameraDepentEffects();
|
|
1290
1340
|
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#containers.model.assetContainer);
|
|
1291
|
-
this.#forceReflectionsInModelGlasses();
|
|
1292
1341
|
this.#startRender();
|
|
1293
1342
|
});
|
|
1294
1343
|
return detail;
|
|
@@ -1382,28 +1431,6 @@ export default class BabylonJSController {
|
|
|
1382
1431
|
}
|
|
1383
1432
|
}
|
|
1384
1433
|
|
|
1385
|
-
/**
|
|
1386
|
-
* Configures realistic glass material properties for all materials whose names include "Glass".
|
|
1387
|
-
* @private
|
|
1388
|
-
* @param {AssetContainer} [assetContainer] - The asset container with materials to process. If undefined, uses the model container.
|
|
1389
|
-
* @returns {void}
|
|
1390
|
-
* @note This method assumes that glass materials are named with the substring "Glass".
|
|
1391
|
-
*/
|
|
1392
|
-
#forceReflectionsInModelGlasses(assetContainer) {
|
|
1393
|
-
if (assetContainer === undefined) {
|
|
1394
|
-
assetContainer = this.#containers.model.assetContainer;
|
|
1395
|
-
}
|
|
1396
|
-
if (!this.#scene || !assetContainer) {
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1399
|
-
assetContainer.materials?.forEach(material => {
|
|
1400
|
-
if (material && material.name && material.name.includes("Glass")) {
|
|
1401
|
-
material.metallic = 0.25;
|
|
1402
|
-
material.environmentIntensity = 1.0;
|
|
1403
|
-
}
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
1434
|
/**
|
|
1408
1435
|
* Translates a node along the scene's vertical (Y) axis by the provided value.
|
|
1409
1436
|
* @private
|
|
@@ -138,7 +138,8 @@ export class MaterialData {
|
|
|
138
138
|
* - Access status via isPending, isSuccess getters.
|
|
139
139
|
*/
|
|
140
140
|
export class CameraData {
|
|
141
|
-
|
|
141
|
+
defaultLocked = true;
|
|
142
|
+
constructor(name = "", value = null, locked = this.defaultLocked) {
|
|
142
143
|
this.name = name;
|
|
143
144
|
this.value = value;
|
|
144
145
|
this.locked = locked;
|
|
@@ -161,7 +162,7 @@ export class CameraData {
|
|
|
161
162
|
this.update.success = false;
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
|
-
setPending(value = undefined, locked =
|
|
165
|
+
setPending(value = undefined, locked = this.defaultLocked) {
|
|
165
166
|
this.reset();
|
|
166
167
|
if (value === undefined) {
|
|
167
168
|
return;
|