@preference-sl/pref-viewer 2.16.0-beta.8 → 2.16.0-beta.9

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.16.0-beta.8",
3
+ "version": "2.16.0-beta.9",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
@@ -51,6 +51,36 @@ function normalizeDirection(x, y, z) {
51
51
  };
52
52
  }
53
53
 
54
+ const PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY = "prefViewerSimplifiedTransmission";
55
+
56
+ export function markPrefViewerSimplifiedTransmissionMaterial(mat) {
57
+ if (!mat) return mat;
58
+ mat.metadata = {
59
+ ...(mat.metadata ?? {}),
60
+ [PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY]: true,
61
+ };
62
+ return mat;
63
+ }
64
+
65
+ export function isPrefViewerTransparentRenderOrderMaterial(mat) {
66
+ if (!mat) return false;
67
+ const isTransmissive = isPrefViewerPhysicalGlassMaterial(mat);
68
+ const isSimplifiedTransmission = mat.metadata?.[PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY] === true;
69
+ const alpha = typeof mat.alpha === "number" ? mat.alpha : 1;
70
+ const isAlphaBlended =
71
+ alpha < 1 ||
72
+ mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHABLEND ||
73
+ mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHATESTANDBLEND ||
74
+ mat.needAlphaBlending?.() === true ||
75
+ mat.needAlphaTesting?.() === true;
76
+
77
+ return Boolean(isTransmissive || isSimplifiedTransmission || isAlphaBlended);
78
+ }
79
+
80
+ export function isPrefViewerPhysicalGlassMaterial(mat) {
81
+ return Boolean(mat?.subSurface?.isRefractionEnabled || mat?.subSurface?.isTranslucencyEnabled);
82
+ }
83
+
54
84
  export function getLightingCycleProfile(timeOfDay = 0.5) {
55
85
  const normalizedTime = clamp01(timeOfDay);
56
86
  const timeHours = normalizedTime * 24;
@@ -1628,6 +1658,81 @@ export default class BabylonJSController {
1628
1658
  });
1629
1659
  }
1630
1660
 
