@nice2dev/ui-3d 1.3.14 → 1.3.16

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.
Files changed (108) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/collaborative/collaborativeScene.js +160 -206
  3. package/dist/cjs/core/editorAsk.js +51 -41
  4. package/dist/cjs/core/i18n.js +13 -14
  5. package/dist/cjs/core/threeCompat.js +29 -42
  6. package/dist/cjs/dance/DanceBridge.js +148 -177
  7. package/dist/cjs/dance/DanceScoreEngine.js +209 -221
  8. package/dist/cjs/dance/PoseDetector.js +302 -275
  9. package/dist/cjs/index.js +110 -113
  10. package/dist/cjs/material/MaterialEditor.module.css.js +242 -5
  11. package/dist/cjs/material/NiceMaterialEditor.js +1213 -757
  12. package/dist/cjs/material/materialEditorTypes.js +35 -49
  13. package/dist/cjs/material/materialEditorUtils.js +684 -747
  14. package/dist/cjs/material/materialNodeDefinitions.js +1411 -1459
  15. package/dist/cjs/model/ModelEditor.js +90 -40
  16. package/dist/cjs/model/ModelEditor.module.css.js +257 -5
  17. package/dist/cjs/model/ModelEditorLeftPanel.js +126 -47
  18. package/dist/cjs/model/ModelEditorMenuBar.js +359 -32
  19. package/dist/cjs/model/ModelEditorRightPanel.js +647 -131
  20. package/dist/cjs/model/ModelEditorSubComponents.js +277 -112
  21. package/dist/cjs/model/ModelEditorTimeline.js +232 -34
  22. package/dist/cjs/model/ModelEditorToolbar.js +170 -14
  23. package/dist/cjs/model/ModelEditorViewport.js +231 -39
  24. package/dist/cjs/model/ModelViewer.js +388 -63
  25. package/dist/cjs/model/ModelViewer.module.css.js +77 -5
  26. package/dist/cjs/model/NiceArmatureEditor.js +473 -274
  27. package/dist/cjs/model/NiceMorphTargetEditor.js +333 -227
  28. package/dist/cjs/model/NiceOctree.js +372 -320
  29. package/dist/cjs/model/NicePhysicsSimulation.js +528 -298
  30. package/dist/cjs/model/NiceProceduralGeometry.js +673 -300
  31. package/dist/cjs/model/NiceTerrainEditor.js +595 -385
  32. package/dist/cjs/model/NiceWeightPainter.js +384 -261
  33. package/dist/cjs/model/NiceXRPreview.js +385 -308
  34. package/dist/cjs/model/cadModeUtils.js +103 -106
  35. package/dist/cjs/model/editorShortcuts.js +171 -194
  36. package/dist/cjs/model/modelEditorTypes.js +100 -118
  37. package/dist/cjs/model/modelEditorUtils.js +1061 -1146
  38. package/dist/cjs/model/simsModeUtils.js +1023 -1053
  39. package/dist/cjs/model/useModelEditor.js +1977 -1933
  40. package/dist/cjs/model/useModelViewer.js +633 -665
  41. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  42. package/dist/cjs/particle/NiceParticleEditor.js +1186 -571
  43. package/dist/cjs/particle/ParticleEditor.module.css.js +122 -5
  44. package/dist/cjs/particle/particleEditorTypes.js +62 -70
  45. package/dist/cjs/particle/particleEditorUtils.js +998 -1013
  46. package/dist/cjs/rendering/NiceCascadedShadows.js +381 -259
  47. package/dist/cjs/rendering/NiceRenderExport.js +424 -337
  48. package/dist/cjs/rendering/NiceSSAO.js +454 -337
  49. package/dist/cjs/rendering/NiceSSR.js +387 -261
  50. package/dist/cjs/rendering/NiceWebGPURenderer.js +359 -230
  51. package/dist/cjs/uv/NiceUVEditor.js +787 -550
  52. package/dist/cjs/uv/UVEditor.module.css.js +53 -5
  53. package/dist/cjs/uv/uvEditorTypes.js +59 -76
  54. package/dist/cjs/uv/uvEditorUtils.js +501 -575
  55. package/dist/esm/collaborative/collaborativeScene.js +163 -205
  56. package/dist/esm/core/editorAsk.js +53 -40
  57. package/dist/esm/core/i18n.js +15 -13
  58. package/dist/esm/core/threeCompat.js +18 -26
  59. package/dist/esm/dance/DanceBridge.js +151 -176
  60. package/dist/esm/dance/DanceScoreEngine.js +211 -220
  61. package/dist/esm/dance/PoseDetector.js +304 -274
  62. package/dist/esm/index.js +258 -36
  63. package/dist/esm/material/MaterialEditor.module.css.js +242 -3
  64. package/dist/esm/material/NiceMaterialEditor.js +1215 -756
  65. package/dist/esm/material/materialEditorTypes.js +45 -48
  66. package/dist/esm/material/materialEditorUtils.js +713 -746
  67. package/dist/esm/material/materialNodeDefinitions.js +1415 -1458
  68. package/dist/esm/model/ModelEditor.js +91 -39
  69. package/dist/esm/model/ModelEditor.module.css.js +257 -3
  70. package/dist/esm/model/ModelEditorLeftPanel.js +126 -45
  71. package/dist/esm/model/ModelEditorMenuBar.js +359 -30
  72. package/dist/esm/model/ModelEditorRightPanel.js +634 -114
  73. package/dist/esm/model/ModelEditorSubComponents.js +265 -94
  74. package/dist/esm/model/ModelEditorTimeline.js +232 -32
  75. package/dist/esm/model/ModelEditorToolbar.js +170 -12
  76. package/dist/esm/model/ModelEditorViewport.js +231 -37
  77. package/dist/esm/model/ModelViewer.js +389 -62
  78. package/dist/esm/model/ModelViewer.module.css.js +77 -3
  79. package/dist/esm/model/NiceArmatureEditor.js +462 -258
  80. package/dist/esm/model/NiceMorphTargetEditor.js +322 -211
  81. package/dist/esm/model/NiceOctree.js +361 -304
  82. package/dist/esm/model/NicePhysicsSimulation.js +517 -282
  83. package/dist/esm/model/NiceProceduralGeometry.js +667 -284
  84. package/dist/esm/model/NiceTerrainEditor.js +584 -369
  85. package/dist/esm/model/NiceWeightPainter.js +373 -245
  86. package/dist/esm/model/NiceXRPreview.js +374 -292
  87. package/dist/esm/model/cadModeUtils.js +95 -88
  88. package/dist/esm/model/editorShortcuts.js +172 -193
  89. package/dist/esm/model/modelEditorTypes.js +90 -100
  90. package/dist/esm/model/modelEditorUtils.js +1056 -1128
  91. package/dist/esm/model/simsModeUtils.js +1021 -1035
  92. package/dist/esm/model/useModelEditor.js +1946 -1920
  93. package/dist/esm/model/useModelViewer.js +619 -647
  94. package/dist/esm/nice2dev-ui-3d.css +1 -1
  95. package/dist/esm/particle/NiceParticleEditor.js +1188 -570
  96. package/dist/esm/particle/ParticleEditor.module.css.js +122 -3
  97. package/dist/esm/particle/particleEditorTypes.js +69 -69
  98. package/dist/esm/particle/particleEditorUtils.js +1027 -1012
  99. package/dist/esm/rendering/NiceCascadedShadows.js +370 -243
  100. package/dist/esm/rendering/NiceRenderExport.js +413 -321
  101. package/dist/esm/rendering/NiceSSAO.js +450 -328
  102. package/dist/esm/rendering/NiceSSR.js +383 -252
  103. package/dist/esm/rendering/NiceWebGPURenderer.js +326 -214
  104. package/dist/esm/uv/NiceUVEditor.js +788 -549
  105. package/dist/esm/uv/UVEditor.module.css.js +53 -3
  106. package/dist/esm/uv/uvEditorTypes.js +68 -75
  107. package/dist/esm/uv/uvEditorUtils.js +515 -557
  108. package/package.json +7 -12
