@preference-sl/pref-viewer 3.1.0-beta.1 → 3.1.0-beta.3
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/README.md +4 -4
- package/package.json +1 -1
- package/src/babylonjs-ar-glass.js +10 -3
- package/src/babylonjs-controller.js +3 -1
- package/src/babylonjs-view-cube-canvas-renderer.js +6 -4
- package/src/ibl-source-resolver.js +107 -0
- package/src/material-utils.js +21 -0
- package/src/pref-viewer-3d-data.js +6 -5
- package/src/pref-viewer-3d.js +6 -6
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ VR uses the `local-floor` reference space without teleportation, controller sele
|
|
|
38
38
|
|
|
39
39
|
Android WebXR preserves the glTF's authored metre dimensions, including existing unit-conversion transforms. It never fits the model to the screen or permits pinch scaling. Assets must encode their dimensions in metres and orient their interior toward the viewer's existing Inner (+Z) direction; the viewer cannot infer an interior face or correct arbitrary source units. The upstream exporter owns those asset conventions.
|
|
40
40
|
|
|
41
|
-
During Android WebXR AR, model materials using physical transmission/refraction or translucency temporarily use alpha blending so the camera background can show through the glass. This is a scalar approximation:
|
|
41
|
+
During Android WebXR AR, model materials using physical transmission/refraction or translucency temporarily use alpha blending so the camera background can show through the glass. This is a scalar approximation: glass the viewer tuned for the web look keeps its authored base color alpha as the AR opacity (the SDK's window glass, `0.15`), so it reads like the 3D view; other physical glass uses 0.35 opacity for full transmission, combined with existing geometric opacity; linked-refraction alpha is interpreted as transmission rather than geometric opacity. The same conversion is applied to the USDZ prepared for iOS Quick Look. Colors, textures and reflection parameters remain unchanged, but physical camera refraction, blur and transmission texture masks are not reproduced. Opaque and existing ordinary alpha-transparent materials are left alone. Original glass values are restored on exit/cancellation, model or material replacement and teardown; normal 3D and Quick Look preparation outside WebXR retain their original materials. The fallback also applies to the no-floor preview. Shared material objects are adapted once and restored together. Real Android glass appearance still requires a device check.
|
|
42
42
|
|
|
43
43
|
On AR entry, the virtual environment is hidden. If no qualifying floor is available, the first finite native camera pose centres the preview two metres along its view ray, retaining the authored dimensions, with Inner facing the phone horizontally. The preview retains its initial world position and yaw instead of following the phone, and cannot be dragged. It is not a verified physical anchor or calibrated 1:1 measurement; erroneous native camera motion can still change its apparent size and position. Pending, failed or empty hit tests do not prevent this preview. A missing native camera pose still prevents initial placement. When a native horizontal-plane hit below the phone becomes available and the phone has a usable horizontal heading, the preview transitions to floor placement: its bottom centre is placed on that height, with its phone-facing orientation corrected by the minimum yaw to align model axis lines with the detected floor axes (nearest multiple of 90 degrees, at most 45 degrees correction). The preferred initial distance is three horizontal metres ahead. Optional native plane detection supplies a floor contour; the viewer checks the full horizontal bounding footprint at its authored scale and initial yaw. If it cannot fit at three metres, it uses the farthest fitting centre on the same forward segment up to three metres. If contours are unavailable/unusable or no full fit exists, it provisionally places the model at three metres without a fit guarantee. Later contour changes never reposition an already placed model. This transition can move the model visibly. It stays in world space as the user moves. The detected horizontal plane pose supplies the grid axes independently of contour-fit availability: a contour containing the accepted hit takes priority, otherwise the horizontal pose below the camera nearest in height to that hit is used (horizontal proximity breaks ties). Without a usable native pose, the X/Z axes of the WebXR tracking reference space provide the reported fallback. Pose-only orientation never changes the retained floor height or guarantees that the footprint fits. Diagnostics report `floorAlignmentSource`, `floorAlignmentStatus`, `floorAlignmentYaw` and `floorAlignmentHeightDifference`. The grid is frozen for model initial placement and gesture feedback; later camera/contour changes do not re-snap the model. Touch the model and drag with one finger to translate it on the retained floor plane, keeping the grab offset. A short tap outside the model places its bottom centre at the tapped point on that plane. Placement commits on release; moving the finger by more than one degree of ray direction cancels an off-model tap, even if it returns to its starting point. Adding a second tracked finger cancels translation or a pending floor tap and starts two-finger rotation around the vertical bottom-centre axis. Rotate the two fingers to change yaw (the rotation sense is inverted from the preceding implementation); pinching never changes scale. The bottom anchor stays fixed. Lifting either finger, a third finger or lost input cancels rotation; lift all fingers before starting another translation. This requires the Android runtime to expose both screen input sources. Fresh surface hits are not required after floor detection: other-height surfaces do not change the retained height. Missing/invalid input tracking, rays parallel to the floor or intersections behind the camera retain the last placement. This uses the mathematical plane at the observed height and does not verify its physical extent or rule out obstacles. WebXR does not universally distinguish a floor from a table. A contour fit checks the reported approximate surface, not obstacles or physical room boundaries; provisional placement provides no contour-fit guarantee. Leaving AR restores the authored model transforms and environment. `getDebugInfo().ar` reports placement state, metre units, initial distance, position and drag state. The diagnostic state `preview` distinguishes the camera-based fallback from `placed` on a detected floor; the fallback does not claim a detected floor or solve native calibration/tracking failures. Large models may extend beyond the viewport because their authored scale is preserved. The no-floor preview remains centred two metres along the initial camera ray.
|
|
44
44
|
|
|
@@ -580,7 +580,7 @@ Render settings are persisted in `localStorage` under the `pref-viewer/render-se
|
|
|
580
580
|
|
|
581
581
|
The showroom daylight background uses a shared linear HDR color calibrated with exposure and ACES tone mapping for the current light studio gray target, `#d9d9d9`, in a scene-less view. Both the static look and time-of-day cycle use this same daylight value; the cycle retains its dark night endpoint. Scene geometry can cover the background. The internal `BabylonJSController.config.look.showroom.backgroundDay` value is not a public background-color option and must be recalibrated if tone mapping or exposure changes.
|
|
582
582
|
|
|
583
|
-
The model's transmissive glass (`KHR_materials_transmission`, as authored by the SDK) also carries a base color alpha of `0.15`, which would multiply its reflection away and leave it flat grey. The viewer sets that alpha to `1` and `metallicF0Factor` to `3` (`BabylonJSController.config.look.glass`, internal like the showroom look) so the environment reflection reads while the glass stays see-through through transmission. Glass in the environment glTF is unaffected: it is simplified to alpha blending instead.
|
|
583
|
+
The model's transmissive glass (`KHR_materials_transmission`, as authored by the SDK) also carries a base color alpha of `0.15`, which would multiply its reflection away and leave it flat grey. The viewer sets that alpha to `1` and `metallicF0Factor` to `3` (`BabylonJSController.config.look.glass`, internal like the showroom look) so the environment reflection reads while the glass stays see-through through transmission. The authored alpha is remembered and becomes the glass opacity in native AR (Android WebXR and the iOS Quick Look USDZ), where there is no physical transmission. Glass in the environment glTF is unaffected: it is simplified to alpha blending instead.
|
|
584
584
|
|
|
585
585
|
## Environment map and scene lights
|
|
586
586
|
|
|
@@ -591,7 +591,7 @@ The model's transmissive glass (`KHR_materials_transmission`, as authored by the
|
|
|
591
591
|
| `.env` | Prefiltered Babylon environment (`CubeTexture.CreateFromPrefilteredData`) | No prefiltering in the browser; a 4K environment is typically under 1 MB. |
|
|
592
592
|
| anything else | Radiance HDR (`HDRCubeTexture`) | Prefiltered on load, which costs time and memory for large files. |
|
|
593
593
|
|
|
594
|
-
Either file is downloaded once, cached in IndexedDB, and reused across reloads.
|
|
594
|
+
Either file is downloaded once, cached in IndexedDB, and reused across reloads. The cached bytes reach Babylon as an in-memory file, never as a `blob:` URL: iOS WebKit answers requests to such URLs with status 0, and the environment then never became ready, so the 3D load never finished. A cached record that is empty or cannot be read within 5 s is purged and the server URL is used for that load.
|
|
595
595
|
|
|
596
596
|
Directional and spot lights that come with the scene glTF (`KHR_lights_punctual`) cast shadows when `render.shadowsEnabled` is `true` and `ibl.shadows` is off, alongside the built-in light's. Every scene and model mesh casts unless its node sets `extras.castShadows: false`, and receives unless it sets `extras.receiveShadows: false`. A light's shadow darkness comes from its node's `extras.shadowDarkness`, from `0` (black) to `1` (no shadow), and is `0.5` when absent:
|
|
597
597
|
|
|
@@ -771,7 +771,7 @@ Both ViewCubes use the palette defined once by `PrefViewerViewCubePalette` in `s
|
|
|
771
771
|
|
|
772
772
|
Public customization uses the unified [Runtime styles](#runtime-styles) schema. The CSS tokens in `PrefViewerViewCubePalette` and `PrefViewerViewCubeTypography` are internal implementation details projected inside the shadow boundary. Palette defaults are centralized; Canvas resolves the computed internal values and falls back for invalid colors. The optional internal dark palette is not automatically selected or a package-root export.
|
|
773
773
|
|
|
774
|
-
Keep these rules when changing the renderer: the cube scene stays virtual and its engine stays out of `EngineStore.Instances` while it is alive (otherwise the viewer can create resources on the cube's WebGL context); Babylon input stays detached from the cube canvas (otherwise the cursor and keyboard focus are disturbed); and the engine keeps `loseContextOnDispose` so its WebGL context is released.
|
|
774
|
+
Keep these rules when changing the renderer: the cube scene stays virtual and its engine stays out of `EngineStore.Instances` while it is alive (otherwise the viewer can create resources on the cube's WebGL context); Babylon input stays detached from the cube canvas (otherwise the cursor and keyboard focus are disturbed); and the engine keeps `loseContextOnDispose` so its WebGL context is released. Both the viewer engine and the cube engine request their WebGL context with an explicit `powerPreference: "default"`: Babylon fills in `"high-performance"` when the option is absent, and iOS 18.7 / Safari 26.6.1 on iPhone returns a context that is already lost for that preference, which left the 3D view blank.
|
|
775
775
|
|
|
776
776
|
ViewCube listener registration, drag handling and hover updates are private instance methods. Region construction, colour parsing and extent interpolation are private static helpers; interpolation progress is passed explicitly. The render menu also encapsulates hue-channel calculation and lighting playback as private methods. Playback uses one callback bound to the menu instance and cancels its scheduled frame on stop or disconnection.
|
|
777
777
|
|
package/package.json
CHANGED
|
@@ -42,7 +42,11 @@ export default class BabylonJSARGlass {
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Snapshots physical glass and switches it to alpha blending without depth occlusion.
|
|
45
|
-
*
|
|
45
|
+
* Glass the viewer tuned for the web look (`MaterialUtils.tunePhysicalGlassMaterial`) takes its authored
|
|
46
|
+
* base alpha as the AR opacity when that alpha is below 1, so it reads like the web view instead of the
|
|
47
|
+
* 0.35 floor of the transmission approximation; glass authored opaque keeps the approximation.
|
|
48
|
+
* Linked refraction uses alpha as inverse transmission, so neither the
|
|
49
|
+
* live nor the authored alpha is reused as geometric opacity there.
|
|
46
50
|
* Retains a disposal observer only while the snapshot is owned; disposed materials are forgotten untouched.
|
|
47
51
|
* @param {import("@babylonjs/core").PBRMaterial} material - Current physical glass not already adapted.
|
|
48
52
|
* @returns {void} Does not allocate/dispose materials or textures or modify color/reflection properties.
|
|
@@ -64,8 +68,11 @@ export default class BabylonJSARGlass {
|
|
|
64
68
|
this.#materials.set(material, saved);
|
|
65
69
|
sub.isRefractionEnabled = false;
|
|
66
70
|
sub.isTranslucencyEnabled = false;
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
const authored = linked ? null : MaterialUtils.authoredGlassAlpha(material);
|
|
72
|
+
material.alpha = authored !== null && authored < 1
|
|
73
|
+
? authored
|
|
74
|
+
: (linked ? 1 : alpha) * Math.max(BabylonJSARGlass.#MIN_GLASS_OPACITY,
|
|
75
|
+
1 - transmission * BabylonJSARGlass.#TRANSMISSION_REDUCTION);
|
|
69
76
|
material.transparencyMode = Material.MATERIAL_ALPHABLEND;
|
|
70
77
|
material.disableDepthWrite = true;
|
|
71
78
|
material.forceDepthWrite = false;
|
|
@@ -3940,7 +3940,9 @@ export default class BabylonJSController {
|
|
|
3940
3940
|
}
|
|
3941
3941
|
const enabledGeneration = ++this.#xrGeneration;
|
|
3942
3942
|
this.#configureDracoCompression();
|
|
3943
|
-
|
|
3943
|
+
// powerPreference is explicit: Babylon fills in "high-performance" when it is absent, and iOS 18.7 /
|
|
3944
|
+
// Safari 26.6.1 (iPhone) returns a WebGL2 context that is already lost for that preference.
|
|
3945
|
+
this.#engine = new Engine(this.#canvas, true, { alpha: true, stencil: true, preserveDrawingBuffer: false, powerPreference: "default" }, true);
|
|
3944
3946
|
this.#engine.disableUniformBuffers = true;
|
|
3945
3947
|
|
|
3946
3948
|
// OPTIMIZATION: Detect hardware tier and apply quality settings
|
|
@@ -87,11 +87,13 @@ export default class BabylonJSViewCubeCanvasRenderer {
|
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* Options of the default cube Engine (as in PrefSketch, plus loseContextOnDispose). Babylon only
|
|
91
|
-
* WEBGL_lose_context.loseContext() on dispose when loseContextOnDispose is set; without it the cube's WebGL
|
|
92
|
-
* would linger until garbage collection, and browsers cap the number of live contexts per page.
|
|
90
|
+
* Options of the default cube Engine (as in PrefSketch, plus loseContextOnDispose and powerPreference). Babylon only
|
|
91
|
+
* calls WEBGL_lose_context.loseContext() on dispose when loseContextOnDispose is set; without it the cube's WebGL
|
|
92
|
+
* context would linger until garbage collection, and browsers cap the number of live contexts per page.
|
|
93
|
+
* powerPreference is explicit because Babylon fills in "high-performance" otherwise, and iOS 18.7 / Safari 26.6.1
|
|
94
|
+
* (iPhone) returns a WebGL2 context that is already lost for that preference.
|
|
93
95
|
*/
|
|
94
|
-
static #VIEW_CUBE_ENGINE_OPTIONS = Object.freeze({ alpha: true, antialias: true, premultipliedAlpha: false, stencil: false, loseContextOnDispose: true });
|
|
96
|
+
static #VIEW_CUBE_ENGINE_OPTIONS = Object.freeze({ alpha: true, antialias: true, premultipliedAlpha: false, stencil: false, loseContextOnDispose: true, powerPreference: "default" });
|
|
95
97
|
|
|
96
98
|
static #DEFAULT_LABELS = Object.freeze({ right: "Right", left: "Left", bottom: "Bottom", top: "Top", back: "Back", front: "Front" });
|
|
97
99
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { FilesInputStore } from "@babylonjs/core";
|
|
2
|
+
import FileStorage from "./file-storage.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the environment map (`options.ibl.url`) into a source Babylon can load on every browser.
|
|
6
|
+
*
|
|
7
|
+
* The cached environment used to reach Babylon as a `blob:` object URL of the IndexedDB record. iOS WebKit answers
|
|
8
|
+
* an XHR to such a URL with status 0, Babylon retries and gives up, and the texture never becomes ready, so the
|
|
9
|
+
* 3D load never finished. Like `GLTFResolver` does for `.glb` files, the cached bytes are read here into memory and
|
|
10
|
+
* handed to Babylon as a `File` through its `file:` scheme (`FilesInputStore`), so no XHR touches a blob. A record
|
|
11
|
+
* that is empty or cannot be read in time is purged and the server URL is used instead, so the load always completes.
|
|
12
|
+
*
|
|
13
|
+
* Every resolution registers its own file name; the owner releases it with `release` once Babylon has consumed it.
|
|
14
|
+
*/
|
|
15
|
+
export default class IBLSourceResolver {
|
|
16
|
+
static #FILE_PREFIX = "pref-viewer-ibl-";
|
|
17
|
+
static #sequence = 0;
|
|
18
|
+
#fileStorage;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Upper bound for reading a cached environment out of IndexedDB before falling back to the server URL.
|
|
22
|
+
* @public
|
|
23
|
+
* @static
|
|
24
|
+
* @type {number}
|
|
25
|
+
*/
|
|
26
|
+
static CACHE_READ_TIMEOUT_MS = 5000;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {FileStorage} [fileStorage] - Cache to read from; defaults to the viewer's shared store.
|
|
30
|
+
*/
|
|
31
|
+
constructor(fileStorage = new FileStorage("PrefViewer", "Files")) {
|
|
32
|
+
this.#fileStorage = fileStorage;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves one environment URL into a loadable source.
|
|
37
|
+
* @public
|
|
38
|
+
* @param {string} url - Server URL of the `.env` or `.hdr` file.
|
|
39
|
+
* @returns {Promise<{cachedUrl: string, timeStamp: string|null}|false>} The `file:` source of the in-memory copy
|
|
40
|
+
* (or the server URL when the cache cannot serve it) with the record's time stamp, or false when the file is
|
|
41
|
+
* unavailable both in the cache and on the server.
|
|
42
|
+
*/
|
|
43
|
+
async resolve(url) {
|
|
44
|
+
let record;
|
|
45
|
+
try {
|
|
46
|
+
record = await IBLSourceResolver.#withTimeout(this.#fileStorage.get(url), IBLSourceResolver.CACHE_READ_TIMEOUT_MS);
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (!record || !record.blob) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const timeStamp = record.timeStamp ?? null;
|
|
54
|
+
let bytes = null;
|
|
55
|
+
if (record.blob.size > 0) {
|
|
56
|
+
try {
|
|
57
|
+
bytes = await IBLSourceResolver.#withTimeout(record.blob.arrayBuffer(), IBLSourceResolver.CACHE_READ_TIMEOUT_MS);
|
|
58
|
+
} catch {
|
|
59
|
+
bytes = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!bytes || bytes.byteLength === 0) {
|
|
63
|
+
// Fire-and-forget: a hung IndexedDB transaction must not block this load either.
|
|
64
|
+
void Promise.resolve(this.#fileStorage.remove(url)).catch(() => {});
|
|
65
|
+
return { cachedUrl: url, timeStamp };
|
|
66
|
+
}
|
|
67
|
+
const baseName = url.split(/[?#]/)[0].split("/").pop() || "environment";
|
|
68
|
+
const name = `${IBLSourceResolver.#FILE_PREFIX}${++IBLSourceResolver.#sequence}-${baseName}`;
|
|
69
|
+
FilesInputStore.FilesToLoad[name.toLowerCase()] = new File([bytes], name, { type: record.blob.type || "" });
|
|
70
|
+
return { cachedUrl: `file:${name}`, timeStamp };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Releases a source produced by `resolve` (or an older `blob:` URL): drops the in-memory file from Babylon's
|
|
75
|
+
* store or revokes the object URL. Server URLs and empty values are ignored.
|
|
76
|
+
* @public
|
|
77
|
+
* @static
|
|
78
|
+
* @param {string|null|undefined} cachedUrl - Source to release.
|
|
79
|
+
* @returns {void}
|
|
80
|
+
*/
|
|
81
|
+
static release(cachedUrl) {
|
|
82
|
+
if (typeof cachedUrl !== "string") {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (cachedUrl.startsWith("file:")) {
|
|
86
|
+
delete FilesInputStore.FilesToLoad[decodeURIComponent(cachedUrl.slice("file:".length)).toLowerCase()];
|
|
87
|
+
} else if (cachedUrl.startsWith("blob:")) {
|
|
88
|
+
URL.revokeObjectURL(cachedUrl);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Bounds a cache read so an IndexedDB transaction that never settles cannot block the load.
|
|
94
|
+
* @private
|
|
95
|
+
* @static
|
|
96
|
+
* @param {Promise<any>} promise - Read to bound.
|
|
97
|
+
* @param {number} ms - Time allowed before rejecting.
|
|
98
|
+
* @returns {Promise<any>} The read's result, or a rejection after `ms`.
|
|
99
|
+
*/
|
|
100
|
+
static #withTimeout(promise, ms) {
|
|
101
|
+
let timer = null;
|
|
102
|
+
const timeout = new Promise((resolve, reject) => {
|
|
103
|
+
timer = setTimeout(() => reject(new Error(`Cache read timed out after ${ms}ms`)), ms);
|
|
104
|
+
});
|
|
105
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
106
|
+
}
|
|
107
|
+
}
|
package/src/material-utils.js
CHANGED
|
@@ -6,6 +6,7 @@ import { PBRMaterial } from "@babylonjs/core";
|
|
|
6
6
|
*/
|
|
7
7
|
export default class MaterialUtils {
|
|
8
8
|
static #SIMPLIFIED_TRANSMISSION_KEY = "prefViewerSimplifiedTransmission";
|
|
9
|
+
static #AUTHORED_GLASS_ALPHA_KEY = "prefViewerAuthoredGlassAlpha";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Tags a PBR material as having been converted from physical KHR_materials_transmission to simple
|
|
@@ -70,6 +71,12 @@ export default class MaterialUtils {
|
|
|
70
71
|
return false;
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
if (MaterialUtils.authoredGlassAlpha(mat) === null && Number.isFinite(mat.alpha)) {
|
|
75
|
+
mat.metadata = {
|
|
76
|
+
...(mat.metadata ?? {}),
|
|
77
|
+
[MaterialUtils.#AUTHORED_GLASS_ALPHA_KEY]: Math.max(0, Math.min(1, mat.alpha)),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
73
80
|
mat.alpha = look?.alpha ?? 1;
|
|
74
81
|
mat.metallicF0Factor = look?.metallicF0Factor ?? 3;
|
|
75
82
|
mat.useSpecularOverAlpha = true;
|
|
@@ -77,6 +84,20 @@ export default class MaterialUtils {
|
|
|
77
84
|
return true;
|
|
78
85
|
}
|
|
79
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Returns the base color alpha the glass was authored with (for example the SDK's `0.15`) before
|
|
89
|
+
* `tunePhysicalGlassMaterial` raised it for the web look. Native AR has no physical transmission,
|
|
90
|
+
* so that authored alpha is the opacity that keeps the glass looking like the web view.
|
|
91
|
+
* @public
|
|
92
|
+
* @static
|
|
93
|
+
* @param {import("@babylonjs/core").Material|null|undefined} mat - Material to read.
|
|
94
|
+
* @returns {number|null} The authored alpha in [0, 1], or null when the material was never tuned.
|
|
95
|
+
*/
|
|
96
|
+
static authoredGlassAlpha(mat) {
|
|
97
|
+
const value = mat?.metadata?.[MaterialUtils.#AUTHORED_GLASS_ALPHA_KEY];
|
|
98
|
+
return Number.isFinite(value) ? value : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
80
101
|
/**
|
|
81
102
|
* Returns true when a material should be placed in the transparent rendering group (renderingGroupId=1).
|
|
82
103
|
*
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import IBLSourceResolver from "./ibl-source-resolver.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* ContainerData - Represents state, cache metadata, and update lifecycle for a 3D asset container
|
|
3
5
|
* (for example: model, environment, or materials).
|
|
@@ -410,15 +412,14 @@ export class IBLData {
|
|
|
410
412
|
}
|
|
411
413
|
|
|
412
414
|
/**
|
|
413
|
-
*
|
|
415
|
+
* Releases a temporary cached source: a `blob:` object URL is revoked and a `file:` source registered by
|
|
416
|
+
* `IBLSourceResolver` is dropped from Babylon's file store. Server URLs are left alone.
|
|
414
417
|
* @private
|
|
415
|
-
* @param {string|null|undefined} url
|
|
418
|
+
* @param {string|null|undefined} url Cached source to release when applicable.
|
|
416
419
|
* @returns {void}
|
|
417
420
|
*/
|
|
418
421
|
#revokeIfBlobURL(url) {
|
|
419
|
-
|
|
420
|
-
URL.revokeObjectURL(url);
|
|
421
|
-
}
|
|
422
|
+
IBLSourceResolver.release(url);
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
/**
|
package/src/pref-viewer-3d.js
CHANGED
|
@@ -2,7 +2,7 @@ import { CameraData, ContainerData, MaterialData, IBLData } from "./pref-viewer-
|
|
|
2
2
|
import BabylonJSController from "./babylonjs-controller.js";
|
|
3
3
|
import { PrefViewer3DStyles } from "./styles.js";
|
|
4
4
|
import PrefViewerControlsPresentation from "./pref-viewer-controls-presentation.js";
|
|
5
|
-
import
|
|
5
|
+
import IBLSourceResolver from "./ibl-source-resolver.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* PrefViewer3D - Custom Web Component for interactive 3D visualization and configuration.
|
|
@@ -446,12 +446,12 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
446
446
|
let intensity = undefined;
|
|
447
447
|
|
|
448
448
|
if (typeof options.ibl.url === "string" && options.ibl.url.length > 0) {
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
if (
|
|
449
|
+
// The cached bytes reach Babylon as an in-memory file, never as a blob: URL (iOS WebKit cannot XHR those).
|
|
450
|
+
const source = await new IBLSourceResolver().resolve(options.ibl.url);
|
|
451
|
+
if (source) {
|
|
452
452
|
url = options.ibl.url;
|
|
453
|
-
cachedUrl =
|
|
454
|
-
timeStamp =
|
|
453
|
+
cachedUrl = source.cachedUrl;
|
|
454
|
+
timeStamp = source.timeStamp;
|
|
455
455
|
} else {
|
|
456
456
|
url = null;
|
|
457
457
|
cachedUrl = null;
|