@preference-sl/pref-viewer 2.16.0-beta.8 → 2.16.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 +6 -6
- package/src/babylonjs-controller.js +82 -102
- package/src/localization/translations.js +5 -17
- package/src/material-utils.js +72 -0
- package/src/pref-viewer-3d.js +24 -8
- package/src/pref-viewer-menu-3d.js +133 -260
- package/src/pref-viewer.js +3 -3
- package/src/render-math.js +127 -0
- package/src/styles.js +71 -162
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preference-sl/pref-viewer",
|
|
3
|
-
"version": "2.16.0
|
|
3
|
+
"version": "2.16.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": "9.
|
|
39
|
-
"@babylonjs/loaders": "9.
|
|
40
|
-
"@babylonjs/serializers": "9.
|
|
41
|
-
"@panzoom/panzoom": "^4.6.
|
|
42
|
-
"babylonjs-gltf2interface": "9.
|
|
38
|
+
"@babylonjs/core": "^9.11.0",
|
|
39
|
+
"@babylonjs/loaders": "^9.11.0",
|
|
40
|
+
"@babylonjs/serializers": "^9.11.0",
|
|
41
|
+
"@panzoom/panzoom": "^4.6.2",
|
|
42
|
+
"babylonjs-gltf2interface": "^9.11.0",
|
|
43
43
|
"buffer": "^6.0.3",
|
|
44
44
|
"idb": "^8.0.3",
|
|
45
45
|
"is-svg": "^6.1.0",
|
|
@@ -10,74 +10,13 @@ import { MaterialData } from "./pref-viewer-3d-data.js";
|
|
|
10
10
|
import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
11
11
|
import OpeningAnimation from "./babylonjs-animation-opening.js";
|
|
12
12
|
import { translate } from "./localization/i18n.js";
|
|
13
|
+
import { PrefViewerStyleVariables } from "./styles.js";
|
|
14
|
+
import { clamp01, lerp, getAdaptiveHardwareScalingLevel, getLightingCycleProfile } from "./render-math.js";
|
|
15
|
+
import { markPrefViewerSimplifiedTransmissionMaterial, isPrefViewerPhysicalGlassMaterial, isPrefViewerTransparentRenderOrderMaterial } from "./material-utils.js";
|
|
13
16
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return safeBaseScaling / safePixelRatio;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function clamp01(value) {
|
|
21
|
-
if (!Number.isFinite(value)) {
|
|
22
|
-
return 0.5;
|
|
23
|
-
}
|
|
24
|
-
return Math.min(1, Math.max(0, value));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function lerp(from, to, amount) {
|
|
28
|
-
const t = clamp01(amount);
|
|
29
|
-
return from + (to - from) * t;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function smoothstep(edge0, edge1, x) {
|
|
33
|
-
if (edge0 === edge1) {
|
|
34
|
-
return x >= edge1 ? 1 : 0;
|
|
35
|
-
}
|
|
36
|
-
const t = clamp01((x - edge0) / (edge1 - edge0));
|
|
37
|
-
return t * t * (3 - 2 * t);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function bellCurve(x) {
|
|
41
|
-
const clamped = clamp01(x);
|
|
42
|
-
return Math.sin(clamped * Math.PI);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function normalizeDirection(x, y, z) {
|
|
46
|
-
const length = Math.hypot(x, y, z) || 1;
|
|
47
|
-
return {
|
|
48
|
-
x: x / length,
|
|
49
|
-
y: y / length,
|
|
50
|
-
z: z / length,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function getLightingCycleProfile(timeOfDay = 0.5) {
|
|
55
|
-
const normalizedTime = clamp01(timeOfDay);
|
|
56
|
-
const timeHours = normalizedTime * 24;
|
|
57
|
-
const sunriseHour = 6.25;
|
|
58
|
-
const sunsetHour = 20.25;
|
|
59
|
-
const daylightProgress = clamp01((timeHours - sunriseHour) / (sunsetHour - sunriseHour));
|
|
60
|
-
const dayFactor = timeHours >= sunriseHour && timeHours <= sunsetHour ? Math.pow(bellCurve(daylightProgress), 0.88) : 0;
|
|
61
|
-
const nightFactor = 1 - dayFactor;
|
|
62
|
-
const solarArc = dayFactor > 0 ? Math.pow(dayFactor, 0.72) : 0;
|
|
63
|
-
const solarAzimuth = lerp(-Math.PI * 0.82, Math.PI * 0.82, daylightProgress);
|
|
64
|
-
const solarElevation = lerp(-0.18, 1.18, solarArc);
|
|
65
|
-
const sunPhase = normalizedTime * Math.PI * 2 - Math.PI / 2;
|
|
66
|
-
const sunDirection = normalizeDirection(-Math.cos(sunPhase), -Math.sin(sunPhase), -0.35);
|
|
67
|
-
const moonDirection = normalizeDirection(-sunDirection.x, -sunDirection.y, -sunDirection.z);
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
timeOfDay: normalizedTime,
|
|
71
|
-
timeHours,
|
|
72
|
-
sunPhase,
|
|
73
|
-
solarAzimuth,
|
|
74
|
-
solarElevation,
|
|
75
|
-
dayFactor,
|
|
76
|
-
nightFactor,
|
|
77
|
-
sunDirection,
|
|
78
|
-
moonDirection,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
17
|
+
// Re-export so existing consumers (tests, other modules) can keep importing from this file.
|
|
18
|
+
export { getAdaptiveHardwareScalingLevel, getLightingCycleProfile } from "./render-math.js";
|
|
19
|
+
export { markPrefViewerSimplifiedTransmissionMaterial, isPrefViewerPhysicalGlassMaterial, isPrefViewerTransparentRenderOrderMaterial } from "./material-utils.js";
|
|
81
20
|
|
|
82
21
|
/**
|
|
83
22
|
* BabylonJSController coordinates the PrefViewer 3D runtime: it bootstraps Babylon.js, manages asset containers,
|
|
@@ -100,9 +39,10 @@ export function getLightingCycleProfile(timeOfDay = 0.5) {
|
|
|
100
39
|
*
|
|
101
40
|
* Runtime Flow
|
|
102
41
|
* 1. Instantiate with `new BabylonJSController(canvas, containers, options)`.
|
|
103
|
-
* 2. Call `enable()` to configure Draco, spin up the engine/scene, attach interaction + XR hooks
|
|
42
|
+
* 2. Call `enable()` to configure Draco, spin up the engine/scene, and attach interaction + XR hooks. The render loop
|
|
43
|
+
* does not start here; it starts automatically after the first `load()` completes.
|
|
104
44
|
* 3. When PrefViewer marks containers/options pending, invoke `load()` (or `reloadWithCurrentSettings()`) so the controller
|
|
105
|
-
* fetches sources, rebuilds containers, and
|
|
45
|
+
* fetches sources, rebuilds containers, reattaches pipelines, and starts the render loop.
|
|
106
46
|
* 4. Use `scheduleRenderSettingsReload()` to merge/persist toggles; when it reports `changed: true`, call
|
|
107
47
|
* `reloadWithCurrentSettings()` to apply the staged settings.
|
|
108
48
|
* 5. Use `setContainerVisibility`, `setMaterialOptions`, `setCameraOptions`, or `setIBLOptions` for targeted updates; these
|
|
@@ -119,10 +59,13 @@ export function getLightingCycleProfile(timeOfDay = 0.5) {
|
|
|
119
59
|
* - getRenderSettings() / scheduleRenderSettingsReload(settings)
|
|
120
60
|
* - setContainerVisibility(name, show)
|
|
121
61
|
* - setMaterialOptions() / setCameraOptions() / setIBLOptions()
|
|
62
|
+
* - setIlluminationConfig(config)
|
|
63
|
+
* - getAnimations() / playAnimation(name, action) / setAnimationProgress(name, progress) / setAnimationLoop(name, loop)
|
|
122
64
|
*
|
|
123
65
|
* Key Invariants
|
|
124
|
-
* - Asset containers must expose `setPendingWithCurrentStorage
|
|
125
|
-
*
|
|
66
|
+
* - Asset containers must expose `setPendingWithCurrentStorage` before calling load/reload; the controller reads that
|
|
67
|
+
* flag to resolve fresh sources. Option objects (camera, materials, IBL) expose `setPendingWithCurrent` for the same
|
|
68
|
+
* purpose. Neither set of flags touches the DOM — they only gate resolver calls.
|
|
126
69
|
* - Camera-dependent pipelines (DefaultRenderingPipeline, SSAO, IBL shadows, directional shadow generators) are rebuilt
|
|
127
70
|
* only after the active camera and environment textures are ready; render-loop restarts gate those transitions.
|
|
128
71
|
* - `show-model`/`show-scene` DOM attributes reflect container visibility; there are no direct `showModel()/hideModel()` APIs.
|
|
@@ -156,7 +99,7 @@ export default class BabylonJSController {
|
|
|
156
99
|
lightingTimeOfDay: 0.5,
|
|
157
100
|
// Highlight settings
|
|
158
101
|
highlightEnabled: true,
|
|
159
|
-
highlightColor:
|
|
102
|
+
highlightColor: PrefViewerStyleVariables.colorPrimary,
|
|
160
103
|
};
|
|
161
104
|
|
|
162
105
|
// Canvas HTML element
|
|
@@ -178,7 +121,7 @@ export default class BabylonJSController {
|
|
|
178
121
|
#XRExperience = null;
|
|
179
122
|
#canvasResizeObserver = null;
|
|
180
123
|
|
|
181
|
-
#hdrTexture = null; //
|
|
124
|
+
#hdrTexture = null; // Reusable in-memory HDR source cloned into scene.environmentTexture across reloads
|
|
182
125
|
|
|
183
126
|
#containers = {};
|
|
184
127
|
#options = {};
|
|
@@ -231,30 +174,30 @@ export default class BabylonJSController {
|
|
|
231
174
|
// Runtime configuration constants (tuning knobs, not per-frame state).
|
|
232
175
|
#config = {
|
|
233
176
|
pointerPicking: {
|
|
234
|
-
movePickIntervalMs: 50, //
|
|
235
|
-
movePickMinDistancePx: 2, //
|
|
177
|
+
movePickIntervalMs: 50, // Cap expensive scene.pick calls to ~20 Hz while moving the pointer
|
|
178
|
+
movePickMinDistancePx: 2, // Skip picks for sub-pixel jitter
|
|
236
179
|
},
|
|
237
180
|
render: {
|
|
238
181
|
burstFramesBase: 2,
|
|
239
|
-
burstFramesEnhanced: 32, //
|
|
182
|
+
burstFramesEnhanced: 32, // When AA/SSAO/IBL is enabled, more frames are needed to reach stable output
|
|
240
183
|
interactionMs: 250,
|
|
241
184
|
animationMs: 200,
|
|
242
185
|
idleThrottleMs: 1000 / 8, // Reduced from 15 FPS to 8 FPS for idle - reduces GPU/CPU load
|
|
243
186
|
idleThresholdMs: 3000, // Time before going to true idle (no renders)
|
|
244
187
|
},
|
|
245
188
|
resize: {
|
|
246
|
-
throttleMs: 50, //
|
|
189
|
+
throttleMs: 50, // Cap resize work to ~20 Hz while dragging/resizing containers
|
|
247
190
|
},
|
|
248
191
|
// Hardware-adaptive quality settings (applied based on GPU capabilities)
|
|
249
192
|
quality: {
|
|
250
193
|
// Settings for different hardware tiers: 'low', 'medium', 'high'
|
|
251
194
|
// Applied automatically based on GPU detected capabilities
|
|
252
|
-
tier: 'medium',
|
|
253
|
-
shadowMapSize: 1024,
|
|
254
|
-
iblShadowResolution: 1,
|
|
255
|
-
iblSampleDirections: 4,
|
|
256
|
-
shadowBlurKernel: 16,
|
|
257
|
-
ssaoEnabled: true,
|
|
195
|
+
tier: 'medium', // Default, will be updated after hardware detection
|
|
196
|
+
shadowMapSize: 1024, // Standard shadow generator resolution
|
|
197
|
+
iblShadowResolution: 1, // IBL shadow resolution exponent (0=1024, 1=2048, 2=4096)
|
|
198
|
+
iblSampleDirections: 4, // IBL shadow quality
|
|
199
|
+
shadowBlurKernel: 16, // Shadow blur amount
|
|
200
|
+
ssaoEnabled: true, // Screen Space Ambient Occlusion
|
|
258
201
|
},
|
|
259
202
|
look: {
|
|
260
203
|
showroom: {
|
|
@@ -378,23 +321,23 @@ export default class BabylonJSController {
|
|
|
378
321
|
// High-end mobile patterns (recent flagship devices)
|
|
379
322
|
const highEndPatterns = [
|
|
380
323
|
/iPhone (1[4-9]|SE)/i, // iPhone 14+
|
|
381
|
-
/iPad Pro/i,
|
|
382
|
-
/SM-S91[0-9]/i,
|
|
383
|
-
/SM-S9[0-9]/i,
|
|
384
|
-
/Pixel [7-9]/i,
|
|
385
|
-
/OnePlus [8-9|10|11]/i,
|
|
324
|
+
/iPad Pro/i, // iPad Pro
|
|
325
|
+
/SM-S91[0-9]/i, // Samsung S21+
|
|
326
|
+
/SM-S9[0-9]/i, // Samsung S20+
|
|
327
|
+
/Pixel [7-9]/i, // Pixel 7+
|
|
328
|
+
/OnePlus [8-9|10|11]/i, // OnePlus 8+
|
|
386
329
|
/Xiaomi (1[2-4]|Mi 1[1-4])/i, // Xiaomi 12+
|
|
387
|
-
/Huawei (P50|Mate 4|)/i,
|
|
330
|
+
/Huawei (P50|Mate 4|)/i, // Huawei P50+
|
|
388
331
|
];
|
|
389
332
|
|
|
390
333
|
// Mid-range patterns
|
|
391
334
|
const midRangePatterns = [
|
|
392
|
-
/iPhone (1[0-3]|SE)/i,
|
|
335
|
+
/iPhone (1[0-3]|SE)/i, // iPhone 10-13
|
|
393
336
|
/iPad Air/i, // iPad Air
|
|
394
|
-
/SM-A[0-9]/i,
|
|
395
|
-
/Redmi Note/i,
|
|
396
|
-
/Pixel [4-6]/i,
|
|
397
|
-
/Moto G/i,
|
|
337
|
+
/SM-A[0-9]/i, // Samsung A series
|
|
338
|
+
/Redmi Note/i, // Redmi Note
|
|
339
|
+
/Pixel [4-6]/i, // Pixel 4-6
|
|
340
|
+
/Moto G/i, // Moto G
|
|
398
341
|
];
|
|
399
342
|
|
|
400
343
|
const isHighEnd = highEndPatterns.some(p => p.test(userAgent));
|
|
@@ -446,13 +389,13 @@ export default class BabylonJSController {
|
|
|
446
389
|
// Mobile low-end (old phones, budget devices)
|
|
447
390
|
low: {
|
|
448
391
|
shadowMapSize: 256, // Very small for speed
|
|
449
|
-
iblShadowResolution: -1,
|
|
392
|
+
iblShadowResolution: -1, // Disable IBL shadows
|
|
450
393
|
iblSampleDirections: 1, // Minimal sampling
|
|
451
394
|
shadowBlurKernel: 0, // No blur
|
|
452
395
|
ssaoEnabled: false,
|
|
453
396
|
antialiasingEnabled: false,
|
|
454
|
-
hardwareScaling: 2.5,
|
|
455
|
-
maxModelResolution: 512,
|
|
397
|
+
hardwareScaling: 2.5, // Aggressive downscaling
|
|
398
|
+
maxModelResolution: 512, // Low-res model loading
|
|
456
399
|
environmentTextureSize: 512,
|
|
457
400
|
},
|
|
458
401
|
// Mobile high-end / Tablet / Desktop low
|
|
@@ -470,7 +413,7 @@ export default class BabylonJSController {
|
|
|
470
413
|
// Desktop mid-range / High-end mobile
|
|
471
414
|
high: {
|
|
472
415
|
shadowMapSize: 1024,
|
|
473
|
-
iblShadowResolution: 0,
|
|
416
|
+
iblShadowResolution: 0, // 1024x1024 - good balance
|
|
474
417
|
iblSampleDirections: 4, // Quality sampling
|
|
475
418
|
shadowBlurKernel: 8, // Sharper shadows for product clarity
|
|
476
419
|
ssaoEnabled: false,
|
|
@@ -482,9 +425,9 @@ export default class BabylonJSController {
|
|
|
482
425
|
// Desktop high-end (RTX, Apple Silicon, etc.)
|
|
483
426
|
ultra: {
|
|
484
427
|
shadowMapSize: 2048,
|
|
485
|
-
iblShadowResolution: 1,
|
|
428
|
+
iblShadowResolution: 1, // 2048x2048
|
|
486
429
|
iblSampleDirections: 8, // Maximum quality
|
|
487
|
-
shadowBlurKernel: 16,
|
|
430
|
+
shadowBlurKernel: 16, // Preserve clarity over softness
|
|
488
431
|
ssaoEnabled: false,
|
|
489
432
|
antialiasingEnabled: true,
|
|
490
433
|
hardwareScaling: 1.0, // Native resolution
|
|
@@ -1627,7 +1570,7 @@ export default class BabylonJSController {
|
|
|
1627
1570
|
}
|
|
1628
1571
|
});
|
|
1629
1572
|
}
|
|
1630
|
-
|
|
1573
|
+
|
|
1631
1574
|
/**
|
|
1632
1575
|
* Applies the showroom tonemapping curve and lighting bias used by the 3D viewer.
|
|
1633
1576
|
* @private
|
|
@@ -2522,6 +2465,40 @@ export default class BabylonJSController {
|
|
|
2522
2465
|
assetContainer.lights = [];
|
|
2523
2466
|
}
|
|
2524
2467
|
|
|
2468
|
+
/**
|
|
2469
|
+
* Strips KHR_materials_transmission, KHR_materials_ior and KHR_materials_volume from
|
|
2470
|
+
* environment-container (scene) materials, converting them to simple alpha blending.
|
|
2471
|
+
* Babylon.js cannot render two physical-transmission materials (from separate GLTF
|
|
2472
|
+
* containers) through each other: the scene decorative glass (lamp cups, etc.) is
|
|
2473
|
+
* simplified so the model's physical glass keeps full correct transmission.
|
|
2474
|
+
* @private
|
|
2475
|
+
* @param {AssetContainer} assetContainer
|
|
2476
|
+
* @returns {void}
|
|
2477
|
+
*/
|
|
2478
|
+
#assetContainer_simplifyTransmissiveMaterials(assetContainer) {
|
|
2479
|
+
assetContainer.materials?.forEach((mat) => {
|
|
2480
|
+
if (!(mat instanceof PBRMaterial)) return;
|
|
2481
|
+
if (!mat.subSurface?.isRefractionEnabled && !mat.subSurface?.isTranslucencyEnabled) return;
|
|
2482
|
+
this.#simplifyTransmissiveMaterial(mat);
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
/**
|
|
2487
|
+
* Converts a single PBRMaterial from physical KHR_materials_transmission to simple alpha
|
|
2488
|
+
* blending. Shared helper used by both the at-load simplification and the post-swap pass.
|
|
2489
|
+
* @private
|
|
2490
|
+
* @param {PBRMaterial} mat
|
|
2491
|
+
* @returns {void}
|
|
2492
|
+
*/
|
|
2493
|
+
#simplifyTransmissiveMaterial(mat) {
|
|
2494
|
+
const intensity = mat.subSurface.refractionIntensity ?? 1.0;
|
|
2495
|
+
mat.subSurface.isRefractionEnabled = false;
|
|
2496
|
+
mat.subSurface.isTranslucencyEnabled = false;
|
|
2497
|
+
mat.alpha = Math.max(0.28, 1.0 - intensity * 0.65);
|
|
2498
|
+
mat.transparencyMode = PBRMaterial.PBRMATERIAL_ALPHABLEND;
|
|
2499
|
+
markPrefViewerSimplifiedTransmissionMaterial(mat);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2525
2502
|
/**
|
|
2526
2503
|
* Assigns unique ids to every imported camera so Babylon.js does not reuse stale SSAO effects between reloads.
|
|
2527
2504
|
* @private
|
|
@@ -2695,6 +2672,9 @@ export default class BabylonJSController {
|
|
|
2695
2672
|
if (container.state.name === "model" || container.state.name === "environment") {
|
|
2696
2673
|
this.#assetContainer_retagCameras(assetContainer);
|
|
2697
2674
|
}
|
|
2675
|
+
if (container.state.name === "environment") {
|
|
2676
|
+
this.#assetContainer_simplifyTransmissiveMaterials(assetContainer);
|
|
2677
|
+
}
|
|
2698
2678
|
this.#replaceContainer(container, assetContainer);
|
|
2699
2679
|
container.state.setSuccess(true);
|
|
2700
2680
|
} else {
|
|
@@ -3384,4 +3364,4 @@ export default class BabylonJSController {
|
|
|
3384
3364
|
this.#requestRender({ frames: 2 });
|
|
3385
3365
|
}
|
|
3386
3366
|
}
|
|
3387
|
-
}
|
|
3367
|
+
}
|
|
@@ -3,18 +3,12 @@ export const translations = {
|
|
|
3
3
|
prefViewer: {
|
|
4
4
|
menu3d: {
|
|
5
5
|
title: "3D render settings",
|
|
6
|
-
subtitle: "
|
|
7
|
-
pendingLabel: "Pending changes",
|
|
6
|
+
subtitle: "Modify the visual effects.",
|
|
8
7
|
actions: {
|
|
9
|
-
apply: "Apply",
|
|
10
|
-
applying: "Applying...",
|
|
11
8
|
play: "Play",
|
|
12
9
|
pause: "Pause",
|
|
13
10
|
},
|
|
14
|
-
|
|
15
|
-
error: "Couldn't apply the changes.",
|
|
16
|
-
},
|
|
17
|
-
switches: {
|
|
11
|
+
features: {
|
|
18
12
|
antiAliasingEnabled: {
|
|
19
13
|
label: "Antialiasing",
|
|
20
14
|
helper: "Softens jagged edges and lines.",
|
|
@@ -70,18 +64,12 @@ export const translations = {
|
|
|
70
64
|
prefViewer: {
|
|
71
65
|
menu3d: {
|
|
72
66
|
title: "Ajustes de render 3D",
|
|
73
|
-
subtitle: "
|
|
74
|
-
pendingLabel: "Pendiente de aplicar",
|
|
67
|
+
subtitle: "Modifica los efectos visuales.",
|
|
75
68
|
actions: {
|
|
76
|
-
apply: "Aplicar",
|
|
77
|
-
applying: "Aplicando...",
|
|
78
69
|
play: "Reproducir",
|
|
79
70
|
pause: "Pausar",
|
|
80
71
|
},
|
|
81
|
-
|
|
82
|
-
error: "No se pudo aplicar los cambios.",
|
|
83
|
-
},
|
|
84
|
-
switches: {
|
|
72
|
+
features: {
|
|
85
73
|
antiAliasingEnabled: {
|
|
86
74
|
label: "Antialiasing",
|
|
87
75
|
helper: "Suaviza aristas y líneas.",
|
|
@@ -100,7 +88,7 @@ export const translations = {
|
|
|
100
88
|
},
|
|
101
89
|
highlightEnabled: {
|
|
102
90
|
label: "Resaltado",
|
|
103
|
-
helper: "
|
|
91
|
+
helper: "Resalta elementos animados al pasar el ratón.",
|
|
104
92
|
},
|
|
105
93
|
highlightColor: {
|
|
106
94
|
label: "Color de resaltado",
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { PBRMaterial } from "@babylonjs/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utilities for classifying and tagging PBR materials by their transparency/transmission mode.
|
|
5
|
+
* Used by BabylonJSController at load time and by pref-viewer-3d for render-order decisions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY = "prefViewerSimplifiedTransmission";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tags a PBR material as having been converted from physical KHR_materials_transmission to simple
|
|
12
|
+
* alpha blending. The tag is stored in `mat.metadata` under an internal key so that downstream
|
|
13
|
+
* render-order passes can still identify these materials as transparent candidates even after
|
|
14
|
+
* their `subSurface.isRefractionEnabled` flag has been cleared.
|
|
15
|
+
*
|
|
16
|
+
* Returns the same material instance (or the original value when `mat` is falsy) so calls can be
|
|
17
|
+
* chained.
|
|
18
|
+
* @param {import("@babylonjs/core").PBRMaterial|null|undefined} mat - Material to tag.
|
|
19
|
+
* @returns {import("@babylonjs/core").PBRMaterial|null|undefined} The same `mat` value that was passed in.
|
|
20
|
+
*/
|
|
21
|
+
export function markPrefViewerSimplifiedTransmissionMaterial(mat) {
|
|
22
|
+
if (!mat) {
|
|
23
|
+
return mat;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
mat.metadata = {
|
|
27
|
+
...(mat.metadata ?? {}),
|
|
28
|
+
[PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY]: true,
|
|
29
|
+
};
|
|
30
|
+
return mat;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns true when the material uses Babylon.js physical glass rendering — i.e. either
|
|
35
|
+
* KHR_materials_transmission (refraction) or KHR_materials_translucency is active.
|
|
36
|
+
*
|
|
37
|
+
* This is intentionally narrower than `isPrefViewerTransparentRenderOrderMaterial`: it only
|
|
38
|
+
* matches full physical transmission, not plain alpha blending or simplified environment glass.
|
|
39
|
+
* @param {import("@babylonjs/core").Material|null|undefined} mat - Material to test.
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function isPrefViewerPhysicalGlassMaterial(mat) {
|
|
43
|
+
return Boolean(mat?.subSurface?.isRefractionEnabled || mat?.subSurface?.isTranslucencyEnabled);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns true when a material should be placed in the transparent rendering group (renderingGroupId=1).
|
|
48
|
+
*
|
|
49
|
+
* A material qualifies when any of the following is true:
|
|
50
|
+
* - It is a physical glass material (KHR_materials_transmission / translucency).
|
|
51
|
+
* - It has been tagged as simplified environment transmission via `markPrefViewerSimplifiedTransmissionMaterial`.
|
|
52
|
+
* - Its effective alpha is below 1, its `transparencyMode` is alpha-blend or alpha-test-and-blend,
|
|
53
|
+
* or it self-reports as needing blending/testing through the Babylon.js material API.
|
|
54
|
+
* @param {import("@babylonjs/core").Material|null|undefined} mat - Material to test.
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
export function isPrefViewerTransparentRenderOrderMaterial(mat) {
|
|
58
|
+
if (!mat) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const isTransmissive = isPrefViewerPhysicalGlassMaterial(mat);
|
|
62
|
+
const isSimplifiedTransmission = mat.metadata?.[PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY] === true;
|
|
63
|
+
const alpha = typeof mat.alpha === "number" ? mat.alpha : 1;
|
|
64
|
+
const isAlphaBlended =
|
|
65
|
+
alpha < 1 ||
|
|
66
|
+
mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHABLEND ||
|
|
67
|
+
mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHATESTANDBLEND ||
|
|
68
|
+
mat.needAlphaBlending?.() === true ||
|
|
69
|
+
mat.needAlphaTesting?.() === true;
|
|
70
|
+
|
|
71
|
+
return Boolean(isTransmissive || isSimplifiedTransmission || isAlphaBlended);
|
|
72
|
+
}
|
package/src/pref-viewer-3d.js
CHANGED
|
@@ -10,28 +10,32 @@ import FileStorage from "./file-storage.js";
|
|
|
10
10
|
* - Encapsulates a Babylon.js-powered 3D viewer for displaying models, environments, and materials.
|
|
11
11
|
* - Manages internal state for containers (model, environment, materials) and options (camera, materials, IBL, render toggles).
|
|
12
12
|
* - Handles asset loading, configuration, and option updates (camera, materials, IBL) through attributes and public methods.
|
|
13
|
-
* - Exposes getters/setters for render settings so PrefViewerMenu3D or other UIs can
|
|
13
|
+
* - Exposes getters/setters for render settings so PrefViewerMenu3D or other UIs can toggle AA/SSAO/IBL/shadows,
|
|
14
|
+
* hover highlight, and time-of-day lighting.
|
|
14
15
|
* - Provides API for showing/hiding the viewer, model, and environment, and for downloading assets.
|
|
15
16
|
* - Emits custom events for loading, loaded, option-setting, and render-setting workflows.
|
|
16
17
|
*
|
|
17
18
|
* Usage:
|
|
18
19
|
* - Use as a custom HTML element: <pref-viewer-3d ...>
|
|
19
20
|
* - Configure via attributes (config, options, show-model, show-scene, visible).
|
|
20
|
-
* - Call getRenderSettings()/applyRenderSettings() to synchronize UI
|
|
21
|
+
* - Call getRenderSettings()/applyRenderSettings() to synchronize UI controls with BabylonJSController settings.
|
|
21
22
|
* - Control viewer state and assets via public methods.
|
|
22
23
|
*
|
|
23
24
|
* Observed Attributes:
|
|
24
25
|
* - show-model: Show or hide the 3D model ("true"/"false").
|
|
25
26
|
* - show-scene: Show or hide the 3D environment ("true"/"false").
|
|
26
27
|
* - visible: Show or hide the entire viewer ("true"/"false").
|
|
28
|
+
* - show-animation-menu: Show or hide animation controls in the 3D controller.
|
|
29
|
+
* - highlight-color: Hex color used for hover highlight on animated meshes.
|
|
30
|
+
* - highlight-enabled: Enable or disable hover highlight ("true"/"false").
|
|
27
31
|
*
|
|
28
32
|
* Public Methods:
|
|
29
33
|
* - show(): Shows the 3D viewer component.
|
|
30
34
|
* - hide(): Hides the 3D viewer component.
|
|
31
35
|
* - load(config): Loads the provided configuration into the viewer.
|
|
32
36
|
* - setOptions(options): Sets viewer options such as camera, materials, and image-based lighting (IBL).
|
|
33
|
-
* - getRenderSettings(): Returns the current
|
|
34
|
-
* - applyRenderSettings(settings):
|
|
37
|
+
* - getRenderSettings(): Returns the current render settings snapshot, including highlight and lighting values.
|
|
38
|
+
* - applyRenderSettings(settings): Applies render settings, reloading only when a setting requires it.
|
|
35
39
|
* - showModel(): Shows the 3D model.
|
|
36
40
|
* - hideModel(): Hides the 3D model.
|
|
37
41
|
* - showEnvironment(): Shows the 3D environment/scene.
|
|
@@ -110,6 +114,7 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
110
114
|
* - "show-model": shows or hides the model according to the attribute value ("true"/"false").
|
|
111
115
|
* - "show-scene": shows or hides the scene according to the attribute value ("true"/"false").
|
|
112
116
|
* - "visible": shows or hides the component according to the attribute value ("true"/"false").
|
|
117
|
+
* - "show-animation-menu", "highlight-color", and "highlight-enabled": forward runtime UI/highlight settings to BabylonJSController.
|
|
113
118
|
*/
|
|
114
119
|
attributeChangedCallback(name, _old, value) {
|
|
115
120
|
switch (name) {
|
|
@@ -874,8 +879,9 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
874
879
|
|
|
875
880
|
/**
|
|
876
881
|
* Returns the current render settings used by the BabylonJSController.
|
|
882
|
+
* The snapshot is suitable for hydrating PrefViewerMenu3D controls.
|
|
877
883
|
* @public
|
|
878
|
-
* @returns {object|null} A shallow copy of the render settings or null if unavailable.
|
|
884
|
+
* @returns {object|null} A shallow copy of the render settings, including quality toggles, highlight state, and lighting time, or null if unavailable.
|
|
879
885
|
*/
|
|
880
886
|
getRenderSettings() {
|
|
881
887
|
if (!this.#babylonJSController) {
|
|
@@ -944,10 +950,20 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
944
950
|
}
|
|
945
951
|
|
|
946
952
|
/**
|
|
947
|
-
* Applies render settings
|
|
953
|
+
* Applies render settings received from PrefViewerMenu3D or another UI.
|
|
954
|
+
* Highlight and time-of-day lighting values are pushed directly to BabylonJSController; AA/SSAO/IBL/shadow changes
|
|
955
|
+
* are scheduled through the render-settings reload path when needed.
|
|
948
956
|
* @public
|
|
949
|
-
* @param {object} settings - Partial settings
|
|
950
|
-
* @
|
|
957
|
+
* @param {object} settings - Partial render settings payload.
|
|
958
|
+
* @param {boolean} [settings.antiAliasingEnabled] - Enables or disables anti-aliasing.
|
|
959
|
+
* @param {boolean} [settings.ambientOcclusionEnabled] - Enables or disables SSAO.
|
|
960
|
+
* @param {boolean} [settings.iblEnabled] - Enables or disables image-based lighting.
|
|
961
|
+
* @param {boolean} [settings.shadowsEnabled] - Enables or disables scene shadows.
|
|
962
|
+
* @param {boolean|string} [settings.highlightEnabled] - Enables or disables hover highlight; string "false" is normalized to false.
|
|
963
|
+
* @param {string} [settings.highlightColor] - Hex color used for hover highlight.
|
|
964
|
+
* @param {number} [settings.lightingTimeOfDay] - Normalized 0..1 time-of-day value.
|
|
965
|
+
* @param {boolean} [settings.persist=true] - False for transient lighting updates such as playback frames.
|
|
966
|
+
* @returns {Promise<{changed: boolean, success: boolean, detail?: object, controller?: object}>} Resolves with the apply/reload summary.
|
|
951
967
|
*/
|
|
952
968
|
async applyRenderSettings(settings) {
|
|
953
969
|
if (!this.#babylonJSController) {
|