@@ -1,281 +1,403 @@
1
- 'use strict';
2
-
3
- var jsxRuntime = require('react/jsx-runtime');
4
- var ui = require('@nice2dev/ui');
5
- var React = require('react');
6
- var THREE = require('three');
7
- var i18n = require('../core/i18n.js');
8
-
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const ui = require("@nice2dev/ui");
5
+ const React = require("react");
6
+ const THREE = require("three");
7
+ const i18n = require("../core/i18n.js");
9
8
  function _interopNamespaceDefault(e) {
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
9
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
10
+ if (e) {
11
+ for (const k in e) {
12
+ if (k !== "default") {
13
+ const d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: () => e[k]
20
17
  });
18
+ }
21
19
  }
22
- n.default = e;
23
- return Object.freeze(n);
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
24
23
  }
25
-
26
- var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
27
-
28
- /* ── Cascade split calculation ── */
24
+ const THREE__namespace = /* @__PURE__ */ _interopNamespaceDefault(THREE);
29
25
  function computeCascadeSplits(cascadeCount, near, far, splitMode, lambda) {
30
- const splits = [];
31
- for (let i = 1; i <= cascadeCount; i++) {
32
- const p = i / cascadeCount;
33
- let logSplit, linearSplit;
34
- switch (splitMode) {
35
- case 'logarithmic':
36
- splits.push(near * Math.pow(far / near, p));
37
- break;
38
- case 'linear':
39
- splits.push(near + (far - near) * p);
40
- break;
41
- case 'practical':
42
- logSplit = near * Math.pow(far / near, p);
43
- linearSplit = near + (far - near) * p;
44
- splits.push(lambda * logSplit + (1 - lambda) * linearSplit);
45
- break;
46
- }
26
+ const splits = [];
27
+ for (let i = 1; i <= cascadeCount; i++) {
28
+ const p = i / cascadeCount;
29
+ let logSplit, linearSplit;
30
+ switch (splitMode) {
31
+ case "logarithmic":
32
+ splits.push(near * Math.pow(far / near, p));
33
+ break;
34
+ case "linear":
35
+ splits.push(near + (far - near) * p);
36
+ break;
37
+ case "practical":
38
+ logSplit = near * Math.pow(far / near, p);
39
+ linearSplit = near + (far - near) * p;
40
+ splits.push(lambda * logSplit + (1 - lambda) * linearSplit);
41
+ break;
47
42
  }
48
- return splits;
43
+ }
44
+ return splits;
49
45
  }
50
46
  function computeFrustumCorners(camera, nearDist, farDist) {
51
- const fov = (camera.fov * Math.PI) / 180;
52
- const aspect = camera.aspect;
53
- const nearHalfH = Math.tan(fov / 2) * nearDist;
54
- const nearHalfW = nearHalfH * aspect;
55
- const farHalfH = Math.tan(fov / 2) * farDist;
56
- const farHalfW = farHalfH * aspect;
57
- camera.matrixWorldInverse;
58
- const invView = camera.matrixWorld;
59
- const corners = [
60
- // Near plane
61
- new THREE__namespace.Vector3(-nearHalfW, -nearHalfH, -nearDist),
62
- new THREE__namespace.Vector3(nearHalfW, -nearHalfH, -nearDist),
63
- new THREE__namespace.Vector3(nearHalfW, nearHalfH, -nearDist),
64
- new THREE__namespace.Vector3(-nearHalfW, nearHalfH, -nearDist),
65
- // Far plane
66
- new THREE__namespace.Vector3(-farHalfW, -farHalfH, -farDist),
67
- new THREE__namespace.Vector3(farHalfW, -farHalfH, -farDist),
68
- new THREE__namespace.Vector3(farHalfW, farHalfH, -farDist),
69
- new THREE__namespace.Vector3(-farHalfW, farHalfH, -farDist),
70
- ];
71
- // Transform to world space
72
- for (const corner of corners) {
73
- corner.applyMatrix4(invView);
74
- }
75
- return corners;
47
+ const fov = camera.fov * Math.PI / 180;
48
+ const aspect = camera.aspect;
49
+ const nearHalfH = Math.tan(fov / 2) * nearDist;
50
+ const nearHalfW = nearHalfH * aspect;
51
+ const farHalfH = Math.tan(fov / 2) * farDist;
52
+ const farHalfW = farHalfH * aspect;
53
+ camera.matrixWorldInverse;
54
+ const invView = camera.matrixWorld;
55
+ const corners = [
56
+ // Near plane
57
+ new THREE__namespace.Vector3(-nearHalfW, -nearHalfH, -nearDist),
58
+ new THREE__namespace.Vector3(nearHalfW, -nearHalfH, -nearDist),
59
+ new THREE__namespace.Vector3(nearHalfW, nearHalfH, -nearDist),
60
+ new THREE__namespace.Vector3(-nearHalfW, nearHalfH, -nearDist),
61
+ // Far plane
62
+ new THREE__namespace.Vector3(-farHalfW, -farHalfH, -farDist),
63
+ new THREE__namespace.Vector3(farHalfW, -farHalfH, -farDist),
64
+ new THREE__namespace.Vector3(farHalfW, farHalfH, -farDist),
65
+ new THREE__namespace.Vector3(-farHalfW, farHalfH, -farDist)
66
+ ];
67
+ for (const corner of corners) {
68
+ corner.applyMatrix4(invView);
69
+ }
70
+ return corners;
76
71
  }
77
- /* ── Hook ── */
78
72
  const DEFAULT_CSM = {
79
- cascadeCount: 3,
80
- splitMode: 'practical',
81
- practicalSplitLambda: 0.5,
82
- maxFar: 200,
83
- shadowMapSize: 2048,
84
- lightDirection: new THREE__namespace.Vector3(-1, -2, -1).normalize(),
85
- lightIntensity: 1.5,
86
- fadeDistance: 5,
87
- cascadeOverrides: [],
73
+ cascadeCount: 3,
74
+ splitMode: "practical",
75
+ practicalSplitLambda: 0.5,
76
+ maxFar: 200,
77
+ shadowMapSize: 2048,
78
+ lightDirection: new THREE__namespace.Vector3(-1, -2, -1).normalize(),
79
+ lightIntensity: 1.5,
80
+ fadeDistance: 5,
81
+ cascadeOverrides: []
88
82
  };
89
83
  function useCSM(scene, camera, configOverride) {
90
- const [config, setConfigState] = React.useState({ ...DEFAULT_CSM, ...configOverride });
91
- const [showDebug, setShowDebug] = React.useState(false);
92
- const [cascadeSplits, setCascadeSplits] = React.useState([]);
93
- const lightsRef = React.useRef([]);
94
- const debugGroup = React.useRef(new THREE__namespace.Group());
95
- const setConfig = React.useCallback((partial) => {
96
- setConfigState((prev) => ({ ...prev, ...partial }));
97
- }, []);
98
- // Create/update cascade lights
99
- React.useEffect(() => {
100
- var _a, _b, _c, _d, _e;
101
- if (!scene || !camera) {
102
- return;
103
- }
104
- // Remove old lights
105
- for (const light of lightsRef.current) {
106
- scene.remove(light);
107
- scene.remove(light.target);
108
- (_a = light.shadow.map) === null || _a === void 0 ? void 0 : _a.dispose();
84
+ const [config, setConfigState] = React.useState({ ...DEFAULT_CSM, ...configOverride });
85
+ const [showDebug, setShowDebug] = React.useState(false);
86
+ const [cascadeSplits, setCascadeSplits] = React.useState([]);
87
+ const lightsRef = React.useRef([]);
88
+ const debugGroup = React.useRef(new THREE__namespace.Group());
89
+ const setConfig = React.useCallback((partial) => {
90
+ setConfigState((prev) => ({ ...prev, ...partial }));
91
+ }, []);
92
+ React.useEffect(() => {
93
+ if (!scene || !camera) {
94
+ return;
95
+ }
96
+ for (const light of lightsRef.current) {
97
+ scene.remove(light);
98
+ scene.remove(light.target);
99
+ light.shadow.map?.dispose();
100
+ }
101
+ lightsRef.current = [];
102
+ const near = camera.near;
103
+ const far = Math.min(camera.far, config.maxFar);
104
+ const splits = computeCascadeSplits(
105
+ config.cascadeCount,
106
+ near,
107
+ far,
108
+ config.splitMode,
109
+ config.practicalSplitLambda
110
+ );
111
+ setCascadeSplits(splits);
112
+ let prevSplit = near;
113
+ for (let i = 0; i < config.cascadeCount; i++) {
114
+ const cascadeFar = splits[i];
115
+ const override = config.cascadeOverrides[i] ?? {};
116
+ const light = new THREE__namespace.DirectionalLight(
117
+ 16777215,
118
+ config.lightIntensity / config.cascadeCount
119
+ );
120
+ light.castShadow = true;
121
+ light.name = `__csm_light_${i}__`;
122
+ const mapSize = override.resolution ?? config.shadowMapSize;
123
+ light.shadow.mapSize.set(mapSize, mapSize);
124
+ light.shadow.bias = override.bias ?? -1e-3;
125
+ light.shadow.normalBias = override.normalBias ?? 0.02;
126
+ const corners = computeFrustumCorners(camera, prevSplit, cascadeFar);
127
+ const center = new THREE__namespace.Vector3();
128
+ for (const c of corners) {
129
+ center.add(c);
130
+ }
131
+ center.divideScalar(8);
132
+ let radius = 0;
133
+ for (const c of corners) {
134
+ radius = Math.max(radius, center.distanceTo(c));
135
+ }
136
+ radius = Math.ceil(radius);
137
+ const lightPos = center.clone().addScaledVector(config.lightDirection, -radius * 2);
138
+ light.position.copy(lightPos);
139
+ light.target.position.copy(center);
140
+ light.shadow.camera.left = -radius;
141
+ light.shadow.camera.right = radius;
142
+ light.shadow.camera.top = radius;
143
+ light.shadow.camera.bottom = -radius;
144
+ light.shadow.camera.near = 0.1;
145
+ light.shadow.camera.far = radius * 4;
146
+ light.shadow.camera.updateProjectionMatrix();
147
+ scene.add(light);
148
+ scene.add(light.target);
149
+ lightsRef.current.push(light);
150
+ prevSplit = cascadeFar;
151
+ }
152
+ return () => {
153
+ for (const light of lightsRef.current) {
154
+ scene.remove(light);
155
+ scene.remove(light.target);
156
+ light.shadow.map?.dispose();
157
+ }
158
+ lightsRef.current = [];
159
+ };
160
+ }, [scene, camera, config]);
161
+ React.useEffect(() => {
162
+ if (!scene) {
163
+ return;
164
+ }
165
+ const dbg = debugGroup.current;
166
+ dbg.name = "__csm_debug__";
167
+ if (showDebug && camera) {
168
+ while (dbg.children.length) {
169
+ dbg.remove(dbg.children[0]);
170
+ }
171
+ const near = camera.near;
172
+ let prevSplit = near;
173
+ const colors = [16711680, 65280, 255, 16776960, 16711935];
174
+ for (let i = 0; i < cascadeSplits.length; i++) {
175
+ const cascadeFar = cascadeSplits[i];
176
+ const corners = computeFrustumCorners(camera, prevSplit, cascadeFar);
177
+ const lineGeo = new THREE__namespace.BufferGeometry().setFromPoints([
178
+ ...corners.slice(0, 4),
179
+ corners[0],
180
+ // near quad
181
+ ...corners.slice(4),
182
+ corners[4]
183
+ // far quad
184
+ ]);
185
+ dbg.add(new THREE__namespace.Line(lineGeo, new THREE__namespace.LineBasicMaterial({ color: colors[i % 5] })));
186
+ for (let j = 0; j < 4; j++) {
187
+ const connectGeo = new THREE__namespace.BufferGeometry().setFromPoints([corners[j], corners[j + 4]]);
188
+ dbg.add(
189
+ new THREE__namespace.Line(connectGeo, new THREE__namespace.LineBasicMaterial({ color: colors[i % 5] }))
190
+ );
109
191
  }
110
- lightsRef.current = [];
111
- const near = camera.near;
112
- const far = Math.min(camera.far, config.maxFar);
113
- const splits = computeCascadeSplits(config.cascadeCount, near, far, config.splitMode, config.practicalSplitLambda);
114
- setCascadeSplits(splits);
115
- // Create a directional light per cascade
116
- let prevSplit = near;
117
- for (let i = 0; i < config.cascadeCount; i++) {
118
- const cascadeFar = splits[i];
119
- const override = (_b = config.cascadeOverrides[i]) !== null && _b !== void 0 ? _b : {};
120
- const light = new THREE__namespace.DirectionalLight(0xffffff, config.lightIntensity / config.cascadeCount);
121
- light.castShadow = true;
122
- light.name = `__csm_light_${i}__`;
123
- const mapSize = (_c = override.resolution) !== null && _c !== void 0 ? _c : config.shadowMapSize;
124
- light.shadow.mapSize.set(mapSize, mapSize);
125
- light.shadow.bias = (_d = override.bias) !== null && _d !== void 0 ? _d : -1e-3;
126
- light.shadow.normalBias = (_e = override.normalBias) !== null && _e !== void 0 ? _e : 0.02;
127
- // Compute cascade frustum bounds
128
- const corners = computeFrustumCorners(camera, prevSplit, cascadeFar);
129
- const center = new THREE__namespace.Vector3();
130
- for (const c of corners) {
131
- center.add(c);
132
- }
133
- center.divideScalar(8);
134
- // Compute sphere radius to encompass frustum
135
- let radius = 0;
136
- for (const c of corners) {
137
- radius = Math.max(radius, center.distanceTo(c));
192
+ prevSplit = cascadeFar;
193
+ }
194
+ scene.add(dbg);
195
+ } else {
196
+ scene.remove(dbg);
197
+ }
198
+ return () => {
199
+ scene.remove(dbg);
200
+ };
201
+ }, [scene, camera, showDebug, cascadeSplits]);
202
+ const update = React.useCallback(() => {
203
+ if (!camera || !scene) {
204
+ return;
205
+ }
206
+ const near = camera.near;
207
+ const far = Math.min(camera.far, config.maxFar);
208
+ const splits = computeCascadeSplits(
209
+ config.cascadeCount,
210
+ near,
211
+ far,
212
+ config.splitMode,
213
+ config.practicalSplitLambda
214
+ );
215
+ let prevSplit = near;
216
+ for (let i = 0; i < lightsRef.current.length; i++) {
217
+ const light = lightsRef.current[i];
218
+ const cascadeFar = splits[i] ?? far;
219
+ const corners = computeFrustumCorners(camera, prevSplit, cascadeFar);
220
+ const center = new THREE__namespace.Vector3();
221
+ for (const c of corners) {
222
+ center.add(c);
223
+ }
224
+ center.divideScalar(8);
225
+ let radius = 0;
226
+ for (const c of corners) {
227
+ radius = Math.max(radius, center.distanceTo(c));
228
+ }
229
+ radius = Math.ceil(radius);
230
+ const lightPos = center.clone().addScaledVector(config.lightDirection, -radius * 2);
231
+ light.position.copy(lightPos);
232
+ light.target.position.copy(center);
233
+ light.shadow.camera.left = -radius;
234
+ light.shadow.camera.right = radius;
235
+ light.shadow.camera.top = radius;
236
+ light.shadow.camera.bottom = -radius;
237
+ light.shadow.camera.far = radius * 4;
238
+ light.shadow.camera.updateProjectionMatrix();
239
+ prevSplit = cascadeFar;
240
+ }
241
+ }, [camera, scene, config]);
242
+ const dispose = React.useCallback(() => {
243
+ if (!scene) {
244
+ return;
245
+ }
246
+ for (const light of lightsRef.current) {
247
+ scene.remove(light);
248
+ scene.remove(light.target);
249
+ light.shadow.map?.dispose();
250
+ }
251
+ lightsRef.current = [];
252
+ scene.remove(debugGroup.current);
253
+ }, [scene]);
254
+ return {
255
+ config,
256
+ setConfig,
257
+ cascadeSplits,
258
+ lights: lightsRef.current,
259
+ showDebug,
260
+ setShowDebug,
261
+ update,
262
+ dispose
263
+ };
264
+ }
265
+ const NiceCascadedShadows = ({
266
+ scene,
267
+ camera,
268
+ config: configProp,
269
+ showDebug: initialDebug,
270
+ className = ""
271
+ }) => {
272
+ const { t } = i18n.useNiceTranslation();
273
+ const { config, setConfig, cascadeSplits, showDebug, setShowDebug } = useCSM(
274
+ scene,
275
+ camera,
276
+ configProp
277
+ );
278
+ React.useEffect(() => {
279
+ if (initialDebug !== void 0) {
280
+ setShowDebug(initialDebug);
281
+ }
282
+ }, [initialDebug, setShowDebug]);
283
+ return /* @__PURE__ */ jsxRuntime.jsxs(
284
+ "div",
285
+ {
286
+ className: `nice-cascaded-shadows ${className}`,
287
+ style: { display: "grid", gap: 8, fontSize: 13 },
288
+ children: [
289
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
290
+ /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }),
291
+ " ",
292
+ t("cascadedShadows.cascadedShadowMaps", "Cascaded Shadow Maps")
293
+ ] }),
294
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
295
+ t("cascadedShadows.cascades", "Cascades: {cascadeCount}").replace(
296
+ "{cascadeCount}",
297
+ String(config.cascadeCount)
298
+ ),
299
+ /* @__PURE__ */ jsxRuntime.jsx(
300
+ "input",
301
+ {
302
+ type: "range",
303
+ min: 1,
304
+ max: 5,
305
+ value: config.cascadeCount,
306
+ onChange: (e) => setConfig({ cascadeCount: parseInt(e.target.value, 10) }),
307
+ style: { width: "100%" }
138
308
  }
139
- radius = Math.ceil(radius);
140
- // Position light
141
- const lightPos = center.clone().addScaledVector(config.lightDirection, -radius * 2);
142
- light.position.copy(lightPos);
143
- light.target.position.copy(center);
144
- // Orthographic shadow camera
145
- light.shadow.camera.left = -radius;
146
- light.shadow.camera.right = radius;
147
- light.shadow.camera.top = radius;
148
- light.shadow.camera.bottom = -radius;
149
- light.shadow.camera.near = 0.1;
150
- light.shadow.camera.far = radius * 4;
151
- light.shadow.camera.updateProjectionMatrix();
152
- scene.add(light);
153
- scene.add(light.target);
154
- lightsRef.current.push(light);
155
- prevSplit = cascadeFar;
156
- }
157
- return () => {
158
- var _a;
159
- for (const light of lightsRef.current) {
160
- scene.remove(light);
161
- scene.remove(light.target);
162
- (_a = light.shadow.map) === null || _a === void 0 ? void 0 : _a.dispose();
309
+ )
310
+ ] }),
311
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
312
+ t("cascadedShadows.maxDistance", "Max Distance: {maxFar}").replace(
313
+ "{maxFar}",
314
+ String(config.maxFar)
315
+ ),
316
+ /* @__PURE__ */ jsxRuntime.jsx(
317
+ "input",
318
+ {
319
+ type: "range",
320
+ min: 50,
321
+ max: 1e3,
322
+ step: 10,
323
+ value: config.maxFar,
324
+ onChange: (e) => setConfig({ maxFar: parseInt(e.target.value, 10) }),
325
+ style: { width: "100%" }
163
326
  }
164
- lightsRef.current = [];
165
- };
166
- }, [scene, camera, config]);
167
- // Debug visualization
168
- React.useEffect(() => {
169
- if (!scene) {
170
- return;
171
- }
172
- const dbg = debugGroup.current;
173
- dbg.name = '__csm_debug__';
174
- if (showDebug && camera) {
175
- // Clear old
176
- while (dbg.children.length) {
177
- dbg.remove(dbg.children[0]);
327
+ )
328
+ ] }),
329
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
330
+ t("cascadedShadows.splitMode", "Split Mode:"),
331
+ /* @__PURE__ */ jsxRuntime.jsxs(
332
+ "select",
333
+ {
334
+ value: config.splitMode,
335
+ onChange: (e) => setConfig({ splitMode: e.target.value }),
336
+ style: { width: "100%", marginTop: 2 },
337
+ children: [
338
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "practical", children: t("cascadedShadows.practical", "Practical") }),
339
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "logarithmic", children: t("cascadedShadows.logarithmic", "Logarithmic") }),
340
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "linear", children: t("cascadedShadows.linear", "Linear") })
341
+ ]
178
342
  }
