@nice2dev/ui-3d 1.3.15 → 1.3.17

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,242 +1,348 @@
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
- /** Built-in guided tour (EN inline + i18n keys → PL and other locales). */
24
+ const THREE__namespace = /* @__PURE__ */ _interopNamespaceDefault(THREE);
29
25
  const MORPH_TARGET_EDITOR_TOUR = [
30
- {
31
- target: '.nice-morph-target-editor',
32
- titleKey: 'tutorial.morphTargetEditor.intro.title',
33
- title: 'Morph target editor',
34
- contentKey: 'tutorial.morphTargetEditor.intro.content',
35
- content: 'Blend the mesh’s morph targets with the per-target influence sliders, create or delete targets, and preview the interpolation.',
36
- placement: 'auto',
37
- },
26
+ {
27
+ target: ".nice-morph-target-editor",
28
+ titleKey: "tutorial.morphTargetEditor.intro.title",
29
+ title: "Morph target editor",
30
+ contentKey: "tutorial.morphTargetEditor.intro.content",
31
+ content: "Blend the mesh’s morph targets with the per-target influence sliders, create or delete targets, and preview the interpolation.",
32
+ placement: "auto"
33
+ }
38
34
  ];
39
- /* ── Hook ── */
40
35
  function useMorphTargetEditor(mesh, onInfluenceChange) {
41
- const [targets, setTargets] = React.useState([]);
42
- const [animations, setAnimations] = React.useState([]);
43
- const [isAnimating, setIsAnimating] = React.useState(false);
44
- const animFrameRef = React.useRef(0);
45
- const animStartRef = React.useRef(0);
46
- const activeAnimRef = React.useRef(null);
47
- // Sync targets from mesh
48
- React.useEffect(() => {
49
- if (!mesh || !mesh.morphTargetDictionary || !mesh.morphTargetInfluences) {
50
- setTargets([]);
51
- return;
52
- }
53
- const dict = mesh.morphTargetDictionary;
54
- const influences = mesh.morphTargetInfluences;
55
- const list = Object.entries(dict).map(([name, index]) => {
56
- var _a;
57
- return ({
58
- name,
59
- index,
60
- influence: (_a = influences[index]) !== null && _a !== void 0 ? _a : 0,
61
- });
62
- });
63
- list.sort((a, b) => a.index - b.index);
64
- setTargets(list);
65
- }, [mesh]);
66
- const setInfluence = React.useCallback((index, value) => {
67
- if (!(mesh === null || mesh === void 0 ? void 0 : mesh.morphTargetInfluences)) {
68
- return;
69
- }
70
- mesh.morphTargetInfluences[index] = value;
71
- setTargets((prev) => prev.map((t) => (t.index === index ? { ...t, influence: value } : t)));
72
- onInfluenceChange === null || onInfluenceChange === void 0 ? void 0 : onInfluenceChange(mesh, index, value);
73
- }, [mesh, onInfluenceChange]);
74
- const createMorphTarget = React.useCallback((name) => {
75
- if (!mesh) {
76
- return;
77
- }
78
- const geometry = mesh.geometry;
79
- const posAttr = geometry.attributes.position;
80
- const count = posAttr.count;
81
- // Clone current position as new morph target
82
- const morphPositions = new Float32Array(count * 3);
83
- for (let i = 0; i < count * 3; i++) {
84
- morphPositions[i] = posAttr.array[i];
85
- }
86
- const morphAttr = new THREE__namespace.BufferAttribute(morphPositions, 3);
87
- morphAttr.name = name;
88
- // Add to morphAttributes
89
- if (!geometry.morphAttributes.position) {
90
- geometry.morphAttributes.position = [];
91
- }
92
- const morphArray = geometry.morphAttributes.position;
93
- morphArray.push(morphAttr);
94
- // Update dictionary and influences
95
- if (!mesh.morphTargetDictionary) {
96
- mesh.morphTargetDictionary = {};
97
- }
98
- if (!mesh.morphTargetInfluences) {
99
- mesh.morphTargetInfluences = [];
100
- }
101
- const newIndex = morphArray.length - 1;
102
- mesh.morphTargetDictionary[name] = newIndex;
103
- mesh.morphTargetInfluences[newIndex] = 0;
104
- mesh.updateMorphTargets();
105
- setTargets((prev) => [...prev, { name, index: newIndex, influence: 0 }]);
106
- }, [mesh]);
107
- const deleteMorphTarget = React.useCallback((index) => {
108
- if (!mesh) {
109
- return;
110
- }
111
- const geometry = mesh.geometry;
112
- const morphArray = geometry.morphAttributes.position;
113
- if (!morphArray || index >= morphArray.length) {
114
- return;
36
+ const [targets, setTargets] = React.useState([]);
37
+ const [animations, setAnimations] = React.useState([]);
38
+ const [isAnimating, setIsAnimating] = React.useState(false);
39
+ const animFrameRef = React.useRef(0);
40
+ const animStartRef = React.useRef(0);
41
+ const activeAnimRef = React.useRef(null);
42
+ React.useEffect(() => {
43
+ if (!mesh || !mesh.morphTargetDictionary || !mesh.morphTargetInfluences) {
44
+ setTargets([]);
45
+ return;
46
+ }
47
+ const dict = mesh.morphTargetDictionary;
48
+ const influences = mesh.morphTargetInfluences;
49
+ const list = Object.entries(dict).map(([name, index]) => ({
50
+ name,
51
+ index,
52
+ influence: influences[index] ?? 0
53
+ }));
54
+ list.sort((a, b) => a.index - b.index);
55
+ setTargets(list);
56
+ }, [mesh]);
57
+ const setInfluence = React.useCallback(
58
+ (index, value) => {
59
+ if (!mesh?.morphTargetInfluences) {
60
+ return;
61
+ }
62
+ mesh.morphTargetInfluences[index] = value;
63
+ setTargets((prev) => prev.map((t) => t.index === index ? { ...t, influence: value } : t));
64
+ onInfluenceChange?.(mesh, index, value);
65
+ },
66
+ [mesh, onInfluenceChange]
67
+ );
68
+ const createMorphTarget = React.useCallback(
69
+ (name) => {
70
+ if (!mesh) {
71
+ return;
72
+ }
73
+ const geometry = mesh.geometry;
74
+ const posAttr = geometry.attributes.position;
75
+ const count = posAttr.count;
76
+ const morphPositions = new Float32Array(count * 3);
77
+ for (let i = 0; i < count * 3; i++) {
78
+ morphPositions[i] = posAttr.array[i];
79
+ }
80
+ const morphAttr = new THREE__namespace.BufferAttribute(morphPositions, 3);
81
+ morphAttr.name = name;
82
+ if (!geometry.morphAttributes.position) {
83
+ geometry.morphAttributes.position = [];
84
+ }
85
+ const morphArray = geometry.morphAttributes.position;
86
+ morphArray.push(morphAttr);
87
+ if (!mesh.morphTargetDictionary) {
88
+ mesh.morphTargetDictionary = {};
89
+ }
90
+ if (!mesh.morphTargetInfluences) {
91
+ mesh.morphTargetInfluences = [];
92
+ }
93
+ const newIndex = morphArray.length - 1;
94
+ mesh.morphTargetDictionary[name] = newIndex;
95
+ mesh.morphTargetInfluences[newIndex] = 0;
96
+ mesh.updateMorphTargets();
97
+ setTargets((prev) => [...prev, { name, index: newIndex, influence: 0 }]);
98
+ },
99
+ [mesh]
100
+ );
101
+ const deleteMorphTarget = React.useCallback(
102
+ (index) => {
103
+ if (!mesh) {
104
+ return;
105
+ }
106
+ const geometry = mesh.geometry;
107
+ const morphArray = geometry.morphAttributes.position;
108
+ if (!morphArray || index >= morphArray.length) {
109
+ return;
110
+ }
111
+ morphArray.splice(index, 1);
112
+ const dict = {};
113
+ const influences = [];
114
+ morphArray.forEach((attr, i) => {
115
+ dict[attr.name || `morph_${i}`] = i;
116
+ influences[i] = mesh.morphTargetInfluences?.[i === index ? -1 : i >= index ? i + 1 : i] ?? 0;
117
+ });
118
+ mesh.morphTargetDictionary = dict;
119
+ mesh.morphTargetInfluences = influences;
120
+ setTargets(
121
+ Object.entries(dict).map(([name, idx]) => ({
122
+ name,
123
+ index: idx,
124
+ influence: influences[idx]
125
+ }))
126
+ );
127
+ },
128
+ [mesh]
129
+ );
130
+ const resetAllInfluences = React.useCallback(() => {
131
+ if (!mesh?.morphTargetInfluences) {
132
+ return;
133
+ }
134
+ mesh.morphTargetInfluences.fill(0);
135
+ setTargets((prev) => prev.map((t) => ({ ...t, influence: 0 })));
136
+ }, [mesh]);
137
+ const startAnimation = React.useCallback(
138
+ (animation) => {
139
+ if (!mesh?.morphTargetInfluences) {
140
+ return;
141
+ }
142
+ activeAnimRef.current = animation;
143
+ setIsAnimating(true);
144
+ animStartRef.current = performance.now();
145
+ const animate = (now) => {
146
+ const anim = activeAnimRef.current;
147
+ if (!anim || !mesh.morphTargetInfluences) {
148
+ return;
115
149
  }
116
- morphArray.splice(index, 1);
117
- // Rebuild dictionary
118
- const dict = {};
119
- const influences = [];
120
- morphArray.forEach((attr, i) => {
121
- var _a, _b;
122
- dict[attr.name || `morph_${i}`] = i;
123
- influences[i] =
124
- (_b = (_a = mesh.morphTargetInfluences) === null || _a === void 0 ? void 0 : _a[i === index ? -1 : i >= index ? i + 1 : i]) !== null && _b !== void 0 ? _b : 0;
125
- });
126
- mesh.morphTargetDictionary = dict;
127
- mesh.morphTargetInfluences = influences;
128
- setTargets(Object.entries(dict).map(([name, idx]) => ({
129
- name,
130
- index: idx,
131
- influence: influences[idx],
132
- })));
133
- }, [mesh]);
134
- const resetAllInfluences = React.useCallback(() => {
135
- if (!(mesh === null || mesh === void 0 ? void 0 : mesh.morphTargetInfluences)) {
136
- return;
150
+ const elapsed = (now - animStartRef.current) / 1e3;
151
+ const t = elapsed % anim.duration / anim.duration;
152
+ const kfs = anim.keyframes;
153
+ let prevKf = kfs[0];
154
+ let nextKf = kfs[kfs.length - 1];
155
+ for (let i = 0; i < kfs.length - 1; i++) {
156
+ if (t >= kfs[i].time / anim.duration && t < kfs[i + 1].time / anim.duration) {
157
+ prevKf = kfs[i];
158
+ nextKf = kfs[i + 1];
159
+ break;
160
+ }
137
161
  }
138
- mesh.morphTargetInfluences.fill(0);
139
- setTargets((prev) => prev.map((t) => ({ ...t, influence: 0 })));
140
- }, [mesh]);
141
- const startAnimation = React.useCallback((animation) => {
142
- if (!(mesh === null || mesh === void 0 ? void 0 : mesh.morphTargetInfluences)) {
143
- return;
162
+ const segDuration = (nextKf.time - prevKf.time) / anim.duration;
163
+ const segT = segDuration > 0 ? (t - prevKf.time / anim.duration) / segDuration : 0;
164
+ for (let i = 0; i < mesh.morphTargetInfluences.length; i++) {
165
+ const from = prevKf.influences[i] ?? 0;
166
+ const to = nextKf.influences[i] ?? 0;
167
+ mesh.morphTargetInfluences[i] = from + (to - from) * segT;
144
168
  }
145
- activeAnimRef.current = animation;
146
- setIsAnimating(true);
147
- animStartRef.current = performance.now();
148
- const animate = (now) => {
149
- var _a, _b;
150
- const anim = activeAnimRef.current;
151
- if (!anim || !mesh.morphTargetInfluences) {
152
- return;
153
- }
154
- const elapsed = (now - animStartRef.current) / 1000;
155
- const t = (elapsed % anim.duration) / anim.duration;
156
- // Interpolate between keyframes
157
- const kfs = anim.keyframes;
158
- let prevKf = kfs[0];
159
- let nextKf = kfs[kfs.length - 1];
160
- for (let i = 0; i < kfs.length - 1; i++) {
161
- if (t >= kfs[i].time / anim.duration && t < kfs[i + 1].time / anim.duration) {
162
- prevKf = kfs[i];
163
- nextKf = kfs[i + 1];
164
- break;
165
- }
166
- }
167
- const segDuration = (nextKf.time - prevKf.time) / anim.duration;
168
- const segT = segDuration > 0 ? (t - prevKf.time / anim.duration) / segDuration : 0;
169
- for (let i = 0; i < mesh.morphTargetInfluences.length; i++) {
170
- const from = (_a = prevKf.influences[i]) !== null && _a !== void 0 ? _a : 0;
171
- const to = (_b = nextKf.influences[i]) !== null && _b !== void 0 ? _b : 0;
172
- mesh.morphTargetInfluences[i] = from + (to - from) * segT;
173
- }
174
- setTargets((prev) => prev.map((tgt) => {
175
- var _a;
176
- return ({
177
- ...tgt,
178
- influence: (_a = mesh.morphTargetInfluences[tgt.index]) !== null && _a !== void 0 ? _a : 0,
179
- });
180
- }));
181
- animFrameRef.current = requestAnimationFrame(animate);
182
- };
169
+ setTargets(
170
+ (prev) => prev.map((tgt) => ({
171
+ ...tgt,
172
+ influence: mesh.morphTargetInfluences[tgt.index] ?? 0
173
+ }))
174
+ );
183
175
  animFrameRef.current = requestAnimationFrame(animate);
184
- }, [mesh]);
185
- const stopAnimation = React.useCallback(() => {
186
- activeAnimRef.current = null;
187
- setIsAnimating(false);
188
- cancelAnimationFrame(animFrameRef.current);
189
- }, []);
190
- React.useEffect(() => {
191
- return () => cancelAnimationFrame(animFrameRef.current);
192
- }, []);
193
- const addAnimation = React.useCallback((animation) => {
194
- setAnimations((prev) => [...prev, animation]);
195
- }, []);
196
- const removeAnimation = React.useCallback((name) => {
197
- setAnimations((prev) => prev.filter((a) => a.name !== name));
198
- }, []);
199
- return {
200
- targets,
201
- setInfluence,
202
- createMorphTarget,
203
- deleteMorphTarget,
204
- resetAllInfluences,
205
- isAnimating,
206
- startAnimation,
207
- stopAnimation,
208
- animations,
209
- addAnimation,
210
- removeAnimation,
211
- };
176
+ };
177
+ animFrameRef.current = requestAnimationFrame(animate);
178
+ },
179
+ [mesh]
180
+ );
181
+ const stopAnimation = React.useCallback(() => {
182
+ activeAnimRef.current = null;
183
+ setIsAnimating(false);
184
+ cancelAnimationFrame(animFrameRef.current);
185
+ }, []);
186
+ React.useEffect(() => {
187
+ return () => cancelAnimationFrame(animFrameRef.current);
188
+ }, []);
189
+ const addAnimation = React.useCallback((animation) => {
190
+ setAnimations((prev) => [...prev, animation]);
191
+ }, []);
192
+ const removeAnimation = React.useCallback((name) => {
193
+ setAnimations((prev) => prev.filter((a) => a.name !== name));
194
+ }, []);
195
+ return {
196
+ targets,
197
+ setInfluence,
198
+ createMorphTarget,
199
+ deleteMorphTarget,
200
+ resetAllInfluences,
201
+ isAnimating,
202
+ startAnimation,
203
+ stopAnimation,
204
+ animations,
205
+ addAnimation,
206
+ removeAnimation
207
+ };
212
208
  }
213
- /* ── Component ── */
214
- const NiceMorphTargetEditor = ({ mesh, onInfluenceChange, className = '', tutorial, }) => {
215
- const { t } = i18n.useNiceTranslation();
216
- const { targets, setInfluence, createMorphTarget, deleteMorphTarget, resetAllInfluences, isAnimating, startAnimation, stopAnimation, animations, addAnimation: _addAnimation, removeAnimation, } = useMorphTargetEditor(mesh, onInfluenceChange);
217
- const [newName, setNewName] = React.useState('');
218
- const handleCreate = () => {
219
- const name = newName.trim();
220
- if (!name) {
221
- return;
222
- }
223
- createMorphTarget(name);
224
- setNewName('');
225
- };
226
- return (jsxRuntime.jsxs("div", { className: `nice-morph-target-editor ${className}`, style: { position: 'relative', display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, MORPH_TARGET_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsx("strong", { children: t('morphTargetEditor.morphTargets', 'Morph Targets') }), jsxRuntime.jsx("button", { type: "button", onClick: resetAllInfluences, style: { fontSize: 11 }, children: t('morphTargetEditor.resetAll', 'Reset All') })] }), targets.length === 0 && (jsxRuntime.jsx("div", { style: { opacity: 0.6, fontStyle: 'italic' }, children: t('morphTargetEditor.noMorphTargetsFound', 'No morph targets found.') })), targets.map((target) => (jsxRuntime.jsxs("div", { style: {
227
- display: 'grid',
228
- gridTemplateColumns: '1fr 50px auto',
229
- alignItems: 'center',
230
- gap: 6,
231
- }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { style: { display: 'block', fontSize: 11, marginBottom: 2 }, children: target.name }), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: target.influence, onChange: (e) => setInfluence(target.index, parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsx("span", { style: { fontSize: 11, textAlign: 'right' }, children: target.influence.toFixed(2) }), jsxRuntime.jsx("button", { type: "button", onClick: () => deleteMorphTarget(target.index), title: t('morphTargetEditor.delete', 'Delete'), "aria-label": t('morphTargetEditor.delete', 'Delete'), style: { background: 'none', border: 'none', cursor: 'pointer' }, children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] }, target.index))), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4, marginTop: 4 }, children: [jsxRuntime.jsx("input", { type: "text", value: newName, onChange: (e) => setNewName(e.target.value), placeholder: t('morphTargetEditor.newMorphTargetName', 'New morph target name…'), onKeyDown: (e) => e.key === 'Enter' && handleCreate(), style: {
232
- flex: 1,
233
- padding: '4px 8px',
234
- border: '1px solid var(--nice-border, #e2e8f0)',
235
- borderRadius: 4,
236
- } }), jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children: t('morphTargetEditor.add', '+ Add') })] }), animations.length > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: 8 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('morphTargetEditor.animations', 'Animations') }), animations.map((anim) => (jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 4 }, children: [jsxRuntime.jsx("span", { style: { flex: 1 }, children: anim.name }), jsxRuntime.jsx("button", { type: "button", onClick: () => (isAnimating ? stopAnimation() : startAnimation(anim)), style: { fontSize: 11 }, "aria-label": isAnimating
237
- ? t('morphTargetEditor.stop', 'Stop')
238
- : t('morphTargetEditor.play', 'Play'), children: jsxRuntime.jsx(ui.NiceIcon, { name: isAnimating ? 'stop' : 'play', size: 14 }) }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeAnimation(anim.name), style: { fontSize: 11 }, "aria-label": t('morphTargetEditor.removeAnimation', 'Remove animation'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] }, anim.name)))] }))] }));
209
+ const NiceMorphTargetEditor = ({
210
+ mesh,
211
+ onInfluenceChange,
212
+ className = "",
213
+ tutorial
214
+ }) => {
215
+ const { t } = i18n.useNiceTranslation();
216
+ const {
217
+ targets,
218
+ setInfluence,
219
+ createMorphTarget,
220
+ deleteMorphTarget,
221
+ resetAllInfluences,
222
+ isAnimating,
223
+ startAnimation,
224
+ stopAnimation,
225
+ animations,
226
+ addAnimation: _addAnimation,
227
+ removeAnimation
228
+ } = useMorphTargetEditor(mesh, onInfluenceChange);
229
+ const [newName, setNewName] = React.useState("");
230
+ const handleCreate = () => {
231
+ const name = newName.trim();
232
+ if (!name) {
233
+ return;
234
+ }
235
+ createMorphTarget(name);
236
+ setNewName("");
237
+ };
238
+ return /* @__PURE__ */ jsxRuntime.jsxs(
239
+ "div",
240
+ {
241
+ className: `nice-morph-target-editor ${className}`,
242
+ style: { position: "relative", display: "grid", gap: 8, fontSize: 13 },
243
+ children: [
244
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { position: "absolute", top: 0, right: 0, zIndex: 20 }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, MORPH_TARGET_EDITOR_TOUR) }) }),
245
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
246
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: t("morphTargetEditor.morphTargets", "Morph Targets") }),
247
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: resetAllInfluences, style: { fontSize: 11 }, children: t("morphTargetEditor.resetAll", "Reset All") })
248
+ ] }),
249
+ targets.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.6, fontStyle: "italic" }, children: t("morphTargetEditor.noMorphTargetsFound", "No morph targets found.") }),
250
+ targets.map((target) => /* @__PURE__ */ jsxRuntime.jsxs(
251
+ "div",
252
+ {
253
+ style: {
254
+ display: "grid",
255
+ gridTemplateColumns: "1fr 50px auto",
256
+ alignItems: "center",
257
+ gap: 6
258
+ },
259
+ children: [
260
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
261
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { display: "block", fontSize: 11, marginBottom: 2 }, children: target.name }),
262
+ /* @__PURE__ */ jsxRuntime.jsx(
263
+ "input",
264
+ {
265
+ type: "range",
266
+ min: 0,
267
+ max: 1,
268
+ step: 0.01,
269
+ value: target.influence,
270
+ onChange: (e) => setInfluence(target.index, parseFloat(e.target.value)),
271
+ style: { width: "100%" }
272
+ }
273
+ )
274
+ ] }),
275
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 11, textAlign: "right" }, children: target.influence.toFixed(2) }),
276
+ /* @__PURE__ */ jsxRuntime.jsx(
277
+ "button",
278
+ {
279
+ type: "button",
280
+ onClick: () => deleteMorphTarget(target.index),
281
+ title: t("morphTargetEditor.delete", "Delete"),
282
+ "aria-label": t("morphTargetEditor.delete", "Delete"),
283
+ style: { background: "none", border: "none", cursor: "pointer" },
284
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 })
285
+ }
286
+ )
287
+ ]
288
+ },
289
+ target.index
290
+ )),
291
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 4, marginTop: 4 }, children: [
292
+ /* @__PURE__ */ jsxRuntime.jsx(
293
+ "input",
294
+ {
295
+ type: "text",
296
+ value: newName,
297
+ onChange: (e) => setNewName(e.target.value),
298
+ placeholder: t("morphTargetEditor.newMorphTargetName", "New morph target name…"),
299
+ onKeyDown: (e) => e.key === "Enter" && handleCreate(),
300
+ style: {
301
+ flex: 1,
302
+ padding: "4px 8px",
303
+ border: "1px solid var(--nice-border, #e2e8f0)",
304
+ borderRadius: 4
305
+ }
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children: t("morphTargetEditor.add", "+ Add") })
309
+ ] }),
310
+ animations.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8 }, children: [
311
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t("morphTargetEditor.animations", "Animations") }),
312
+ animations.map((anim) => /* @__PURE__ */ jsxRuntime.jsxs(
313
+ "div",
314
+ {
315
+ style: { display: "flex", alignItems: "center", gap: 4, marginTop: 4 },
316
+ children: [
317
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 }, children: anim.name }),
318
+ /* @__PURE__ */ jsxRuntime.jsx(
319
+ "button",
320
+ {
321
+ type: "button",
322
+ onClick: () => isAnimating ? stopAnimation() : startAnimation(anim),
323
+ style: { fontSize: 11 },
324
+ "aria-label": isAnimating ? t("morphTargetEditor.stop", "Stop") : t("morphTargetEditor.play", "Play"),
325
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: isAnimating ? "stop" : "play", size: 14 })
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsxRuntime.jsx(
329
+ "button",
330
+ {
331
+ type: "button",
332
+ onClick: () => removeAnimation(anim.name),
333
+ style: { fontSize: 11 },
334
+ "aria-label": t("morphTargetEditor.removeAnimation", "Remove animation"),
335
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 })
336
+ }
337
+ )
338
+ ]
339
+ },
340
+ anim.name
341
+ ))
342
+ ] })
343
+ ]
344
+ }
345
+ );
239
346
  };
240
-
241
347
  exports.NiceMorphTargetEditor = NiceMorphTargetEditor;
242
348
  exports.useMorphTargetEditor = useMorphTargetEditor;