1661
+ /**
1662
+ * Fixes cross-GLTF transparency ordering. Detects transparent/transmissive meshes
1663
+ * (alpha-blended, alpha-tested, simplified environment transmission, or
1664
+ * KHR_materials_transmission) across all loaded containers and moves them to
1665
+ * renderingGroupId=1 with depth-write disabled.
1666
+ * This ensures opaque scene geometry (e.g. lamp cups behind a model window) is
1667
+ * written to the depth buffer first (group 0) so the glass layer composites on top
1668
+ * without occluding it.
1669
+ * @private
1670
+ * @returns {void}
1671
+ */
1672
+ #applyTransmittanceRenderOrder() {
1673
+ if (!this.#scene) return;
1674
+
1675
+ const envMeshIds = new Set(
1676
+ this.#containers.environment?.assetContainer?.meshes.map((m) => m.uniqueId) ?? []
1677
+ );
1678
+ const modelMeshIds = new Set(
1679
+ this.#containers.model?.assetContainer?.meshes.map((m) => m.uniqueId) ?? []
1680
+ );
1681
+
1682
+ // Map each material to the model meshes using it — needed to detect shared materials
1683
+ // before modifying an env mesh's material (must clone to avoid breaking model glass).
1684
+ const matToModelMeshes = new Map();
1685
+ this.#scene.meshes.forEach((mesh) => {
1686
+ if (!modelMeshIds.has(mesh.uniqueId) || !mesh.material) return;
1687
+ if (!matToModelMeshes.has(mesh.material)) matToModelMeshes.set(mesh.material, new Set());
1688
+ matToModelMeshes.get(mesh.material).add(mesh.uniqueId);
1689
+ });
1690
+
1691
+ this.#scene.meshes.forEach((mesh) => {
1692
+ const mat = mesh.material;
1693
+ if (!mat) return;
1694
+ const isTransmissive = isPrefViewerPhysicalGlassMaterial(mat);
1695
+ const isTransparentLike = isPrefViewerTransparentRenderOrderMaterial(mat);
1696
+ if (!isTransparentLike) return;
1697
+
1698
+ if (envMeshIds.has(mesh.uniqueId)) {
1699
+ // Environment mesh carrying a transmissive material introduced by a material swap
1700
+ // (initial-load materials are pre-simplified in #assetContainer_simplifyTransmissiveMaterials).
1701
+ // Clone if shared with a model mesh so the model's physical glass is not affected.
1702
+ let targetMat = mat;
1703
+ if (matToModelMeshes.has(mat)) {
1704
+ targetMat = mat.clone(`${mat.name}_env_simplified`);
1705
+ mesh.material = targetMat;
1706
+ }
1707
+ if (isTransmissive) {
1708
+ this.#simplifyTransmissiveMaterial(targetMat);
1709
+ }
1710
+ mesh.renderingGroupId = 1;
1711
+ targetMat.disableDepthWrite = true;
1712
+ targetMat.alphaIndex = 0;
1713
+ } else if (modelMeshIds.has(mesh.uniqueId)) {
1714
+ // Model transparent materials render after all group-0 geometry.
1715
+ mesh.renderingGroupId = 1;
1716
+ mat.disableDepthWrite = true;
1717
+
1718
+ // Physical model glass keeps transmission, but strips volume absorption (tintColor)
1719
+ // and roughness because those blur/darken transmitted geometry. Do not apply this
1720
+ // tuning to plain alpha-blended model materials: render-order and physical glass
1721
+ // material tuning are separate concerns.
1722
+ if (isTransmissive && mat.subSurface) {
1723
+ mat.subSurface.tintColor = Color3.White();
1724
+ mat.roughness = 0;
1725
+ }
1726
+ mat.alphaIndex = 1;
1727
+ } else {
1728
+ // Unknown container (future-proof safe default).
1729
+ mesh.renderingGroupId = 1;
1730
+ mat.disableDepthWrite = true;
1731
+ mat.alphaIndex = 1;
1732
+ }
1733
+ });
1734
+ }
1735
+
1631
1736
  /**
1632
1737
  * Applies the showroom tonemapping curve and lighting bias used by the 3D viewer.
1633
1738
  * @private
@@ -2522,6 +2627,40 @@ export default class BabylonJSController {
2522
2627
  assetContainer.lights = [];
2523
2628
  }
2524
2629
 
2630
+ /**
2631
+ * Strips KHR_materials_transmission, KHR_materials_ior and KHR_materials_volume from
2632
+ * environment-container (scene) materials, converting them to simple alpha blending.
2633
+ * Babylon.js cannot render two physical-transmission materials (from separate GLTF
2634
+ * containers) through each other: the scene decorative glass (lamp cups, etc.) is
2635
+ * simplified so the model's physical glass keeps full correct transmission.
2636
+ * @private
2637
+ * @param {AssetContainer} assetContainer
2638
+ * @returns {void}
2639
+ */
2640
+ #assetContainer_simplifyTransmissiveMaterials(assetContainer) {
2641
+ assetContainer.materials?.forEach((mat) => {
2642
+ if (!(mat instanceof PBRMaterial)) return;
2643
+ if (!mat.subSurface?.isRefractionEnabled && !mat.subSurface?.isTranslucencyEnabled) return;
2644
+ this.#simplifyTransmissiveMaterial(mat);
2645
+ });
2646
+ }
2647
+
2648
+ /**
2649
+ * Converts a single PBRMaterial from physical KHR_materials_transmission to simple alpha
2650
+ * blending. Shared helper used by both the at-load simplification and the post-swap pass.
2651
+ * @private
2652
+ * @param {PBRMaterial} mat
2653
+ * @returns {void}
2654
+ */
2655
+ #simplifyTransmissiveMaterial(mat) {
2656
+ const intensity = mat.subSurface.refractionIntensity ?? 1.0;
2657
+ mat.subSurface.isRefractionEnabled = false;
2658
+ mat.subSurface.isTranslucencyEnabled = false;
2659
+ mat.alpha = Math.max(0.28, 1.0 - intensity * 0.65);
2660
+ mat.transparencyMode = PBRMaterial.PBRMATERIAL_ALPHABLEND;
2661
+ markPrefViewerSimplifiedTransmissionMaterial(mat);
2662
+ }
2663
+
2525
2664
  /**
2526
2665
  * Assigns unique ids to every imported camera so Babylon.js does not reuse stale SSAO effects between reloads.
2527
2666
  * @private
@@ -2695,6 +2834,9 @@ export default class BabylonJSController {
2695
2834
  if (container.state.name === "model" || container.state.name === "environment") {
2696
2835
  this.#assetContainer_retagCameras(assetContainer);
2697
2836
  }
2837
+ if (container.state.name === "environment") {
2838
+ this.#assetContainer_simplifyTransmissiveMaterials(assetContainer);
2839
+ }
2698
2840
  this.#replaceContainer(container, assetContainer);
2699
2841
  container.state.setSuccess(true);
2700
2842
  } else {
@@ -2721,6 +2863,7 @@ export default class BabylonJSController {
2721
2863
  this.#checkModelMetadata(oldModelMetadata, newModelMetadata);
2722
2864
  this.#setMaxSimultaneousLights();
2723
2865
  this.#enhanceTextureQuality();
2866
+ this.#applyTransmittanceRenderOrder();
2724
2867
  this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#containers.model.assetContainer);
2725
2868
  // Apply stored highlight settings so fresh page loads respect persisted state
2726
2869
  this.#setHighlightConfig({
@@ -3203,6 +3346,7 @@ export default class BabylonJSController {
3203
3346
  setMaterialOptions() {
3204
3347
  this.#stopRender();
3205
3348
  const materialsOptionsSetted = this.#setOptions_Materials();
3349
+ this.#applyTransmittanceRenderOrder();
3206
3350
  this.#startRender();
3207
3351
  return materialsOptionsSetted;
3208
3352
  }
@@ -3384,4 +3528,4 @@ export default class BabylonJSController {
3384
3528
  this.#requestRender({ frames: 2 });
3385
3529
  }
3386
3530
  }
3387
- }
3531
+ }