179
- const near = camera.near;
180
- let prevSplit = near;
181
- const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff];
182
- for (let i = 0; i < cascadeSplits.length; i++) {
183
- const cascadeFar = cascadeSplits[i];
184
- const corners = computeFrustumCorners(camera, prevSplit, cascadeFar);
185
- // Draw frustum wireframe
186
- const lineGeo = new THREE__namespace.BufferGeometry().setFromPoints([
187
- ...corners.slice(0, 4),
188
- corners[0], // near quad
189
- ...corners.slice(4),
190
- corners[4], // far quad
191
- ]);
192
- dbg.add(new THREE__namespace.Line(lineGeo, new THREE__namespace.LineBasicMaterial({ color: colors[i % 5] })));
193
- // Connecting lines
194
- for (let j = 0; j < 4; j++) {
195
- const connectGeo = new THREE__namespace.BufferGeometry().setFromPoints([corners[j], corners[j + 4]]);
196
- dbg.add(new THREE__namespace.Line(connectGeo, new THREE__namespace.LineBasicMaterial({ color: colors[i % 5] })));
197
- }
198
- prevSplit = cascadeFar;
343
+ )
344
+ ] }),
345
+ config.splitMode === "practical" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
346
+ t("cascadedShadows.lambda", "Lambda: {practicalSplitLambda}").replace(
347
+ "{practicalSplitLambda}",
348
+ String(config.practicalSplitLambda.toFixed(2))
349
+ ),
350
+ /* @__PURE__ */ jsxRuntime.jsx(
351
+ "input",
352
+ {
353
+ type: "range",
354
+ min: 0,
355
+ max: 1,
356
+ step: 0.05,
357
+ value: config.practicalSplitLambda,
358
+ onChange: (e) => setConfig({ practicalSplitLambda: parseFloat(e.target.value) }),
359
+ style: { width: "100%" }
199
360
  }
200
- scene.add(dbg);
201
- }
202
- else {
203
- scene.remove(dbg);
204
- }
205
- return () => {
206
- scene.remove(dbg);
207
- };
208
- }, [scene, camera, showDebug, cascadeSplits]);
209
- const update = React.useCallback(() => {
210
- var _a;
211
- if (!camera || !scene) {
212
- return;
213
- }
214
- const near = camera.near;
215
- const far = Math.min(camera.far, config.maxFar);
216
- const splits = computeCascadeSplits(config.cascadeCount, near, far, config.splitMode, config.practicalSplitLambda);
217
- let prevSplit = near;
218
- for (let i = 0; i < lightsRef.current.length; i++) {
219
- const light = lightsRef.current[i];
220
- const cascadeFar = (_a = splits[i]) !== null && _a !== void 0 ? _a : far;
221
- const corners = computeFrustumCorners(camera, prevSplit, cascadeFar);
222
- const center = new THREE__namespace.Vector3();
223
- for (const c of corners) {
224
- center.add(c);
361
+ )
362
+ ] }),
363
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
364
+ t("cascadedShadows.shadowMapSize", "Shadow Map Size: {shadowMapSize}").replace(
365
+ "{shadowMapSize}",
366
+ String(config.shadowMapSize)
367
+ ),
368
+ /* @__PURE__ */ jsxRuntime.jsxs(
369
+ "select",
370
+ {
371
+ value: config.shadowMapSize,
372
+ onChange: (e) => setConfig({ shadowMapSize: parseInt(e.target.value, 10) }),
373
+ style: { width: "100%", marginTop: 2 },
374
+ children: [
375
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 512, children: "512" }),
376
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 1024, children: "1024" }),
377
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 2048, children: "2048" }),
378
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 4096, children: "4096" })
379
+ ]
225
380
  }
