@preference-sl/pref-viewer 2.17.0-beta.0 → 2.17.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/README.md +48 -16
- package/index.d.ts +2 -1
- package/package.json +1 -1
- package/src/babylonjs-animation-controller.js +12 -163
- package/src/babylonjs-animation-opening-menu.js +8 -1
- package/src/babylonjs-animation-opening.js +21 -1
- package/src/babylonjs-animation-overlay-controller.js +145 -0
- package/src/babylonjs-controller.js +332 -177
- package/src/babylonjs-selection-controller.js +215 -0
- package/src/babylonjs-view-cube-controller.js +169 -77
- package/src/file-storage.js +13 -0
- package/src/gltf-storage.js +97 -0
- package/src/localization/i18n.js +147 -74
- package/src/material-utils.js +67 -60
- package/src/panzoom-controller.js +14 -1
- package/src/pref-viewer-2d.js +16 -4
- package/src/pref-viewer-3d-data.js +166 -0
- package/src/pref-viewer-3d.js +11 -0
- package/src/pref-viewer-dialog.js +7 -1
- package/src/pref-viewer-menu-3d.js +55 -35
- package/src/pref-viewer.js +56 -20
- package/src/render-math.js +130 -117
- package/src/svg-resolver.js +5 -4
package/src/pref-viewer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PrefViewerTask from "./pref-viewer-task.js";
|
|
2
2
|
import { PREF_VIEWER_STYLES_STORAGE_KEY, PrefViewerStyles, PrefViewerStyleVariables } from "./styles.js";
|
|
3
|
-
import
|
|
3
|
+
import I18n from "./localization/i18n.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* PrefViewer - Custom Web Component for immersive 2D/3D product visualization with localization, persistence, and download tooling.
|
|
@@ -26,7 +26,7 @@ import { DEFAULT_LOCALE, resolveLocale, setLocale, SUPPORTED_LOCALES } from "./l
|
|
|
26
26
|
* - config: Configuration descriptor with assets, culture/styles and optional boolean showRenderMenu/showViewCube flags.
|
|
27
27
|
* - model / scene / materials / drawing: Independent asset entry points (URLs, Base64, indexed data) for each subsystem.
|
|
28
28
|
* - options: JSON describing render toggles (AA, SSAO, IBL, shadows), animation menu, highlight, lighting, and misc viewer knobs.
|
|
29
|
-
* - culture: Locale identifier (RFC-5646 style) that resolves against `SUPPORTED_LOCALES` before applying.
|
|
29
|
+
* - culture: Locale identifier (RFC-5646 style) that resolves against `I18n.SUPPORTED_LOCALES` before applying.
|
|
30
30
|
* - mode: Switches between `2d` and `3d`, automatically hiding/showing the relevant subcomponents and menu states.
|
|
31
31
|
* - show-model / show-scene: Boolean flags to toggle visibility without reloading the underlying resources.
|
|
32
32
|
* - show-render-menu: Boolean flag to show or hide the render-settings menu (default: true).
|
|
@@ -95,8 +95,8 @@ export default class PrefViewer extends HTMLElement {
|
|
|
95
95
|
|
|
96
96
|
// Localization/culture management
|
|
97
97
|
#CULTURE_STORAGE_KEY = "pref-viewer/culture";
|
|
98
|
-
#cultureInitial = DEFAULT_LOCALE;
|
|
99
|
-
#cultureList = SUPPORTED_LOCALES;
|
|
98
|
+
#cultureInitial = I18n.DEFAULT_LOCALE;
|
|
99
|
+
#cultureList = I18n.SUPPORTED_LOCALES;
|
|
100
100
|
#culture = "";
|
|
101
101
|
#cultureSuppressAttribute = false;
|
|
102
102
|
|
|
@@ -117,6 +117,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
117
117
|
#handlers = {
|
|
118
118
|
on2DZoomChanged: null,
|
|
119
119
|
on2DElementClick: null,
|
|
120
|
+
on3DElementClick: null,
|
|
120
121
|
onMenuApply: null,
|
|
121
122
|
onViewerHoverStart: null,
|
|
122
123
|
onViewerHoverEnd: null,
|
|
@@ -135,9 +136,9 @@ export default class PrefViewer extends HTMLElement {
|
|
|
135
136
|
this.attachShadow({ mode: "open" });
|
|
136
137
|
const storedCulture = this.#cultureReadPersisted();
|
|
137
138
|
if (storedCulture) {
|
|
138
|
-
this.#culture = resolveLocale(storedCulture);
|
|
139
|
+
this.#culture = I18n.resolveLocale(storedCulture);
|
|
139
140
|
} else {
|
|
140
|
-
this.#culture = setLocale(this.#cultureInitial);
|
|
141
|
+
this.#culture = I18n.setLocale(this.#cultureInitial);
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
this.#applyStyles(this.#stylesReadPersisted(), { persist: false, applyCssVariables: false });
|
|
@@ -281,6 +282,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
281
282
|
this.#component2D.remove();
|
|
282
283
|
}
|
|
283
284
|
if (this.#component3D) {
|
|
285
|
+
this.#component3D.removeEventListener("pref-viewer-3d-element-click", this.#handlers.on3DElementClick);
|
|
284
286
|
this.#component3D.removeEventListener("pref-viewer-3d-loaded", this.#handlers.on3DSceneLoaded);
|
|
285
287
|
this.#component3D.removeEventListener("pref-viewer-3d-error", this.#handlers.on3DSceneError);
|
|
286
288
|
this.#component3D.removeEventListener("view-cube-layout", this.#handlers.onViewCubeLayout);
|
|
@@ -355,6 +357,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
355
357
|
* @returns {void}
|
|
356
358
|
*/
|
|
357
359
|
#createComponent3D() {
|
|
360
|
+
this.#handlers.on3DElementClick = this.#on3DElementClick.bind(this);
|
|
358
361
|
this.#component3D = document.createElement("pref-viewer-3d");
|
|
359
362
|
this.#component3D.setAttribute("visible", "false");
|
|
360
363
|
this.#component3D.setAttribute("show-view-cube", this.getAttribute("show-view-cube") === "false" ? "false" : "true");
|
|
@@ -375,6 +378,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
375
378
|
this.#handlers.on3DSceneError = () => {
|
|
376
379
|
this.#menu3D?.setApplying(false, true);
|
|
377
380
|
};
|
|
381
|
+
this.#component3D.addEventListener("pref-viewer-3d-element-click", this.#handlers.on3DElementClick);
|
|
378
382
|
this.#component3D.addEventListener("pref-viewer-3d-loaded", this.#handlers.on3DSceneLoaded);
|
|
379
383
|
this.#component3D.addEventListener("pref-viewer-3d-error", this.#handlers.on3DSceneError);
|
|
380
384
|
this.#wrapper.appendChild(this.#component3D);
|
|
@@ -668,14 +672,14 @@ export default class PrefViewer extends HTMLElement {
|
|
|
668
672
|
return changed;
|
|
669
673
|
}
|
|
670
674
|
const { reflectAttribute = false } = options;
|
|
671
|
-
const resolved = resolveLocale(cultureId);
|
|
675
|
+
const resolved = I18n.resolveLocale(cultureId);
|
|
672
676
|
changed = resolved !== this.#culture;
|
|
673
677
|
if (reflectAttribute) {
|
|
674
678
|
this.#cultureReflectAttribute(resolved);
|
|
675
679
|
}
|
|
676
680
|
if (changed) {
|
|
677
681
|
this.#culture = resolved;
|
|
678
|
-
setLocale(resolved);
|
|
682
|
+
I18n.setLocale(resolved);
|
|
679
683
|
this.#menu3D?.setCulture?.(resolved);
|
|
680
684
|
this.#component3D?.refreshViewCube?.();
|
|
681
685
|
this.#culturePersist(resolved);
|
|
@@ -782,6 +786,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
782
786
|
|
|
783
787
|
this.removeAttribute("loading-3d");
|
|
784
788
|
this.setAttribute("loaded-3d", "");
|
|
789
|
+
this.#applySelectionStateTo3D();
|
|
785
790
|
|
|
786
791
|
this.#dispatchPrefViewerEvent("pref-viewer-3d-loaded", detail ?? {});
|
|
787
792
|
this.#dispatchPrefViewerEvent("scene-loaded", detail ?? {}); // Legacy event for backward compatibility
|
|
@@ -893,12 +898,23 @@ export default class PrefViewer extends HTMLElement {
|
|
|
893
898
|
this.notifyElementClick(event.detail?.ids ?? []);
|
|
894
899
|
}
|
|
895
900
|
|
|
901
|
+
/**
|
|
902
|
+
* Forwards semantic click ids from the encapsulated 3D component through the root event contract.
|
|
903
|
+
* @private
|
|
904
|
+
* @param {CustomEvent<{ids: string[]}>} event - Internal 3D model click event.
|
|
905
|
+
* @returns {void}
|
|
906
|
+
*/
|
|
907
|
+
#on3DElementClick(event) {
|
|
908
|
+
event.stopPropagation();
|
|
909
|
+
this.notifyElementClick(event.detail?.ids ?? []);
|
|
910
|
+
}
|
|
911
|
+
|
|
896
912
|
/**
|
|
897
913
|
* Handles the custom "pref-viewer-3d-render-settings-change-requested" event emitted by the menu component.
|
|
898
914
|
* Forwards the requested settings to the render-application pipeline.
|
|
899
915
|
* @private
|
|
900
916
|
* @param {CustomEvent} event - Menu event containing the `settings` payload.
|
|
901
|
-
* @returns {void}
|
|
917
|
+
* @returns {Promise<void>}
|
|
902
918
|
*/
|
|
903
919
|
async #onMenuApply(event) {
|
|
904
920
|
event.stopPropagation();
|
|
@@ -966,7 +982,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
966
982
|
* Processes the next task when finished.
|
|
967
983
|
* @private
|
|
968
984
|
* @param {object} drawing - The drawing object to process.
|
|
969
|
-
* @returns {void}
|
|
985
|
+
* @returns {Promise<void>}
|
|
970
986
|
*/
|
|
971
987
|
async #processDrawing(drawing) {
|
|
972
988
|
if (!this.#component2D) {
|
|
@@ -1028,7 +1044,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1028
1044
|
* Dispatches loading events and processes the next task.
|
|
1029
1045
|
* @private
|
|
1030
1046
|
* @param {object} options - The options object to process.
|
|
1031
|
-
* @returns {void}
|
|
1047
|
+
* @returns {Promise<void>}
|
|
1032
1048
|
*/
|
|
1033
1049
|
async #processOptions(options) {
|
|
1034
1050
|
if (!this.#component3D) {
|
|
@@ -1129,7 +1145,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1129
1145
|
* Non-string and empty-string results are filtered out.
|
|
1130
1146
|
* @private
|
|
1131
1147
|
* @param {Array<string|{id?: string, Id?: string}>} value - Array of id-like items to normalize.
|
|
1132
|
-
* @returns {string[]}
|
|
1148
|
+
* @returns {string[]} Non-empty string ids, preserving input order and duplicate occurrences.
|
|
1133
1149
|
*/
|
|
1134
1150
|
#normalizeIdList(value) {
|
|
1135
1151
|
if (!Array.isArray(value)) {
|
|
@@ -1180,11 +1196,31 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1180
1196
|
this.#dispatchPrefViewerEvent("pref-viewer-selection-state-changed", this.getSelectionState());
|
|
1181
1197
|
}
|
|
1182
1198
|
|
|
1183
|
-
/** Apply the current semantic snapshot to the 2D renderer when available.
|
|
1199
|
+
/** Apply the current semantic snapshot to the 2D renderer when available. * @returns {void}
|
|
1200
|
+
*/
|
|
1184
1201
|
#applySelectionStateTo2D() {
|
|
1185
1202
|
this.#component2D?.applySelectionState?.(this.getSelectionState());
|
|
1186
1203
|
}
|
|
1187
1204
|
|
|
1205
|
+
/**
|
|
1206
|
+
* Applies the current semantic snapshot to the 3D renderer when available.
|
|
1207
|
+
* @private
|
|
1208
|
+
* @returns {void}
|
|
1209
|
+
*/
|
|
1210
|
+
#applySelectionStateTo3D() {
|
|
1211
|
+
this.#component3D?.applySelectionState?.(this.getSelectionState());
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Synchronizes the current semantic selection snapshot with both renderers.
|
|
1216
|
+
* @private
|
|
1217
|
+
* @returns {void}
|
|
1218
|
+
*/
|
|
1219
|
+
#applySelectionState() {
|
|
1220
|
+
this.#applySelectionStateTo2D();
|
|
1221
|
+
this.#applySelectionStateTo3D();
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1188
1224
|
/**
|
|
1189
1225
|
* Dispatches a `pref-viewer-3d-turnable-changed` event carrying the current turnable snapshot.
|
|
1190
1226
|
* Called after `startTurnable`, `stopTurnable`, or `toggleTurnable` mutates the internal state.
|
|
@@ -1505,7 +1541,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1505
1541
|
*/
|
|
1506
1542
|
setSelectableElements(elements = []) {
|
|
1507
1543
|
this.#selectionState.selectableIds = this.#normalizeIdList(elements);
|
|
1508
|
-
this.#
|
|
1544
|
+
this.#applySelectionState();
|
|
1509
1545
|
this.#emitSelectionStateChanged();
|
|
1510
1546
|
return this.getSelectionState();
|
|
1511
1547
|
}
|
|
@@ -1520,7 +1556,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1520
1556
|
const selectedItems = this.#normalizeSelectionItems(elements);
|
|
1521
1557
|
this.#selectionState.selectedItems = selectedItems;
|
|
1522
1558
|
this.#selectionState.selectedIds = selectedItems.map((item) => item.id);
|
|
1523
|
-
this.#
|
|
1559
|
+
this.#applySelectionState();
|
|
1524
1560
|
this.#emitSelectionStateChanged();
|
|
1525
1561
|
return this.getSelectionState();
|
|
1526
1562
|
}
|
|
@@ -1536,7 +1572,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1536
1572
|
const selectedItems = this.#normalizeSelectionItems(state.selectedItems ?? state.selectedIds ?? state.selected ?? []);
|
|
1537
1573
|
this.#selectionState.selectedItems = selectedItems;
|
|
1538
1574
|
this.#selectionState.selectedIds = selectedItems.map((item) => item.id);
|
|
1539
|
-
this.#
|
|
1575
|
+
this.#applySelectionState();
|
|
1540
1576
|
this.#emitSelectionStateChanged();
|
|
1541
1577
|
return this.getSelectionState();
|
|
1542
1578
|
}
|
|
@@ -1549,7 +1585,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1549
1585
|
clearSelectedElements() {
|
|
1550
1586
|
this.#selectionState.selectedIds = [];
|
|
1551
1587
|
this.#selectionState.selectedItems = [];
|
|
1552
|
-
this.#
|
|
1588
|
+
this.#applySelectionState();
|
|
1553
1589
|
this.#emitSelectionStateChanged();
|
|
1554
1590
|
return this.getSelectionState();
|
|
1555
1591
|
}
|
|
@@ -1561,7 +1597,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1561
1597
|
*/
|
|
1562
1598
|
clearSelectableElements() {
|
|
1563
1599
|
this.#selectionState.selectableIds = [];
|
|
1564
|
-
this.#
|
|
1600
|
+
this.#applySelectionState();
|
|
1565
1601
|
this.#emitSelectionStateChanged();
|
|
1566
1602
|
return this.getSelectionState();
|
|
1567
1603
|
}
|
|
@@ -1577,7 +1613,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1577
1613
|
selectedIds: [],
|
|
1578
1614
|
selectedItems: [],
|
|
1579
1615
|
};
|
|
1580
|
-
this.#
|
|
1616
|
+
this.#applySelectionState();
|
|
1581
1617
|
this.#emitSelectionStateChanged();
|
|
1582
1618
|
return this.getSelectionState();
|
|
1583
1619
|
}
|
|
@@ -1643,7 +1679,7 @@ export default class PrefViewer extends HTMLElement {
|
|
|
1643
1679
|
* Remove every SVG node previously added through appendSvgNode.
|
|
1644
1680
|
* @public
|
|
1645
1681
|
* @returns {void}
|
|
1646
|
-
|
|
1682
|
+
*/
|
|
1647
1683
|
removeAppendedSvgNodes() {
|
|
1648
1684
|
this.#component2D?.removeAppendedSvgNodes?.();
|
|
1649
1685
|
}
|
package/src/render-math.js
CHANGED
|
@@ -1,127 +1,140 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure
|
|
3
|
-
*
|
|
2
|
+
* Pure lighting and hardware-scaling calculations without Babylon.js dependencies.
|
|
3
|
+
* @class RenderMath
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
export default class RenderMath {
|
|
6
|
+
/**
|
|
7
|
+
* Clamps a number to the [0, 1] range.
|
|
8
|
+
* Returns 0.5 for non-finite inputs (NaN, ±Infinity) so callers get a safe mid-point fallback.
|
|
9
|
+
* @public
|
|
10
|
+
* @static
|
|
11
|
+
* @param {number} value - The value to clamp.
|
|
12
|
+
* @returns {number} A number in [0, 1].
|
|
13
|
+
*/
|
|
14
|
+
static clamp01(value) {
|
|
15
|
+
if (!Number.isFinite(value)) {
|
|
16
|
+
return 0.5;
|
|
17
|
+
}
|
|
18
|
+
return Math.min(1, Math.max(0, value));
|
|
15
19
|
}
|
|
16
|
-
return Math.min(1, Math.max(0, value));
|
|
17
|
-
}
|
|
18
20
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Linearly interpolates between two values.
|
|
23
|
+
* The interpolation amount is clamped to [0, 1] before use.
|
|
24
|
+
* @public
|
|
25
|
+
* @static
|
|
26
|
+
* @param {number} from - Start value (returned when amount ≤ 0).
|
|
27
|
+
* @param {number} to - End value (returned when amount ≥ 1).
|
|
28
|
+
* @param {number} amount - Blend factor in [0, 1].
|
|
29
|
+
* @returns {number} Interpolated value between `from` and `to`.
|
|
30
|
+
*/
|
|
31
|
+
static lerp(from, to, amount) {
|
|
32
|
+
const t = RenderMath.clamp01(amount);
|
|
33
|
+
return from + (to - from) * t;
|
|
34
|
+
}
|
|
31
35
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Evaluates a bell-curve (half sine arc) over [0, 1].
|
|
38
|
+
* Returns 0 at both endpoints and 1 at the midpoint (x = 0.5).
|
|
39
|
+
* Used to model the intensity rise and fall of sunlight across the day.
|
|
40
|
+
* @private
|
|
41
|
+
* @static
|
|
42
|
+
* @param {number} x - Input in [0, 1]; clamped internally.
|
|
43
|
+
* @returns {number} Bell-curve output in [0, 1].
|
|
44
|
+
*/
|
|
45
|
+
static #bellCurve(x) {
|
|
46
|
+
const clamped = RenderMath.clamp01(x);
|
|
47
|
+
return Math.sin(clamped * Math.PI);
|
|
48
|
+
}
|
|
43
49
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Returns a unit vector for the given (x, y, z) direction.
|
|
52
|
+
* Leaves the zero vector unchanged by using a divisor of 1 when its length is zero.
|
|
53
|
+
* @private
|
|
54
|
+
* @static
|
|
55
|
+
* @param {number} x
|
|
56
|
+
* @param {number} y
|
|
57
|
+
* @param {number} z
|
|
58
|
+
* @returns {{x: number, y: number, z: number}} Normalized direction vector.
|
|
59
|
+
*/
|
|
60
|
+
static #normalizeDirection(x, y, z) {
|
|
61
|
+
const length = Math.hypot(x, y, z) || 1;
|
|
62
|
+
return {
|
|
63
|
+
x: x / length,
|
|
64
|
+
y: y / length,
|
|
65
|
+
z: z / length,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
60
68
|
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Computes the Babylon.js `hardwareScalingLevel` needed to render at the requested base
|
|
71
|
+
* resolution on a HiDPI display.
|
|
72
|
+
*
|
|
73
|
+
* Babylon's hardware scaling is the inverse of the device pixel ratio: a level of 0.5 on a
|
|
74
|
+
* 2× display renders at native resolution. This helper applies the base scaling factor (which
|
|
75
|
+
* may already downscale for performance on low-end hardware) on top of the device pixel ratio.
|
|
76
|
+
*
|
|
77
|
+
* Both inputs are validated and fall back to 1 when zero, negative, or non-finite.
|
|
78
|
+
* @public
|
|
79
|
+
* @static
|
|
80
|
+
* @param {number} [baseScaling=1] - Desired base scaling level (1 = full resolution, >1 = downscaled).
|
|
81
|
+
* @param {number} [pixelRatio=1] - Device pixel ratio (`window.devicePixelRatio`).
|
|
82
|
+
* @returns {number} Hardware scaling level to pass to `engine.setHardwareScalingLevel()`.
|
|
83
|
+
*/
|
|
84
|
+
static getAdaptiveHardwareScalingLevel(baseScaling = 1, pixelRatio = 1) {
|
|
85
|
+
const safeBaseScaling = Number.isFinite(baseScaling) && baseScaling > 0 ? baseScaling : 1;
|
|
86
|
+
const safePixelRatio = Number.isFinite(pixelRatio) && pixelRatio > 0 ? pixelRatio : 1;
|
|
87
|
+
return safeBaseScaling / safePixelRatio;
|
|
88
|
+
}
|
|
79
89
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Computes a lighting profile for a given time of day, used to drive the scene's directional
|
|
92
|
+
* lights, environment intensity, and tonemapping curve.
|
|
93
|
+
*
|
|
94
|
+
* The model follows a simplified solar arc: sunrise at 06:15 and sunset at 20:15 (fractional
|
|
95
|
+
* hours). Day factor peaks at solar noon and falls to zero outside daylight hours. Sun and moon
|
|
96
|
+
* directions are antipodal unit vectors derived from the sun phase angle.
|
|
97
|
+
* @public
|
|
98
|
+
* @static
|
|
99
|
+
* @param {number} [timeOfDay=0.5] - Normalized time in [0, 1] where 0 = midnight and 1 = next midnight.
|
|
100
|
+
* Values outside [0, 1] are clamped.
|
|
101
|
+
* @returns {{
|
|
102
|
+
* timeOfDay: number,
|
|
103
|
+
* timeHours: number,
|
|
104
|
+
* sunPhase: number,
|
|
105
|
+
* solarAzimuth: number,
|
|
106
|
+
* solarElevation: number,
|
|
107
|
+
* dayFactor: number,
|
|
108
|
+
* nightFactor: number,
|
|
109
|
+
* sunDirection: {x: number, y: number, z: number},
|
|
110
|
+
* moonDirection: {x: number, y: number, z: number}
|
|
111
|
+
* }} Lighting profile snapshot for the requested time.
|
|
112
|
+
*/
|
|
113
|
+
static getLightingCycleProfile(timeOfDay = 0.5) {
|
|
114
|
+
const normalizedTime = RenderMath.clamp01(timeOfDay);
|
|
115
|
+
const timeHours = normalizedTime * 24;
|
|
116
|
+
const sunriseHour = 6.25;
|
|
117
|
+
const sunsetHour = 20.25;
|
|
118
|
+
const daylightProgress = RenderMath.clamp01((timeHours - sunriseHour) / (sunsetHour - sunriseHour));
|
|
119
|
+
const dayFactor = timeHours >= sunriseHour && timeHours <= sunsetHour ? Math.pow(RenderMath.#bellCurve(daylightProgress), 0.88) : 0;
|
|
120
|
+
const nightFactor = 1 - dayFactor;
|
|
121
|
+
const solarArc = dayFactor > 0 ? Math.pow(dayFactor, 0.72) : 0;
|
|
122
|
+
const solarAzimuth = RenderMath.lerp(-Math.PI * 0.82, Math.PI * 0.82, daylightProgress);
|
|
123
|
+
const solarElevation = RenderMath.lerp(-0.18, 1.18, solarArc);
|
|
124
|
+
const sunPhase = normalizedTime * Math.PI * 2 - Math.PI / 2;
|
|
125
|
+
const sunDirection = RenderMath.#normalizeDirection(-Math.cos(sunPhase), -Math.sin(sunPhase), -0.35);
|
|
126
|
+
const moonDirection = RenderMath.#normalizeDirection(-sunDirection.x, -sunDirection.y, -sunDirection.z);
|
|
115
127
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
return {
|
|
129
|
+
timeOfDay: normalizedTime,
|
|
130
|
+
timeHours,
|
|
131
|
+
sunPhase,
|
|
132
|
+
solarAzimuth,
|
|
133
|
+
solarElevation,
|
|
134
|
+
dayFactor,
|
|
135
|
+
nightFactor,
|
|
136
|
+
sunDirection,
|
|
137
|
+
moonDirection,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
127
140
|
}
|
package/src/svg-resolver.js
CHANGED
|
@@ -15,7 +15,7 @@ import { initDb, loadModel } from "./gltf-storage.js";
|
|
|
15
15
|
* - Provides a unified API for consumers to obtain SVG content and metadata.
|
|
16
16
|
*
|
|
17
17
|
* Usage:
|
|
18
|
-
* - Instantiate with an initial SVG state object
|
|
18
|
+
* - Instantiate with an optional initial SVG state object.
|
|
19
19
|
* Example: const resolver = new SVGResolver({ value: "", size: 0, timeStamp: null, update: true });
|
|
20
20
|
* - Call getSVG(svgConfig) to resolve and update the SVG state.
|
|
21
21
|
* Example: await resolver.getSVG({ storage: { url: "data:image/svg+xml;base64,..." } });
|
|
@@ -78,7 +78,7 @@ export default class SVGResolver {
|
|
|
78
78
|
* Parse an input that may be a plain SVG string or a data URI (possibly base64).
|
|
79
79
|
* @private
|
|
80
80
|
* @param {string} source - Data URI or raw string to decode/check.
|
|
81
|
-
* @returns {
|
|
81
|
+
* @returns {[string|null, number]} Decoded SVG text or null, followed by the source size used for cache comparison.
|
|
82
82
|
*/
|
|
83
83
|
#parseSource(source) {
|
|
84
84
|
let value = null;
|
|
@@ -210,14 +210,15 @@ export default class SVGResolver {
|
|
|
210
210
|
/**
|
|
211
211
|
* Resolve and prepare an SVG from multiple storage forms.
|
|
212
212
|
* @public
|
|
213
|
-
* @param {object
|
|
213
|
+
* @param {object} svgConfig - Configuration object. Expected shape for storage:
|
|
214
214
|
* { storage: { url: "<url>" } } // remote URL or data URI (base64 or plain)
|
|
215
215
|
* { storage: { db: "<db>", table: "<table>", id: "<id>" } } // IndexedDB record
|
|
216
216
|
* @param {object} [svg] - Optional external SVG state object to seed/update. If provided, the resolver
|
|
217
217
|
* updates this object in-place: { value: string, size: number, timeStamp: string|null, update: boolean }.
|
|
218
218
|
* @returns {Promise<false|{value:string,size:number,timeStamp:string|null,update:boolean}>}
|
|
219
|
-
* - Resolves to false when no update is required or
|
|
219
|
+
* - Resolves to false when no update is required or the source cannot be decoded/fetched.
|
|
220
220
|
* - Resolves to the internal SVG state object when an update was prepared.
|
|
221
|
+
* @throws {Error} Rejects on IndexedDB failures or an absent record in the requested database.
|
|
221
222
|
* @description
|
|
222
223
|
* - Accepts SVG (base64 or plain string) from IndexedDB (storage.db/table/id), from a data URI (base64 or plain string) (storage.url),
|
|
223
224
|
* from a remote URL (storage.url) or from direct SVG string (storage.url).
|