@nice2dev/ui-3d 1.2.0 → 1.2.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/CHANGELOG.md +142 -0
- package/dist/cjs/core/editorAsk.js +48 -0
- package/dist/cjs/core/i18n.js +7 -3
- package/dist/cjs/dance/DanceScoreEngine.js +7 -0
- package/dist/cjs/dance/PoseDetector.js +4 -2
- package/dist/cjs/material/NiceMaterialEditor.js +8 -3
- package/dist/cjs/material/materialEditorTypes.js +1 -1
- package/dist/cjs/material/materialNodeDefinitions.js +14 -11
- package/dist/cjs/model/ModelEditor.js +9 -2
- package/dist/cjs/model/ModelEditorLeftPanel.js +10 -4
- package/dist/cjs/model/ModelEditorMenuBar.js +10 -8
- package/dist/cjs/model/ModelEditorRightPanel.js +28 -22
- package/dist/cjs/model/ModelEditorSubComponents.js +17 -9
- package/dist/cjs/model/ModelEditorTimeline.js +8 -4
- package/dist/cjs/model/ModelEditorToolbar.js +6 -4
- package/dist/cjs/model/ModelEditorViewport.js +19 -6
- package/dist/cjs/model/ModelViewer.js +9 -7
- package/dist/cjs/model/NiceArmatureEditor.js +9 -7
- package/dist/cjs/model/NiceMorphTargetEditor.js +7 -3
- package/dist/cjs/model/NiceOctree.js +9 -4
- package/dist/cjs/model/NicePhysicsSimulation.js +7 -5
- package/dist/cjs/model/NiceProceduralGeometry.js +6 -4
- package/dist/cjs/model/NiceTerrainEditor.js +9 -7
- package/dist/cjs/model/NiceWeightPainter.js +6 -4
- package/dist/cjs/model/NiceXRPreview.js +9 -4
- package/dist/cjs/model/useModelEditor.js +113 -87
- package/dist/cjs/model/useModelViewer.js +13 -6
- package/dist/cjs/nice2dev-ui-3d.css +1 -1
- package/dist/cjs/particle/NiceParticleEditor.js +23 -11
- package/dist/cjs/rendering/NiceCascadedShadows.js +3 -1
- package/dist/cjs/rendering/NiceRenderExport.js +9 -3
- package/dist/cjs/rendering/NiceSSAO.js +4 -1
- package/dist/cjs/rendering/NiceSSR.js +4 -1
- package/dist/cjs/rendering/NiceWebGPURenderer.js +5 -2
- package/dist/cjs/uv/NiceUVEditor.js +16 -12
- package/dist/cjs/uv/uvEditorTypes.js +1 -0
- package/dist/esm/core/editorAsk.js +45 -0
- package/dist/esm/core/i18n.js +8 -4
- package/dist/esm/dance/DanceScoreEngine.js +7 -0
- package/dist/esm/dance/PoseDetector.js +4 -2
- package/dist/esm/material/NiceMaterialEditor.js +8 -3
- package/dist/esm/material/materialEditorTypes.js +1 -1
- package/dist/esm/material/materialNodeDefinitions.js +14 -11
- package/dist/esm/model/ModelEditor.js +9 -2
- package/dist/esm/model/ModelEditorLeftPanel.js +10 -4
- package/dist/esm/model/ModelEditorMenuBar.js +10 -8
- package/dist/esm/model/ModelEditorRightPanel.js +28 -22
- package/dist/esm/model/ModelEditorSubComponents.js +17 -9
- package/dist/esm/model/ModelEditorTimeline.js +8 -4
- package/dist/esm/model/ModelEditorToolbar.js +6 -4
- package/dist/esm/model/ModelEditorViewport.js +19 -6
- package/dist/esm/model/ModelViewer.js +9 -7
- package/dist/esm/model/NiceArmatureEditor.js +9 -7
- package/dist/esm/model/NiceMorphTargetEditor.js +7 -3
- package/dist/esm/model/NiceOctree.js +9 -4
- package/dist/esm/model/NicePhysicsSimulation.js +7 -5
- package/dist/esm/model/NiceProceduralGeometry.js +6 -4
- package/dist/esm/model/NiceTerrainEditor.js +9 -7
- package/dist/esm/model/NiceWeightPainter.js +6 -4
- package/dist/esm/model/NiceXRPreview.js +9 -4
- package/dist/esm/model/useModelEditor.js +113 -87
- package/dist/esm/model/useModelViewer.js +13 -6
- package/dist/esm/nice2dev-ui-3d.css +1 -1
- package/dist/esm/particle/NiceParticleEditor.js +23 -11
- package/dist/esm/rendering/NiceCascadedShadows.js +3 -1
- package/dist/esm/rendering/NiceRenderExport.js +9 -3
- package/dist/esm/rendering/NiceSSAO.js +4 -1
- package/dist/esm/rendering/NiceSSR.js +4 -1
- package/dist/esm/rendering/NiceWebGPURenderer.js +5 -2
- package/dist/esm/uv/NiceUVEditor.js +16 -12
- package/dist/esm/uv/uvEditorTypes.js +1 -0
- package/dist/types/core/editorAsk.d.ts +26 -0
- package/dist/types/core/i18n.d.ts +1 -17
- package/dist/types/dance/DanceScoreEngine.d.ts +0 -6
- package/dist/types/model/useModelEditor.d.ts +3 -0
- package/package.json +3 -3
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
|
|
8
9
|
function _interopNamespaceDefault(e) {
|
|
9
10
|
var n = Object.create(null);
|
|
@@ -232,6 +233,7 @@ function useArmatureEditor(skeleton, scene, onBoneSelect) {
|
|
|
232
233
|
}
|
|
233
234
|
/* ── Component ── */
|
|
234
235
|
const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera: _camera, onBoneSelect, onPoseChange: _onPoseChange, className = '', tutorial, }) => {
|
|
236
|
+
const { t } = i18n.useNiceTranslation();
|
|
235
237
|
const { bones, selectedBoneId, selectBone, setBonePosition: _setBonePosition, setBoneRotation, addBone, removeBone, savePose, loadPose, resetPose, savedPoses, skeletonHelper: _skeletonHelper, } = useArmatureEditor(skeleton, scene, onBoneSelect);
|
|
236
238
|
const [newBoneName, setNewBoneName] = React.useState('');
|
|
237
239
|
const selectedBone = React.useMemo(() => bones.find((b) => b.id === selectedBoneId), [bones, selectedBoneId]);
|
|
@@ -249,13 +251,13 @@ const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera
|
|
|
249
251
|
borderRadius: 4,
|
|
250
252
|
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "target", size: 14 }), " ", boneInfo.name] }), childBones.map((child) => renderBoneTree(child, depth + 1))] }, boneInfo.id));
|
|
251
253
|
};
|
|
252
|
-
return (jsxRuntime.jsxs("div", { className: `nice-armature-editor ${className}`, style: { position: 'relative', display: 'flex', flexDirection: 'column', gap: 8 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, ARMATURE_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: resetPose, title:
|
|
254
|
+
return (jsxRuntime.jsxs("div", { className: `nice-armature-editor ${className}`, style: { position: 'relative', display: 'flex', flexDirection: 'column', gap: 8 }, children: [jsxRuntime.jsx("span", { style: { position: 'absolute', top: 0, right: 0, zIndex: 20 }, children: jsxRuntime.jsx(ui.NiceTutorialButton, { steps: ui.resolveTutorial(tutorial, ARMATURE_EDITOR_TOUR) }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: resetPose, title: t('armatureEditor.resetPose', 'Reset Pose'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " ", t('armatureEditor.reset', 'Reset')] }), jsxRuntime.jsxs("button", { type: "button", onClick: () => savePose(`Pose ${savedPoses.length + 1}`), title: t('armatureEditor.savePose', 'Save Pose'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "save", size: 14 }), " ", t('armatureEditor.savePose', 'Save Pose')] })] }), jsxRuntime.jsx("div", { style: {
|
|
253
255
|
maxHeight: 200,
|
|
254
256
|
overflowY: 'auto',
|
|
255
257
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
256
258
|
borderRadius: 6,
|
|
257
259
|
padding: 4,
|
|
258
|
-
}, children: rootBones.length === 0 ? (jsxRuntime.jsx("div", { style: { padding: 16, textAlign: 'center', color: 'var(--nice-text-muted, #999)' }, children:
|
|
260
|
+
}, children: rootBones.length === 0 ? (jsxRuntime.jsx("div", { style: { padding: 16, textAlign: 'center', color: 'var(--nice-text-muted, #999)' }, children: t('armatureEditor.noSkeletonFound', 'No skeleton found') })) : (rootBones.map((b) => renderBoneTree(b))) }), selectedBoneId && (jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("input", { type: "text", value: newBoneName, onChange: (e) => setNewBoneName(e.target.value), placeholder: t('armatureEditor.newBoneName', 'New bone name'), style: {
|
|
259
261
|
flex: 1,
|
|
260
262
|
padding: '4px 8px',
|
|
261
263
|
borderRadius: 4,
|
|
@@ -263,26 +265,26 @@ const NiceArmatureEditor = ({ skeleton, skinnedMesh: _skinnedMesh, scene, camera
|
|
|
263
265
|
} }), jsxRuntime.jsx("button", { type: "button", disabled: !newBoneName.trim(), onClick: () => {
|
|
264
266
|
addBone(selectedBoneId, newBoneName.trim());
|
|
265
267
|
setNewBoneName('');
|
|
266
|
-
}, children:
|
|
268
|
+
}, children: t('armatureEditor.add', '+ Add') }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBone(selectedBoneId), "aria-label": t('armatureEditor.removeBone', 'Remove bone'), children: jsxRuntime.jsx(ui.NiceIcon, { name: "trash", size: 14 }) })] })), selectedBone && (jsxRuntime.jsxs("div", { style: {
|
|
267
269
|
display: 'grid',
|
|
268
270
|
gap: 4,
|
|
269
271
|
fontSize: 12,
|
|
270
272
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
271
273
|
borderRadius: 6,
|
|
272
274
|
padding: 8,
|
|
273
|
-
}, children: [jsxRuntime.jsx("strong", { children: selectedBone.name }), jsxRuntime.
|
|
275
|
+
}, children: [jsxRuntime.jsx("strong", { children: selectedBone.name }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationX', 'Rotation X: {x}°').replace('{x}', String((selectedBone.localRotation.x * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.x * (180 / Math.PI), onChange: (e) => {
|
|
274
276
|
const rot = selectedBone.localRotation.clone();
|
|
275
277
|
rot.x = parseFloat(e.target.value) * (Math.PI / 180);
|
|
276
278
|
setBoneRotation(selectedBone.id, rot);
|
|
277
|
-
} }), jsxRuntime.
|
|
279
|
+
} }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationY', 'Rotation Y: {y}°').replace('{y}', String((selectedBone.localRotation.y * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.y * (180 / Math.PI), onChange: (e) => {
|
|
278
280
|
const rot = selectedBone.localRotation.clone();
|
|
279
281
|
rot.y = parseFloat(e.target.value) * (Math.PI / 180);
|
|
280
282
|
setBoneRotation(selectedBone.id, rot);
|
|
281
|
-
} }), jsxRuntime.
|
|
283
|
+
} }), jsxRuntime.jsx("label", { children: t('armatureEditor.rotationZ', 'Rotation Z: {z}°').replace('{z}', String((selectedBone.localRotation.z * (180 / Math.PI)).toFixed(1))) }), jsxRuntime.jsx("input", { type: "range", min: -180, max: 180, value: selectedBone.localRotation.z * (180 / Math.PI), onChange: (e) => {
|
|
282
284
|
const rot = selectedBone.localRotation.clone();
|
|
283
285
|
rot.z = parseFloat(e.target.value) * (Math.PI / 180);
|
|
284
286
|
setBoneRotation(selectedBone.id, rot);
|
|
285
|
-
} })] })), savedPoses.length > 0 && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children:
|
|
287
|
+
} })] })), savedPoses.length > 0 && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('armatureEditor.savedPoses', 'Saved Poses') }), savedPoses.map((pose) => (jsxRuntime.jsx("button", { type: "button", onClick: () => loadPose(pose), style: { textAlign: 'left', padding: '4px 8px', fontSize: 12 }, children: pose.name }, pose.id)))] }))] }));
|
|
286
288
|
};
|
|
287
289
|
|
|
288
290
|
exports.NiceArmatureEditor = NiceArmatureEditor;
|
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
|
|
8
9
|
function _interopNamespaceDefault(e) {
|
|
9
10
|
var n = Object.create(null);
|
|
@@ -211,6 +212,7 @@ function useMorphTargetEditor(mesh, onInfluenceChange) {
|
|
|
211
212
|
}
|
|
212
213
|
/* ── Component ── */
|
|
213
214
|
const NiceMorphTargetEditor = ({ mesh, onInfluenceChange, className = '', tutorial, }) => {
|
|
215
|
+
const { t } = i18n.useNiceTranslation();
|
|
214
216
|
const { targets, setInfluence, createMorphTarget, deleteMorphTarget, resetAllInfluences, isAnimating, startAnimation, stopAnimation, animations, addAnimation: _addAnimation, removeAnimation, } = useMorphTargetEditor(mesh, onInfluenceChange);
|
|
215
217
|
const [newName, setNewName] = React.useState('');
|
|
216
218
|
const handleCreate = () => {
|
|
@@ -221,17 +223,19 @@ const NiceMorphTargetEditor = ({ mesh, onInfluenceChange, className = '', tutori
|
|
|
221
223
|
createMorphTarget(name);
|
|
222
224
|
setNewName('');
|
|
223
225
|
};
|
|
224
|
-
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:
|
|
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: {
|
|
225
227
|
display: 'grid',
|
|
226
228
|
gridTemplateColumns: '1fr 50px auto',
|
|
227
229
|
alignItems: 'center',
|
|
228
230
|
gap: 6,
|
|
229
|
-
}, 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:
|
|
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: {
|
|
230
232
|
flex: 1,
|
|
231
233
|
padding: '4px 8px',
|
|
232
234
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
233
235
|
borderRadius: 4,
|
|
234
|
-
} }), jsxRuntime.jsx("button", { type: "button", onClick: handleCreate, children:
|
|
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)))] }))] }));
|
|
235
239
|
};
|
|
236
240
|
|
|
237
241
|
exports.NiceMorphTargetEditor = NiceMorphTargetEditor;
|
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
|
|
8
9
|
function _interopNamespaceDefault(e) {
|
|
9
10
|
var n = Object.create(null);
|
|
@@ -345,20 +346,24 @@ function useOctree(scene, configOverride) {
|
|
|
345
346
|
}
|
|
346
347
|
/* ── Component ── */
|
|
347
348
|
const NiceOctree = ({ scene, config, showDebug: initialShowDebug, className = '', }) => {
|
|
349
|
+
const { t } = i18n.useNiceTranslation();
|
|
348
350
|
const octree = useOctree(scene, config);
|
|
351
|
+
const { setShowDebug } = octree;
|
|
349
352
|
React.useEffect(() => {
|
|
350
353
|
if (initialShowDebug !== undefined) {
|
|
351
|
-
|
|
354
|
+
setShowDebug(initialShowDebug);
|
|
352
355
|
}
|
|
353
|
-
}, [initialShowDebug]);
|
|
354
|
-
return (jsxRuntime.jsxs("div", { className: `nice-octree ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "tree", size: 14 }),
|
|
356
|
+
}, [initialShowDebug, setShowDebug]);
|
|
357
|
+
return (jsxRuntime.jsxs("div", { className: `nice-octree ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "tree", size: 14 }), ' ', t('octree.spatialIndexOctree', 'Spatial Index (Octree)')] }), jsxRuntime.jsxs("button", { type: "button", onClick: octree.build, style: {
|
|
355
358
|
background: 'var(--nice-color-accent, #6366f1)',
|
|
356
359
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
357
360
|
border: 'none',
|
|
358
361
|
borderRadius: 4,
|
|
359
362
|
padding: '6px 12px',
|
|
360
363
|
cursor: 'pointer',
|
|
361
|
-
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " Build Octree
|
|
364
|
+
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('octree.buildOctree', 'Build Octree')] }), jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: t('octree.nodesObjects', 'Nodes: {nodeCount} · Objects: {objectCount}')
|
|
365
|
+
.replace('{nodeCount}', String(octree.nodeCount))
|
|
366
|
+
.replace('{objectCount}', String(octree.objectCount)) }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: octree.showDebug, onChange: (e) => octree.setShowDebug(e.target.checked) }), t('octree.showDebugWireframes', 'Show debug wireframes')] })] }));
|
|
362
367
|
};
|
|
363
368
|
|
|
364
369
|
exports.NiceOctree = NiceOctree;
|
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
|
|
8
9
|
function _interopNamespaceDefault(e) {
|
|
9
10
|
var n = Object.create(null);
|
|
@@ -282,11 +283,12 @@ function usePhysicsSimulation(scene, onSimulationStep) {
|
|
|
282
283
|
}
|
|
283
284
|
/* ── Component ── */
|
|
284
285
|
const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, className = '', }) => {
|
|
286
|
+
const { t } = i18n.useNiceTranslation();
|
|
285
287
|
const { worldConfig, setWorldConfig, bodies, addBody, removeBody, updateBody, getBodyForObject, isRunning, play, pause, step, reset, debugDraw, setDebugDraw, } = usePhysicsSimulation(scene, onSimulationStep);
|
|
286
288
|
const selectedBody = selectedObject
|
|
287
289
|
? getBodyForObject(selectedObject.name || selectedObject.uuid)
|
|
288
290
|
: undefined;
|
|
289
|
-
return (jsxRuntime.jsxs("div", { className: `nice-physics-simulation ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " Physics
|
|
291
|
+
return (jsxRuntime.jsxs("div", { className: `nice-physics-simulation ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('physicsSimulation.physics', 'Physics')] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 11 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debugDraw, onChange: (e) => setDebugDraw(e.target.checked) }), t('physicsSimulation.debug', 'Debug')] })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("button", { type: "button", onClick: isRunning ? pause : play, style: {
|
|
290
292
|
flex: 1,
|
|
291
293
|
background: isRunning ? 'var(--nice-danger, #ef4444)' : 'var(--nice-success, #22c55e)',
|
|
292
294
|
color: 'var(--nice-color-white, #fff)',
|
|
@@ -294,20 +296,20 @@ const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, classN
|
|
|
294
296
|
borderRadius: 4,
|
|
295
297
|
padding: '5px 8px',
|
|
296
298
|
cursor: 'pointer',
|
|
297
|
-
}, children: isRunning ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }), " Pause
|
|
299
|
+
}, children: isRunning ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }), " ", t('physicsSimulation.pause', 'Pause')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", t('physicsSimulation.play', 'Play')] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: step, disabled: isRunning, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "skip-forward", size: 14 }), " ", t('physicsSimulation.step', 'Step')] }), jsxRuntime.jsxs("button", { type: "button", onClick: reset, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " ", t('physicsSimulation.reset', 'Reset')] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.gravityY', 'Gravity Y: {y}').replace('{y}', String(worldConfig.gravity.y.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: -20, max: 0, step: 0.1, value: worldConfig.gravity.y, onChange: (e) => setWorldConfig({ gravity: new THREE__namespace.Vector3(0, parseFloat(e.target.value), 0) }), style: { width: '100%' } })] }), selectedObject && (jsxRuntime.jsxs("fieldset", { style: {
|
|
298
300
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
299
301
|
borderRadius: 6,
|
|
300
302
|
padding: 6,
|
|
301
303
|
display: 'grid',
|
|
302
304
|
gap: 4,
|
|
303
|
-
}, children: [jsxRuntime.
|
|
305
|
+
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('physicsSimulation.objectPhysics', '{name} — Physics').replace('{name}', selectedObject.name || t('physicsSimulation.object', 'Object')) }), !selectedBody ? (jsxRuntime.jsx("button", { type: "button", onClick: () => addBody(selectedObject), style: {
|
|
304
306
|
background: 'var(--nice-color-accent, #6366f1)',
|
|
305
307
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
306
308
|
border: 'none',
|
|
307
309
|
borderRadius: 4,
|
|
308
310
|
padding: '5px 8px',
|
|
309
311
|
cursor: 'pointer',
|
|
310
|
-
}, children:
|
|
312
|
+
}, children: t('physicsSimulation.addRigidBody', '+ Add Rigid Body') })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { children: [t('physicsSimulation.type', 'Type:'), jsxRuntime.jsxs("select", { value: selectedBody.bodyType, onChange: (e) => updateBody(selectedBody.id, { bodyType: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "dynamic", children: t('physicsSimulation.dynamic', 'Dynamic') }), jsxRuntime.jsx("option", { value: "static", children: t('physicsSimulation.static', 'Static') }), jsxRuntime.jsx("option", { value: "kinematic", children: t('physicsSimulation.kinematic', 'Kinematic') })] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.shape', 'Shape:'), jsxRuntime.jsxs("select", { value: selectedBody.colliderShape, onChange: (e) => updateBody(selectedBody.id, { colliderShape: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "box", children: t('physicsSimulation.box', 'Box') }), jsxRuntime.jsx("option", { value: "sphere", children: t('physicsSimulation.sphere', 'Sphere') }), jsxRuntime.jsx("option", { value: "capsule", children: t('physicsSimulation.capsule', 'Capsule') }), jsxRuntime.jsx("option", { value: "convexHull", children: t('physicsSimulation.convexHull', 'Convex Hull') }), jsxRuntime.jsx("option", { value: "trimesh", children: t('physicsSimulation.trimesh', 'Trimesh') })] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.mass', 'Mass: {mass}').replace('{mass}', String(selectedBody.mass.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 100, step: 0.1, value: selectedBody.mass, onChange: (e) => updateBody(selectedBody.id, { mass: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.restitution', 'Restitution: {restitution}').replace('{restitution}', String(selectedBody.restitution.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.restitution, onChange: (e) => updateBody(selectedBody.id, { restitution: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.friction', 'Friction: {friction}').replace('{friction}', String(selectedBody.friction.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.friction, onChange: (e) => updateBody(selectedBody.id, { friction: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: selectedBody.isSensor, onChange: (e) => updateBody(selectedBody.id, { isSensor: e.target.checked }) }), t('physicsSimulation.sensorTriggerOnly', 'Sensor (trigger only)')] }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBody(selectedBody.id), style: {
|
|
311
313
|
background: 'var(--nice-danger, #ef4444)',
|
|
312
314
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
313
315
|
border: 'none',
|
|
@@ -315,7 +317,7 @@ const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, classN
|
|
|
315
317
|
padding: '4px 8px',
|
|
316
318
|
cursor: 'pointer',
|
|
317
319
|
fontSize: 11,
|
|
318
|
-
}, children:
|
|
320
|
+
}, children: t('physicsSimulation.removeRigidBody', 'Remove Rigid Body') })] }))] })), jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: t('physicsSimulation.totalBodies', 'Total bodies: {bodies}').replace('{bodies}', String(bodies.length)) })] }));
|
|
319
321
|
};
|
|
320
322
|
|
|
321
323
|
exports.NicePhysicsSimulation = NicePhysicsSimulation;
|
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
|
|
8
9
|
function _interopNamespaceDefault(e) {
|
|
9
10
|
var n = Object.create(null);
|
|
@@ -313,21 +314,22 @@ function useProceduralGeometry(scene, onGenerated) {
|
|
|
313
314
|
}
|
|
314
315
|
/* ── Component ── */
|
|
315
316
|
const NiceProceduralGeometry = ({ scene, onGenerated, className = '', }) => {
|
|
317
|
+
const { t } = i18n.useNiceTranslation();
|
|
316
318
|
const { type, setType, treeParams, setTreeParams, rockParams, setRockParams, buildingParams, setBuildingParams, gearParams, setGearParams, generate, } = useProceduralGeometry(scene, onGenerated);
|
|
317
|
-
return (jsxRuntime.jsxs("div", { className: `nice-procedural-geometry ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("strong", { children:
|
|
319
|
+
return (jsxRuntime.jsxs("div", { className: `nice-procedural-geometry ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsx("strong", { children: t('proceduralGeometry.proceduralGeometry', 'Procedural Geometry') }), jsxRuntime.jsxs("select", { value: type, onChange: (e) => setType(e.target.value), style: { width: '100%', padding: '4px 8px' }, children: [jsxRuntime.jsx("option", { value: "tree", children: t('proceduralGeometry.tree', 'Tree') }), jsxRuntime.jsx("option", { value: "rock", children: t('proceduralGeometry.rock', 'Rock') }), jsxRuntime.jsx("option", { value: "building", children: t('proceduralGeometry.building', 'Building') }), jsxRuntime.jsx("option", { value: "pipe", children: t('proceduralGeometry.pipe', 'Pipe') }), jsxRuntime.jsx("option", { value: "gear", children: t('proceduralGeometry.gear', 'Gear') })] }), type === 'tree' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.height', 'Height: {trunkHeight}').replace('{trunkHeight}', String(treeParams.trunkHeight.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 1, max: 15, step: 0.5, value: treeParams.trunkHeight, onChange: (e) => setTreeParams({ ...treeParams, trunkHeight: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.branches', 'Branches: {branches}').replace('{branches}', String(treeParams.branches)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 6, value: treeParams.branches, onChange: (e) => setTreeParams({ ...treeParams, branches: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.depth', 'Depth: {depth}').replace('{depth}', String(treeParams.depth)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: treeParams.depth, onChange: (e) => setTreeParams({ ...treeParams, depth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.angle', 'Angle: {branchAngle}°').replace('{branchAngle}', String(treeParams.branchAngle)), jsxRuntime.jsx("input", { type: "range", min: 10, max: 80, value: treeParams.branchAngle, onChange: (e) => setTreeParams({ ...treeParams, branchAngle: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.seed', 'Seed: {seed}').replace('{seed}', String(treeParams.seed)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: treeParams.seed, onChange: (e) => setTreeParams({ ...treeParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'rock' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.radius', 'Radius: {radius}').replace('{radius}', String(rockParams.radius.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.2, max: 5, step: 0.1, value: rockParams.radius, onChange: (e) => setRockParams({ ...rockParams, radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.detail', 'Detail: {detail}').replace('{detail}', String(rockParams.detail)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 5, value: rockParams.detail, onChange: (e) => setRockParams({ ...rockParams, detail: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.noise', 'Noise: {noiseAmount}').replace('{noiseAmount}', String(rockParams.noiseAmount.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 0.5, step: 0.01, value: rockParams.noiseAmount, onChange: (e) => setRockParams({ ...rockParams, noiseAmount: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.seed', 'Seed: {seed}').replace('{seed}', String(rockParams.seed)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 999, value: rockParams.seed, onChange: (e) => setRockParams({ ...rockParams, seed: parseInt(e.target.value, 10) }), style: { width: '100%' } })] })] })), type === 'building' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.width', 'Width: {width}').replace('{width}', String(buildingParams.width)), jsxRuntime.jsx("input", { type: "range", min: 4, max: 20, step: 1, value: buildingParams.width, onChange: (e) => setBuildingParams({ ...buildingParams, width: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.floors', 'Floors: {floors}').replace('{floors}', String(buildingParams.floors)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 20, value: buildingParams.floors, onChange: (e) => setBuildingParams({ ...buildingParams, floors: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.windowsFloor', 'Windows/Floor: {windowsPerFloor}').replace('{windowsPerFloor}', String(buildingParams.windowsPerFloor)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: buildingParams.windowsPerFloor, onChange: (e) => setBuildingParams({
|
|
318
320
|
...buildingParams,
|
|
319
321
|
windowsPerFloor: parseInt(e.target.value, 10),
|
|
320
|
-
}), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [
|
|
322
|
+
}), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.roof', 'Roof:'), jsxRuntime.jsxs("select", { value: buildingParams.roofStyle, onChange: (e) => setBuildingParams({
|
|
321
323
|
...buildingParams,
|
|
322
324
|
roofStyle: e.target.value,
|
|
323
|
-
}), style: { marginLeft: 8 }, children: [jsxRuntime.jsx("option", { value: "flat", children:
|
|
325
|
+
}), style: { marginLeft: 8 }, children: [jsxRuntime.jsx("option", { value: "flat", children: t('proceduralGeometry.flat', 'Flat') }), jsxRuntime.jsx("option", { value: "gable", children: t('proceduralGeometry.gable', 'Gable') }), jsxRuntime.jsx("option", { value: "hip", children: t('proceduralGeometry.hip', 'Hip') })] })] })] })), type === 'gear' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.outerRadius', 'Outer Radius: {outerRadius}').replace('{outerRadius}', String(gearParams.outerRadius.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 5, step: 0.1, value: gearParams.outerRadius, onChange: (e) => setGearParams({ ...gearParams, outerRadius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.teeth', 'Teeth: {teeth}').replace('{teeth}', String(gearParams.teeth)), jsxRuntime.jsx("input", { type: "range", min: 4, max: 30, value: gearParams.teeth, onChange: (e) => setGearParams({ ...gearParams, teeth: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.toothDepth', 'Tooth Depth: {toothDepth}').replace('{toothDepth}', String(gearParams.toothDepth.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: gearParams.toothDepth, onChange: (e) => setGearParams({ ...gearParams, toothDepth: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('proceduralGeometry.thickness', 'Thickness: {thickness}').replace('{thickness}', String(gearParams.thickness.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 2, step: 0.1, value: gearParams.thickness, onChange: (e) => setGearParams({ ...gearParams, thickness: parseFloat(e.target.value) }), style: { width: '100%' } })] })] })), jsxRuntime.jsxs("button", { type: "button", onClick: generate, style: {
|
|
324
326
|
background: 'var(--nice-color-accent, #6366f1)',
|
|
325
327
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
326
328
|
border: 'none',
|
|
327
329
|
borderRadius: 4,
|
|
328
330
|
padding: '6px 12px',
|
|
329
331
|
cursor: 'pointer',
|
|
330
|
-
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "wand", size: 14 }), " Generate
|
|
332
|
+
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "wand", size: 14 }), " ", t('proceduralGeometry.generate', 'Generate')] })] }));
|
|
331
333
|
};
|
|
332
334
|
|
|
333
335
|
exports.NiceProceduralGeometry = NiceProceduralGeometry;
|
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
|
|
8
9
|
function _interopNamespaceDefault(e) {
|
|
9
10
|
var n = Object.create(null);
|
|
@@ -340,6 +341,7 @@ function useTerrainEditor(scene, camera, canvas, config = { width: 100, depth: 1
|
|
|
340
341
|
}
|
|
341
342
|
/* ── Component ── */
|
|
342
343
|
const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrainChange, className = '', tutorial, }) => {
|
|
344
|
+
const { t } = i18n.useNiceTranslation();
|
|
343
345
|
const config = {
|
|
344
346
|
width: 100,
|
|
345
347
|
depth: 100,
|
|
@@ -350,14 +352,14 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
|
|
|
350
352
|
const { brush, setBrush, isPainting, startPainting, stopPainting, generateFlat, generateNoise, exportHeightmap, textureLayers, setTextureLayer, activeTextureLayer, setActiveTextureLayer, } = useTerrainEditor(scene, camera, canvas, config, onTerrainChange);
|
|
351
353
|
const [noiseOctaves, setNoiseOctaves] = React.useState(6);
|
|
352
354
|
const [noisePersistence, setNoisePersistence] = React.useState(0.5);
|
|
353
|
-
return (jsxRuntime.jsxs("div", { className: `nice-terrain-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, TERRAIN_EDITOR_TOUR) }) }), jsxRuntime.jsx("strong", { children:
|
|
355
|
+
return (jsxRuntime.jsxs("div", { className: `nice-terrain-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, TERRAIN_EDITOR_TOUR) }) }), jsxRuntime.jsx("strong", { children: t('terrainEditor.terrainEditor', 'Terrain Editor') }), jsxRuntime.jsxs("fieldset", { style: {
|
|
354
356
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
355
357
|
borderRadius: 6,
|
|
356
358
|
padding: 4,
|
|
357
359
|
display: 'flex',
|
|
358
360
|
flexWrap: 'wrap',
|
|
359
361
|
gap: 2,
|
|
360
|
-
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children:
|
|
362
|
+
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('terrainEditor.brush', 'Brush') }), ['raise', 'lower', 'flatten', 'smooth', 'noise', 'paint'].map((m) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setBrush({ mode: m }), style: {
|
|
361
363
|
padding: '3px 6px',
|
|
362
364
|
fontSize: 11,
|
|
363
365
|
cursor: 'pointer',
|
|
@@ -365,7 +367,7 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
|
|
|
365
367
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
366
368
|
background: brush.mode === m ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
367
369
|
color: brush.mode === m ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
368
|
-
}, children: m }, m)))] }), jsxRuntime.jsxs("label", { children: [
|
|
370
|
+
}, children: m }, m)))] }), jsxRuntime.jsxs("label", { children: [t('terrainEditor.radius', 'Radius: {radius}').replace('{radius}', String(brush.radius.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.5, max: 20, step: 0.5, value: brush.radius, onChange: (e) => setBrush({ radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('terrainEditor.strength', 'Strength: {strength}').replace('{strength}', String(brush.strength.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.01, max: 1, step: 0.01, value: brush.strength, onChange: (e) => setBrush({ strength: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsx("button", { type: "button", onClick: isPainting ? stopPainting : startPainting, style: {
|
|
369
371
|
background: isPainting
|
|
370
372
|
? 'var(--nice-danger, #ef4444)'
|
|
371
373
|
: 'var(--nice-color-accent, #6366f1)',
|
|
@@ -374,19 +376,19 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
|
|
|
374
376
|
borderRadius: 4,
|
|
375
377
|
padding: '6px 12px',
|
|
376
378
|
cursor: 'pointer',
|
|
377
|
-
}, children: isPainting ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " Stop Painting
|
|
379
|
+
}, children: isPainting ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " ", t('terrainEditor.stopPainting', 'Stop Painting')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "brush", size: 14 }), " ", t('terrainEditor.startPainting', 'Start Painting')] })) }), jsxRuntime.jsxs("fieldset", { style: {
|
|
378
380
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
379
381
|
borderRadius: 6,
|
|
380
382
|
padding: 6,
|
|
381
383
|
display: 'grid',
|
|
382
384
|
gap: 4,
|
|
383
|
-
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children:
|
|
385
|
+
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('terrainEditor.procedural', 'Procedural') }), jsxRuntime.jsxs("label", { children: [t('terrainEditor.octaves', 'Octaves: {noiseOctaves}').replace('{noiseOctaves}', String(noiseOctaves)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 10, value: noiseOctaves, onChange: (e) => setNoiseOctaves(parseInt(e.target.value, 10)), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('terrainEditor.persistence', 'Persistence: {noisePersistence}').replace('{noisePersistence}', String(noisePersistence.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 1, step: 0.05, value: noisePersistence, onChange: (e) => setNoisePersistence(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => generateNoise(noiseOctaves, noisePersistence), style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "mountain", size: 14 }), ' ', t('terrainEditor.generateNoise', 'Generate Noise')] }), jsxRuntime.jsxs("button", { type: "button", onClick: generateFlat, style: { flex: 1 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "square", size: 14 }), " ", t('terrainEditor.flatten', 'Flatten')] })] })] }), brush.mode === 'paint' && (jsxRuntime.jsxs("fieldset", { style: {
|
|
384
386
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
385
387
|
borderRadius: 6,
|
|
386
388
|
padding: 6,
|
|
387
389
|
display: 'grid',
|
|
388
390
|
gap: 4,
|
|
389
|
-
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children:
|
|
391
|
+
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('terrainEditor.textureLayers', 'Texture Layers') }), textureLayers.map((layer, i) => (jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "radio", name: "texLayer", checked: activeTextureLayer === i, onChange: () => setActiveTextureLayer(i) }), jsxRuntime.jsx("input", { type: "text", value: layer.name, onChange: (e) => setTextureLayer(i, { name: e.target.value }), style: {
|
|
390
392
|
flex: 1,
|
|
391
393
|
padding: '2px 6px',
|
|
392
394
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
@@ -399,7 +401,7 @@ const NiceTerrainEditor = ({ scene, camera, canvas, config: configProp, onTerrai
|
|
|
399
401
|
link.href = c.toDataURL('image/png');
|
|
400
402
|
link.click();
|
|
401
403
|
}
|
|
402
|
-
}, style: { fontSize: 11 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "download", size: 14 }),
|
|
404
|
+
}, style: { fontSize: 11 }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "download", size: 14 }), ' ', t('terrainEditor.exportHeightmapPng', 'Export Heightmap PNG')] })] }));
|
|
403
405
|
};
|
|
404
406
|
|
|
405
407
|
exports.NiceTerrainEditor = NiceTerrainEditor;
|
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
|
|
8
9
|
function _interopNamespaceDefault(e) {
|
|
9
10
|
var n = Object.create(null);
|
|
@@ -246,17 +247,18 @@ function updateHeatMapColors(mesh, boneIndex, colors) {
|
|
|
246
247
|
}
|
|
247
248
|
/* ── Component ── */
|
|
248
249
|
const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, scene, camera, canvas, onWeightsChange, className = '', }) => {
|
|
250
|
+
const { t } = i18n.useNiceTranslation();
|
|
249
251
|
const { brush, setBrush, isPainting, activeBoneIndex, setActiveBoneIndex, boneNames, startPainting, stopPainting, normalizeWeights, showHeatMap, setShowHeatMap, } = useWeightPainter(skinnedMesh, scene, camera, canvas, onWeightsChange);
|
|
250
252
|
React.useEffect(() => {
|
|
251
253
|
setActiveBoneIndex(initialBoneIndex);
|
|
252
254
|
}, [initialBoneIndex, setActiveBoneIndex]);
|
|
253
|
-
return (jsxRuntime.jsxs("div", { className: `nice-weight-painter ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("label", { children: [
|
|
255
|
+
return (jsxRuntime.jsxs("div", { className: `nice-weight-painter ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("label", { children: [t('weightPainter.bone', 'Bone:'), jsxRuntime.jsx("select", { value: activeBoneIndex, onChange: (e) => setActiveBoneIndex(parseInt(e.target.value, 10)), style: { marginLeft: 8, width: '100%' }, children: boneNames.map((name, i) => (jsxRuntime.jsx("option", { value: i, children: name }, i))) })] }), jsxRuntime.jsxs("fieldset", { style: {
|
|
254
256
|
display: 'flex',
|
|
255
257
|
gap: 4,
|
|
256
258
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
257
259
|
borderRadius: 6,
|
|
258
260
|
padding: 4,
|
|
259
|
-
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children:
|
|
261
|
+
}, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('weightPainter.mode', 'Mode') }), ['add', 'subtract', 'smooth', 'blur'].map((mode) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setBrush({ mode }), style: {
|
|
260
262
|
flex: 1,
|
|
261
263
|
padding: '4px 6px',
|
|
262
264
|
background: brush.mode === mode ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
@@ -265,7 +267,7 @@ const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, sce
|
|
|
265
267
|
borderRadius: 4,
|
|
266
268
|
cursor: 'pointer',
|
|
267
269
|
fontSize: 11,
|
|
268
|
-
}, children: mode }, mode)))] }), jsxRuntime.jsxs("label", { children: [
|
|
270
|
+
}, children: mode }, mode)))] }), jsxRuntime.jsxs("label", { children: [t('weightPainter.radius', 'Radius: {radius}').replace('{radius}', String(brush.radius.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 2, step: 0.05, value: brush.radius, onChange: (e) => setBrush({ radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('weightPainter.strength', 'Strength: {strength}').replace('{strength}', String(brush.strength.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.01, max: 1, step: 0.01, value: brush.strength, onChange: (e) => setBrush({ strength: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("button", { type: "button", onClick: isPainting ? stopPainting : startPainting, style: {
|
|
269
271
|
flex: 1,
|
|
270
272
|
background: isPainting
|
|
271
273
|
? 'var(--nice-danger, #ef4444)'
|
|
@@ -275,7 +277,7 @@ const NiceWeightPainter = ({ skinnedMesh, activeBoneIndex: initialBoneIndex, sce
|
|
|
275
277
|
borderRadius: 4,
|
|
276
278
|
padding: '6px 12px',
|
|
277
279
|
cursor: 'pointer',
|
|
278
|
-
}, children: isPainting ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " Stop Painting
|
|
280
|
+
}, children: isPainting ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " ", t('weightPainter.stopPainting', 'Stop Painting')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "brush", size: 14 }), ' ', t('weightPainter.startPainting', 'Start Painting')] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: normalizeWeights, title: t('weightPainter.normalizeAllWeights', 'Normalize all weights'), children: [jsxRuntime.jsx(ui.NiceIcon, { name: "scale", size: 14 }), " ", t('weightPainter.normalize', 'Normalize')] })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showHeatMap, onChange: (e) => setShowHeatMap(e.target.checked) }), t('weightPainter.showWeightHeatMap', 'Show weight heat map')] })] }));
|
|
279
281
|
};
|
|
280
282
|
|
|
281
283
|
exports.NiceWeightPainter = NiceWeightPainter;
|
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var THREE = require('three');
|
|
7
|
+
var i18n = require('../core/i18n.js');
|
|
7
8
|
|
|
8
9
|
function _interopNamespaceDefault(e) {
|
|
9
10
|
var n = Object.create(null);
|
|
@@ -280,13 +281,14 @@ function useXRPreview(renderer, scene, camera, onSessionStart, onSessionEnd) {
|
|
|
280
281
|
}
|
|
281
282
|
/* ── Component ── */
|
|
282
283
|
const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionStart, onSessionEnd, className = '', }) => {
|
|
284
|
+
const { t } = i18n.useNiceTranslation();
|
|
283
285
|
const { isSupported, isSessionActive, mode, setMode, startSession, endSession, controllers, teleportEnabled, setTeleportEnabled, hitTestResult, } = useXRPreview(renderer, scene, camera, onSessionStart, onSessionEnd);
|
|
284
286
|
React.useEffect(() => {
|
|
285
287
|
if (initialMode) {
|
|
286
288
|
setMode(initialMode);
|
|
287
289
|
}
|
|
288
290
|
}, [initialMode, setMode]);
|
|
289
|
-
return (jsxRuntime.jsxs("div", { className: `nice-xr-preview ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "eye", size: 14 }), " XR Preview
|
|
291
|
+
return (jsxRuntime.jsxs("div", { className: `nice-xr-preview ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "eye", size: 14 }), " ", t('xrPreview.xrPreview', 'XR Preview')] }), !isSupported && (jsxRuntime.jsx("div", { style: { color: 'var(--nice-danger, #ef4444)', fontSize: 11 }, children: t('xrPreview.webxrIsNotSupportedIn', 'WebXR is not supported in this browser.') })), isSupported && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setMode('vr'), style: {
|
|
290
292
|
flex: 1,
|
|
291
293
|
padding: '5px 8px',
|
|
292
294
|
borderRadius: 4,
|
|
@@ -294,7 +296,7 @@ const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionSt
|
|
|
294
296
|
background: mode === 'vr' ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
295
297
|
color: mode === 'vr' ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
296
298
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
297
|
-
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "eye", size: 14 }), " VR
|
|
299
|
+
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "eye", size: 14 }), " ", t('xrPreview.vr', 'VR')] }), jsxRuntime.jsxs("button", { type: "button", onClick: () => setMode('ar'), style: {
|
|
298
300
|
flex: 1,
|
|
299
301
|
padding: '5px 8px',
|
|
300
302
|
borderRadius: 4,
|
|
@@ -302,7 +304,7 @@ const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionSt
|
|
|
302
304
|
background: mode === 'ar' ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
|
|
303
305
|
color: mode === 'ar' ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
304
306
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
305
|
-
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "window", size: 14 }), " AR
|
|
307
|
+
}, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "window", size: 14 }), " ", t('xrPreview.ar', 'AR')] })] }), jsxRuntime.jsx("button", { type: "button", onClick: isSessionActive ? endSession : startSession, style: {
|
|
306
308
|
background: isSessionActive
|
|
307
309
|
? 'var(--nice-danger, #ef4444)'
|
|
308
310
|
: 'var(--nice-success, #22c55e)',
|
|
@@ -311,7 +313,10 @@ const NiceXRPreview = ({ renderer, scene, camera, mode: initialMode, onSessionSt
|
|
|
311
313
|
borderRadius: 4,
|
|
312
314
|
padding: '6px 12px',
|
|
313
315
|
cursor: 'pointer',
|
|
314
|
-
}, children: isSessionActive ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " Exit XR
|
|
316
|
+
}, children: isSessionActive ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "stop", size: 14 }), " ", t('xrPreview.exitXr', 'Exit XR')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", t('xrPreview.enterXr', 'Enter XR')] })) }), mode === 'vr' && (jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: teleportEnabled, onChange: (e) => setTeleportEnabled(e.target.checked) }), t('xrPreview.teleportation', 'Teleportation')] })), isSessionActive && (jsxRuntime.jsxs("div", { style: { fontSize: 11, opacity: 0.7 }, children: [t('xrPreview.controllers', 'Controllers: {controllers}').replace('{controllers}', String(controllers.length)), jsxRuntime.jsx("br", {}), mode === 'ar' && hitTestResult && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: t('xrPreview.hit', 'Hit: ({x}, {y}, {z})')
|
|
317
|
+
.replace('{x}', String(hitTestResult.x.toFixed(2)))
|
|
318
|
+
.replace('{y}', String(hitTestResult.y.toFixed(2)))
|
|
319
|
+
.replace('{z}', String(hitTestResult.z.toFixed(2))) }))] }))] }))] }));
|
|
315
320
|
};
|
|
316
321
|
|
|
317
322
|
exports.NiceXRPreview = NiceXRPreview;
|