226
- center.divideScalar(8);
227
- let radius = 0;
228
- for (const c of corners) {
229
- radius = Math.max(radius, center.distanceTo(c));
381
+ )
382
+ ] }),
383
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 10, opacity: 0.7 }, children: t("cascadedShadows.cascadeSplits", "Cascade splits: {cascadeSplits}").replace(
384
+ "{cascadeSplits}",
385
+ String(cascadeSplits.map((s) => s.toFixed(1)).join(" | "))
386
+ ) }),
387
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
388
+ /* @__PURE__ */ jsxRuntime.jsx(
389
+ "input",
390
+ {
391
+ type: "checkbox",
392
+ checked: showDebug,
393
+ onChange: (e) => setShowDebug(e.target.checked)
230
394
  }
231
- radius = Math.ceil(radius);
232
- const lightPos = center.clone().addScaledVector(config.lightDirection, -radius * 2);
233
- light.position.copy(lightPos);
234
- light.target.position.copy(center);
235
- light.shadow.camera.left = -radius;
236
- light.shadow.camera.right = radius;
237
- light.shadow.camera.top = radius;
238
- light.shadow.camera.bottom = -radius;
239
- light.shadow.camera.far = radius * 4;
240
- light.shadow.camera.updateProjectionMatrix();
241
- prevSplit = cascadeFar;
242
- }
243
- }, [camera, scene, config]);
244
- const dispose = React.useCallback(() => {
245
- var _a;
246
- if (!scene) {
247
- return;
248
- }
249
- for (const light of lightsRef.current) {
250
- scene.remove(light);
251
- scene.remove(light.target);
252
- (_a = light.shadow.map) === null || _a === void 0 ? void 0 : _a.dispose();
253
- }
254
- lightsRef.current = [];
255
- scene.remove(debugGroup.current);
256
- }, [scene]);
257
- return {
258
- config,
259
- setConfig,
260
- cascadeSplits,
261
- lights: lightsRef.current,
262
- showDebug,
263
- setShowDebug,
264
- update,
265
- dispose,
266
- };
267
- }
268
- /* ── Component ── */
269
- const NiceCascadedShadows = ({ scene, camera, config: configProp, showDebug: initialDebug, className = '', }) => {
270
- const { t } = i18n.useNiceTranslation();
271
- const { config, setConfig, cascadeSplits, showDebug, setShowDebug } = useCSM(scene, camera, configProp);
272
- React.useEffect(() => {
273
- if (initialDebug !== undefined) {
274
- setShowDebug(initialDebug);
275
- }
276
- }, [initialDebug, setShowDebug]);
277
- return (jsxRuntime.jsxs("div", { className: `nice-cascaded-shadows ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('cascadedShadows.cascadedShadowMaps', 'Cascaded Shadow Maps')] }), jsxRuntime.jsxs("label", { children: [t('cascadedShadows.cascades', 'Cascades: {cascadeCount}').replace('{cascadeCount}', String(config.cascadeCount)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: config.cascadeCount, onChange: (e) => setConfig({ cascadeCount: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('cascadedShadows.maxDistance', 'Max Distance: {maxFar}').replace('{maxFar}', String(config.maxFar)), jsxRuntime.jsx("input", { type: "range", min: 50, max: 1000, step: 10, value: config.maxFar, onChange: (e) => setConfig({ maxFar: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('cascadedShadows.splitMode', 'Split Mode:'), jsxRuntime.jsxs("select", { value: config.splitMode, onChange: (e) => setConfig({ splitMode: e.target.value }), style: { width: '100%', marginTop: 2 }, children: [jsxRuntime.jsx("option", { value: "practical", children: t('cascadedShadows.practical', 'Practical') }), jsxRuntime.jsx("option", { value: "logarithmic", children: t('cascadedShadows.logarithmic', 'Logarithmic') }), jsxRuntime.jsx("option", { value: "linear", children: t('cascadedShadows.linear', 'Linear') })] })] }), config.splitMode === 'practical' && (jsxRuntime.jsxs("label", { children: [t('cascadedShadows.lambda', 'Lambda: {practicalSplitLambda}').replace('{practicalSplitLambda}', String(config.practicalSplitLambda.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: config.practicalSplitLambda, onChange: (e) => setConfig({ practicalSplitLambda: parseFloat(e.target.value) }), style: { width: '100%' } })] })), jsxRuntime.jsxs("label", { children: [t('cascadedShadows.shadowMapSize', 'Shadow Map Size: {shadowMapSize}').replace('{shadowMapSize}', String(config.shadowMapSize)), jsxRuntime.jsxs("select", { value: config.shadowMapSize, onChange: (e) => setConfig({ shadowMapSize: parseInt(e.target.value, 10) }), style: { width: '100%', marginTop: 2 }, children: [jsxRuntime.jsx("option", { value: 512, children: "512" }), jsxRuntime.jsx("option", { value: 1024, children: "1024" }), jsxRuntime.jsx("option", { value: 2048, children: "2048" }), jsxRuntime.jsx("option", { value: 4096, children: "4096" })] })] }), jsxRuntime.jsx("div", { style: { fontSize: 10, opacity: 0.7 }, children: t('cascadedShadows.cascadeSplits', 'Cascade splits: {cascadeSplits}').replace('{cascadeSplits}', String(cascadeSplits.map((s) => s.toFixed(1)).join(' | '))) }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showDebug, onChange: (e) => setShowDebug(e.target.checked) }), t('cascadedShadows.showCascadeFrustums', 'Show cascade frustums')] })] }));
395
+ ),
396
+ t("cascadedShadows.showCascadeFrustums", "Show cascade frustums")
397
+ ] })
398
+ ]
399
+ }
400
+ );
278
401
  };
279
-
280
402
  exports.NiceCascadedShadows = NiceCascadedShadows;
281
403
  exports.useCSM = useCSM;