@nice2dev/ui-3d 1.1.10 → 1.2.1
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 +173 -0
- package/README.md +11 -15
- package/dist/cjs/core/editorAsk.js +48 -0
- package/dist/cjs/core/i18n.js +7 -3
- package/dist/cjs/core/threeCompat.js +52 -0
- package/dist/cjs/dance/DanceScoreEngine.js +7 -0
- package/dist/cjs/dance/PoseDetector.js +116 -64
- package/dist/cjs/index.js +0 -3
- package/dist/cjs/material/NiceMaterialEditor.js +7 -3
- package/dist/cjs/material/materialEditorTypes.js +1 -1
- package/dist/cjs/material/materialNodeDefinitions.js +14 -11
- package/dist/cjs/model/ModelEditor.js +3 -5
- package/dist/cjs/model/ModelEditorLeftPanel.js +10 -4
- package/dist/cjs/model/ModelEditorMenuBar.js +7 -5
- package/dist/cjs/model/ModelEditorRightPanel.js +18 -16
- package/dist/cjs/model/ModelEditorSubComponents.js +8 -6
- package/dist/cjs/model/ModelEditorTimeline.js +6 -4
- package/dist/cjs/model/ModelEditorToolbar.js +6 -4
- package/dist/cjs/model/ModelEditorViewport.js +16 -6
- package/dist/cjs/model/ModelViewer.js +6 -7
- package/dist/cjs/model/NiceArmatureEditor.js +8 -6
- package/dist/cjs/model/NiceMorphTargetEditor.js +5 -3
- package/dist/cjs/model/NiceOctree.js +3 -1
- package/dist/cjs/model/NicePhysicsSimulation.js +7 -5
- package/dist/cjs/model/NiceProceduralGeometry.js +5 -3
- package/dist/cjs/model/NiceTerrainEditor.js +10 -8
- package/dist/cjs/model/NiceWeightPainter.js +6 -4
- package/dist/cjs/model/NiceXRPreview.js +7 -5
- package/dist/cjs/model/useModelEditor.js +30 -18
- package/dist/cjs/model/useModelViewer.js +13 -9
- package/dist/cjs/nice2dev-ui-3d.css +1 -1
- package/dist/cjs/particle/NiceParticleEditor.js +14 -9
- package/dist/cjs/rendering/NiceCascadedShadows.js +3 -1
- package/dist/cjs/rendering/NiceRenderExport.js +11 -5
- package/dist/cjs/rendering/NiceSSAO.js +3 -1
- package/dist/cjs/rendering/NiceSSR.js +3 -1
- package/dist/cjs/rendering/NiceWebGPURenderer.js +7 -4
- 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/core/threeCompat.js +30 -0
- package/dist/esm/dance/DanceScoreEngine.js +7 -0
- package/dist/esm/dance/PoseDetector.js +118 -65
- package/dist/esm/index.js +3 -3
- package/dist/esm/material/NiceMaterialEditor.js +7 -3
- package/dist/esm/material/materialEditorTypes.js +1 -1
- package/dist/esm/material/materialNodeDefinitions.js +14 -11
- package/dist/esm/model/ModelEditor.js +4 -5
- package/dist/esm/model/ModelEditorLeftPanel.js +10 -4
- package/dist/esm/model/ModelEditorMenuBar.js +7 -5
- package/dist/esm/model/ModelEditorRightPanel.js +18 -16
- package/dist/esm/model/ModelEditorSubComponents.js +8 -6
- package/dist/esm/model/ModelEditorTimeline.js +6 -4
- package/dist/esm/model/ModelEditorToolbar.js +6 -4
- package/dist/esm/model/ModelEditorViewport.js +16 -6
- package/dist/esm/model/ModelViewer.js +7 -7
- package/dist/esm/model/NiceArmatureEditor.js +9 -7
- package/dist/esm/model/NiceMorphTargetEditor.js +5 -3
- package/dist/esm/model/NiceOctree.js +3 -1
- package/dist/esm/model/NicePhysicsSimulation.js +7 -5
- package/dist/esm/model/NiceProceduralGeometry.js +5 -3
- package/dist/esm/model/NiceTerrainEditor.js +10 -8
- package/dist/esm/model/NiceWeightPainter.js +6 -4
- package/dist/esm/model/NiceXRPreview.js +7 -5
- package/dist/esm/model/useModelEditor.js +30 -18
- package/dist/esm/model/useModelViewer.js +13 -9
- package/dist/esm/nice2dev-ui-3d.css +1 -1
- package/dist/esm/particle/NiceParticleEditor.js +14 -9
- package/dist/esm/rendering/NiceCascadedShadows.js +3 -1
- package/dist/esm/rendering/NiceRenderExport.js +11 -5
- package/dist/esm/rendering/NiceSSAO.js +3 -1
- package/dist/esm/rendering/NiceSSR.js +3 -1
- package/dist/esm/rendering/NiceWebGPURenderer.js +7 -4
- 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/core/threeCompat.d.ts +14 -0
- package/dist/types/dance/DanceScoreEngine.d.ts +0 -6
- package/dist/types/dance/PoseDetector.d.ts +14 -13
- package/dist/types/index.d.ts +4 -4
- package/dist/types/model/ModelEditor.d.ts +0 -4
- package/dist/types/model/ModelViewer.d.ts +0 -4
- package/dist/types/model/useModelEditor.d.ts +3 -0
- package/package.json +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceTutorialButton, resolveTutorial, NiceAppShell, resolveCssColor, NiceIcon } from '@nice2dev/ui';
|
|
3
3
|
import { useRef, useMemo, useEffect, useReducer, useState } from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
import styles from './ParticleEditor.module.css.js';
|
|
5
6
|
import { MODULE_NAMES, BLEND_MODE_NAMES, RENDER_MODE_NAMES, EMITTER_SHAPE_NAMES } from './particleEditorTypes.js';
|
|
6
7
|
import { applyPreset, createDefaultSystem, PARTICLE_PRESETS, importSystemsFromJSON, exportSystemsToJSON, cloneSystem } from './particleEditorUtils.js';
|
|
@@ -280,6 +281,7 @@ function useParticleEditor(initialSystems) {
|
|
|
280
281
|
return { state, dispatch };
|
|
281
282
|
}
|
|
282
283
|
function SystemListPanel({ systems, selectedId, onSelect, onAdd, onRemove, onDuplicate, onRename, onAddPreset, }) {
|
|
284
|
+
const { t } = useNiceTranslation();
|
|
283
285
|
const [editingId, setEditingId] = useState(null);
|
|
284
286
|
const [editName, setEditName] = useState('');
|
|
285
287
|
const [showPresets, setShowPresets] = useState(false);
|
|
@@ -293,20 +295,21 @@ function SystemListPanel({ systems, selectedId, onSelect, onAdd, onRemove, onDup
|
|
|
293
295
|
}
|
|
294
296
|
setEditingId(null);
|
|
295
297
|
};
|
|
296
|
-
return (jsxs("div", { className: styles.systemList, children: [jsxs("div", { className: styles.systemListHeader, children: [jsxs("span", { children: ["Systems (", systems.length, ")"] }), jsxs("div", { className: styles.systemListActions, children: [jsx("button", { onClick: onAdd, title:
|
|
298
|
+
return (jsxs("div", { className: styles.systemList, children: [jsxs("div", { className: styles.systemListHeader, children: [jsxs("span", { children: ["Systems (", systems.length, ")"] }), jsxs("div", { className: styles.systemListActions, children: [jsx("button", { onClick: onAdd, title: t('particleEditor.newSystem', 'New System'), "aria-label": t('particleEditor.newSystem', 'New System'), children: jsx(NiceIcon, { name: "plus", size: 14 }) }), jsx("button", { onClick: () => setShowPresets(!showPresets), title: t('particleEditor.addPreset', 'Add Preset'), "aria-label": t('particleEditor.addPreset', 'Add Preset'), children: jsx(NiceIcon, { name: "clipboard", size: 14 }) })] })] }), showPresets && (jsx("div", { className: styles.presetMenu, children: Object.keys(PARTICLE_PRESETS).map((preset) => (jsx("button", { className: styles.presetItem, onClick: () => {
|
|
297
299
|
onAddPreset(preset);
|
|
298
300
|
setShowPresets(false);
|
|
299
301
|
}, children: preset.charAt(0).toUpperCase() + preset.slice(1) }, preset))) })), jsx("div", { className: styles.systemItems, children: systems.map((system) => (jsx("div", { className: `${styles.systemItem} ${selectedId === system.id ? styles.selected : ''}`, onClick: () => onSelect(system.id), children: editingId === system.id ? (jsx("input", { type: "text", value: editName, onChange: (e) => setEditName(e.target.value), onBlur: handleFinishRename, onKeyDown: (e) => e.key === 'Enter' && handleFinishRename(), autoFocus: true, className: styles.renameInput })) : (jsxs(Fragment, { children: [jsx("span", { className: styles.systemName, onDoubleClick: () => handleStartRename(system), children: system.name }), jsxs("div", { className: styles.systemItemActions, children: [jsx("button", { onClick: (e) => {
|
|
300
302
|
e.stopPropagation();
|
|
301
303
|
onDuplicate(system.id);
|
|
302
|
-
}, title:
|
|
304
|
+
}, title: t('particleEditor.duplicate', 'Duplicate'), "aria-label": t('particleEditor.duplicate', 'Duplicate'), children: jsx(NiceIcon, { name: "clipboard", size: 14 }) }), jsx("button", { onClick: (e) => {
|
|
303
305
|
e.stopPropagation();
|
|
304
306
|
onRemove(system.id);
|
|
305
|
-
}, title:
|
|
307
|
+
}, title: t('particleEditor.remove', 'Remove'), "aria-label": t('particleEditor.remove', 'Remove'), disabled: systems.length <= 1, children: jsx(NiceIcon, { name: "trash", size: 14 }) })] })] })) }, system.id))) })] }));
|
|
306
308
|
}
|
|
307
309
|
function InspectorPanel({ system, expandedModules, selectedModule, onToggleModule, onSelectModule: _onSelectModule, onUpdateSystem, }) {
|
|
310
|
+
const { t } = useNiceTranslation();
|
|
308
311
|
if (!system) {
|
|
309
|
-
return (jsx("div", { className: styles.inspector, children: jsx("div", { className: styles.emptyInspector, children:
|
|
312
|
+
return (jsx("div", { className: styles.inspector, children: jsx("div", { className: styles.emptyInspector, children: t('particleEditor.noSystemSelected', 'No system selected') }) }));
|
|
310
313
|
}
|
|
311
314
|
const modules = [
|
|
312
315
|
{ key: 'main', hasEnabled: false },
|
|
@@ -351,6 +354,7 @@ function InspectorPanel({ system, expandedModules, selectedModule, onToggleModul
|
|
|
351
354
|
}, className: styles.moduleCheckbox })), jsx("span", { className: styles.moduleName, children: MODULE_NAMES[mod.key] || mod.key })] }), expandedModules.includes(mod.key) && (jsx("div", { className: styles.moduleContent, children: jsx(ModuleEditor, { system: system, moduleKey: mod.key, onUpdate: onUpdateSystem }) }))] }, mod.key))) })] }));
|
|
352
355
|
}
|
|
353
356
|
function ModuleEditor({ system, moduleKey, onUpdate }) {
|
|
357
|
+
const { t } = useNiceTranslation();
|
|
354
358
|
const renderDistributionInput = (label, value, onChange) => (jsxs("div", { className: styles.propertyRow, children: [jsx("label", { children: label }), jsxs("div", { className: styles.distributionInput, children: [jsxs("select", { value: value.type, onChange: (e) => {
|
|
355
359
|
const newType = e.target.value;
|
|
356
360
|
if (newType === 'constant') {
|
|
@@ -368,7 +372,7 @@ function ModuleEditor({ system, moduleKey, onUpdate }) {
|
|
|
368
372
|
],
|
|
369
373
|
});
|
|
370
374
|
}
|
|
371
|
-
}, className: styles.distributionType, children: [jsx("option", { value: "constant", children:
|
|
375
|
+
}, className: styles.distributionType, children: [jsx("option", { value: "constant", children: t('particleEditor.constant', 'Constant') }), jsx("option", { value: "random", children: t('particleEditor.random', 'Random') }), jsx("option", { value: "curve", children: t('particleEditor.curve', 'Curve') })] }), value.type === 'constant' && (jsx("input", { type: "number", value: value.value, onChange: (e) => onChange({ ...value, value: parseFloat(e.target.value) || 0 }), className: styles.numberInput })), value.type === 'random' && (jsxs(Fragment, { children: [jsx("input", { type: "number", value: value.min, onChange: (e) => onChange({ ...value, min: parseFloat(e.target.value) || 0 }), className: styles.numberInput, placeholder: t('particleEditor.min', 'Min') }), jsx("input", { type: "number", value: value.max, onChange: (e) => onChange({ ...value, max: parseFloat(e.target.value) || 0 }), className: styles.numberInput, placeholder: t('particleEditor.max', 'Max') })] }))] })] }));
|
|
372
376
|
const renderNumberInput = (label, value, onChange, props) => (jsxs("div", { className: styles.propertyRow, children: [jsx("label", { children: label }), jsx("input", { type: "number", value: value, onChange: (e) => onChange(parseFloat(e.target.value) || 0), className: styles.numberInput, ...props })] }));
|
|
373
377
|
const renderToggle = (label, value, onChange) => (jsxs("div", { className: styles.propertyRow, children: [jsx("label", { children: label }), jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked), className: styles.toggleInput })] }));
|
|
374
378
|
const renderSelect = (label, value, options, onChange) => (jsxs("div", { className: styles.propertyRow, children: [jsx("label", { children: label }), jsx("select", { value: value, onChange: (e) => onChange(e.target.value), className: styles.selectInput, children: Object.entries(options).map(([key, name]) => (jsx("option", { value: key, children: name }, key))) })] }));
|
|
@@ -393,7 +397,7 @@ function ModuleEditor({ system, moduleKey, onUpdate }) {
|
|
|
393
397
|
},
|
|
394
398
|
],
|
|
395
399
|
},
|
|
396
|
-
}), children:
|
|
400
|
+
}), children: t('particleEditor.addBurst', 'Add Burst') })] })] }));
|
|
397
401
|
case 'shape':
|
|
398
402
|
return (jsxs(Fragment, { children: [renderSelect('Shape', system.shape.type, EMITTER_SHAPE_NAMES, (v) =>
|
|
399
403
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- writing into discriminated-union variant; runtime narrowed by `type`
|
|
@@ -421,11 +425,12 @@ function ModuleEditor({ system, moduleKey, onUpdate }) {
|
|
|
421
425
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- BLEND_MODE_NAMES is keyed by ParticleBlendMode but renderSelect is generic
|
|
422
426
|
BLEND_MODE_NAMES, (v) => onUpdate({ material: { ...system.material, blendMode: v } }))] }));
|
|
423
427
|
default:
|
|
424
|
-
return jsx("div", { className: styles.moduleNotImplemented, children:
|
|
428
|
+
return (jsx("div", { className: styles.moduleNotImplemented, children: t('particleEditor.moduleEditorNotYetImplemented', 'Module editor not yet implemented') }));
|
|
425
429
|
}
|
|
426
430
|
}
|
|
427
431
|
function Toolbar({ isPlaying, isPaused, playbackSpeed, currentTime, canUndo, canRedo, showGrid, showBounds, showEmitterShape, viewMode, onPlay, onPause, onStop, onRestart, onSpeedChange, onUndo, onRedo, onToggleGrid, onToggleBounds, onToggleEmitterShape, onViewModeChange, onNew, onExport, onImport, }) {
|
|
428
|
-
|
|
432
|
+
const { t } = useNiceTranslation();
|
|
433
|
+
return (jsxs("div", { className: styles.toolbar, children: [jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onNew, title: t('particleEditor.newProject', 'New Project'), "aria-label": t('particleEditor.newProject', 'New Project'), children: jsx(NiceIcon, { name: "file", size: 16 }) }), jsx("button", { onClick: onImport, title: t('particleEditor.import', 'Import'), "aria-label": t('particleEditor.import', 'Import'), children: jsx(NiceIcon, { name: "folder", size: 16 }) }), jsx("button", { onClick: onExport, title: t('particleEditor.export', 'Export'), "aria-label": t('particleEditor.export', 'Export'), children: jsx(NiceIcon, { name: "save", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onUndo, disabled: !canUndo, title: t('particleEditor.undo', 'Undo'), "aria-label": t('particleEditor.undo', 'Undo'), children: jsx(NiceIcon, { name: "arrow-left", size: 16 }) }), jsx("button", { onClick: onRedo, disabled: !canRedo, title: t('particleEditor.redo', 'Redo'), "aria-label": t('particleEditor.redo', 'Redo'), children: jsx(NiceIcon, { name: "arrow-right", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [isPlaying && !isPaused ? (jsx("button", { onClick: onPause, title: t('particleEditor.pause', 'Pause'), "aria-label": t('particleEditor.pause', 'Pause'), children: jsx(NiceIcon, { name: "pause", size: 16 }) })) : (jsx("button", { onClick: onPlay, title: t('particleEditor.play', 'Play'), "aria-label": t('particleEditor.play', 'Play'), children: jsx(NiceIcon, { name: "play", size: 16 }) })), jsx("button", { onClick: onStop, title: t('particleEditor.stop', 'Stop'), "aria-label": t('particleEditor.stop', 'Stop'), children: jsx(NiceIcon, { name: "stop", size: 16 }) }), jsx("button", { onClick: onRestart, title: t('particleEditor.restart', 'Restart'), "aria-label": t('particleEditor.restart', 'Restart'), children: jsx(NiceIcon, { name: "refresh", size: 16 }) }), jsxs("select", { value: playbackSpeed, onChange: (e) => onSpeedChange(parseFloat(e.target.value)), className: styles.speedSelect, title: t('particleEditor.playbackSpeed', 'Playback Speed'), children: [jsx("option", { value: 0.25, children: "0.25x" }), jsx("option", { value: 0.5, children: "0.5x" }), jsx("option", { value: 1, children: "1x" }), jsx("option", { value: 2, children: "2x" }), jsx("option", { value: 4, children: "4x" })] }), jsxs("span", { className: styles.timeDisplay, children: [currentTime.toFixed(2), "s"] })] }), jsx("div", { className: styles.toolbarSeparator }), jsxs("div", { className: styles.toolbarGroup, children: [jsx("button", { onClick: onToggleGrid, className: showGrid ? styles.active : '', title: t('particleEditor.toggleGrid', 'Toggle Grid'), "aria-label": t('particleEditor.toggleGrid', 'Toggle Grid'), children: jsx(NiceIcon, { name: "hash", size: 16 }) }), jsx("button", { onClick: onToggleBounds, className: showBounds ? styles.active : '', title: t('particleEditor.toggleBounds', 'Toggle Bounds'), "aria-label": t('particleEditor.toggleBounds', 'Toggle Bounds'), children: jsx(NiceIcon, { name: "square", size: 16 }) }), jsx("button", { onClick: onToggleEmitterShape, className: showEmitterShape ? styles.active : '', title: t('particleEditor.toggleEmitterShape', 'Toggle Emitter Shape'), "aria-label": t('particleEditor.toggleEmitterShape', 'Toggle Emitter Shape'), children: jsx(NiceIcon, { name: "target", size: 16 }) })] }), jsx("div", { className: styles.toolbarSeparator }), jsx("div", { className: styles.toolbarGroup, children: ['3d', 'top', 'side', 'front'].map((mode) => (jsx("button", { onClick: () => onViewModeChange(mode), className: viewMode === mode ? styles.active : '', title: mode.toUpperCase(), children: mode === '3d' ? jsx(NiceIcon, { name: "video", size: 16 }) : mode.charAt(0).toUpperCase() }, mode))) })] }));
|
|
429
434
|
}
|
|
430
435
|
function PreviewPanel({ systems, isPlaying, isPaused, currentTime, showGrid, showEmitterShape, }) {
|
|
431
436
|
const canvasRef = useRef(null);
|
|
@@ -466,7 +471,7 @@ function PreviewPanel({ systems, isPlaying, isPaused, currentTime, showGrid, sho
|
|
|
466
471
|
}
|
|
467
472
|
// Draw emitter shape indicator
|
|
468
473
|
if (showEmitterShape && systems.length > 0) {
|
|
469
|
-
ctx.strokeStyle = 'rgba(0,200,255,0.5)';
|
|
474
|
+
ctx.strokeStyle = resolveCssColor('var(--nice-primary-tint-60, rgba(0,200,255,0.5))', ctx.canvas);
|
|
470
475
|
ctx.lineWidth = 2;
|
|
471
476
|
ctx.setLineDash([5, 5]);
|
|
472
477
|
ctx.beginPath();
|
|
@@ -2,6 +2,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { NiceIcon } from '@nice2dev/ui';
|
|
3
3
|
import { useEffect, useState, useRef, useCallback } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
5
6
|
|
|
6
7
|
/* ── Cascade split calculation ── */
|
|
7
8
|
function computeCascadeSplits(cascadeCount, near, far, splitMode, lambda) {
|
|
@@ -245,13 +246,14 @@ function useCSM(scene, camera, configOverride) {
|
|
|
245
246
|
}
|
|
246
247
|
/* ── Component ── */
|
|
247
248
|
const NiceCascadedShadows = ({ scene, camera, config: configProp, showDebug: initialDebug, className = '', }) => {
|
|
249
|
+
const { t } = useNiceTranslation();
|
|
248
250
|
const { config, setConfig, cascadeSplits, showDebug, setShowDebug } = useCSM(scene, camera, configProp);
|
|
249
251
|
useEffect(() => {
|
|
250
252
|
if (initialDebug !== undefined) {
|
|
251
253
|
setShowDebug(initialDebug);
|
|
252
254
|
}
|
|
253
255
|
}, [initialDebug, setShowDebug]);
|
|
254
|
-
return (jsxs("div", { className: `nice-cascaded-shadows ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }),
|
|
256
|
+
return (jsxs("div", { className: `nice-cascaded-shadows ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "lightbulb", size: 14 }), ' ', t('cascadedShadows.cascadedShadowMaps', 'Cascaded Shadow Maps')] }), jsxs("label", { children: ["Cascades: ", config.cascadeCount, jsx("input", { type: "range", min: 1, max: 5, value: config.cascadeCount, onChange: (e) => setConfig({ cascadeCount: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Max Distance: ", config.maxFar, 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%' } })] }), jsxs("label", { children: ["Split Mode:", jsxs("select", { value: config.splitMode, onChange: (e) => setConfig({ splitMode: e.target.value }), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: "practical", children: t('cascadedShadows.practical', 'Practical') }), jsx("option", { value: "logarithmic", children: t('cascadedShadows.logarithmic', 'Logarithmic') }), jsx("option", { value: "linear", children: t('cascadedShadows.linear', 'Linear') })] })] }), config.splitMode === 'practical' && (jsxs("label", { children: ["Lambda: ", config.practicalSplitLambda.toFixed(2), 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%' } })] })), jsxs("label", { children: ["Shadow Map Size: ", config.shadowMapSize, jsxs("select", { value: config.shadowMapSize, onChange: (e) => setConfig({ shadowMapSize: parseInt(e.target.value, 10) }), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: 512, children: "512" }), jsx("option", { value: 1024, children: "1024" }), jsx("option", { value: 2048, children: "2048" }), jsx("option", { value: 4096, children: "4096" })] })] }), jsxs("div", { style: { fontSize: 10, opacity: 0.7 }, children: ["Cascade splits: ", cascadeSplits.map((s) => s.toFixed(1)).join(' | ')] }), jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsx("input", { type: "checkbox", checked: showDebug, onChange: (e) => setShowDebug(e.target.checked) }), t('cascadedShadows.showCascadeFrustums', 'Show cascade frustums')] })] }));
|
|
255
257
|
};
|
|
256
258
|
|
|
257
259
|
export { NiceCascadedShadows, useCSM };
|
|
@@ -2,6 +2,7 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import { NiceIcon } from '@nice2dev/ui';
|
|
3
3
|
import { useState, useCallback } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
5
6
|
|
|
6
7
|
/* ── EXR writer (minimal, uncompressed) ── */
|
|
7
8
|
function writeEXR(width, height, floatData) {
|
|
@@ -182,6 +183,7 @@ const DEFAULT_CONFIG = {
|
|
|
182
183
|
// resolves to nothing, so the exported frame needs the literal (cf. 344d1b2).
|
|
183
184
|
backgroundColor: '#000000',
|
|
184
185
|
backgroundMode: 'transparent',
|
|
186
|
+
// audit-allow: literal-color +2 — the default sky gradient of the rendered image
|
|
185
187
|
gradientTop: '#87CEEB',
|
|
186
188
|
gradientBottom: '#2E4057',
|
|
187
189
|
samples: 1,
|
|
@@ -296,6 +298,7 @@ function useRenderExport(renderer, scene, camera) {
|
|
|
296
298
|
}
|
|
297
299
|
/* ── Component ── */
|
|
298
300
|
const NiceRenderExport = ({ renderer, scene, camera, className = '', }) => {
|
|
301
|
+
const { t } = useNiceTranslation();
|
|
299
302
|
const { config, setConfig, render, isRendering, presetSizes } = useRenderExport(renderer, scene, camera);
|
|
300
303
|
const handleRender = async () => {
|
|
301
304
|
const blob = await render();
|
|
@@ -310,7 +313,7 @@ const NiceRenderExport = ({ renderer, scene, camera, className = '', }) => {
|
|
|
310
313
|
link.click();
|
|
311
314
|
URL.revokeObjectURL(url);
|
|
312
315
|
};
|
|
313
|
-
return (jsxs("div", { className: `nice-render-export ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "camera", size: 14 }), " Render Export
|
|
316
|
+
return (jsxs("div", { className: `nice-render-export ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "camera", size: 14 }), " ", t('renderExport.renderExport', 'Render Export')] }), jsx("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 2 }, children: presetSizes.map((p) => (jsx("button", { type: "button", onClick: () => setConfig({ width: p.width, height: p.height }), style: {
|
|
314
317
|
padding: '2px 6px',
|
|
315
318
|
fontSize: 10,
|
|
316
319
|
cursor: 'pointer',
|
|
@@ -320,18 +323,21 @@ const NiceRenderExport = ({ renderer, scene, camera, className = '', }) => {
|
|
|
320
323
|
? 'var(--nice-color-accent, #6366f1)'
|
|
321
324
|
: 'transparent',
|
|
322
325
|
color: config.width === p.width && config.height === p.height
|
|
323
|
-
? 'var(--nice-
|
|
326
|
+
? 'var(--nice-color-white, #fff)'
|
|
324
327
|
: 'inherit',
|
|
325
|
-
}, children: p.label }, p.label))) }), jsxs("div", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsx("input", { type: "number", value: config.width, min: 1, max: 8192, onChange: (e) => setConfig({ width: parseInt(e.target.value, 10) || 1 }), style: { width: 70, padding: '3px 6px' } }), jsx("span", { children: "\u00D7" }), jsx("input", { type: "number", value: config.height, min: 1, max: 8192, onChange: (e) => setConfig({ height: parseInt(e.target.value, 10) || 1 }), style: { width: 70, padding: '3px 6px' } })] }), jsxs("label", { children: ["Format:", jsxs("select", { value: config.format, onChange: (e) => setConfig({ format: e.target.value }), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: "png", children: "PNG (lossless + alpha)" }), jsx("option", { value: "jpeg", children: "JPEG (lossy, no alpha)" }), jsx("option", { value: "exr", children: "EXR (HDR float)" })] })] }), jsxs("label", { children: ["Background:", jsxs("select", { value: config.backgroundMode, onChange: (e) => setConfig({ backgroundMode: e.target.value }), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: "transparent", children:
|
|
328
|
+
}, children: p.label }, p.label))) }), jsxs("div", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsx("input", { type: "number", value: config.width, min: 1, max: 8192, onChange: (e) => setConfig({ width: parseInt(e.target.value, 10) || 1 }), style: { width: 70, padding: '3px 6px' } }), jsx("span", { children: "\u00D7" }), jsx("input", { type: "number", value: config.height, min: 1, max: 8192, onChange: (e) => setConfig({ height: parseInt(e.target.value, 10) || 1 }), style: { width: 70, padding: '3px 6px' } })] }), jsxs("label", { children: ["Format:", jsxs("select", { value: config.format, onChange: (e) => setConfig({ format: e.target.value }), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: "png", children: "PNG (lossless + alpha)" }), jsx("option", { value: "jpeg", children: "JPEG (lossy, no alpha)" }), jsx("option", { value: "exr", children: "EXR (HDR float)" })] })] }), jsxs("label", { children: ["Background:", jsxs("select", { value: config.backgroundMode, onChange: (e) => setConfig({ backgroundMode: e.target.value }), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: "transparent", children: t('renderExport.transparent', 'Transparent') }), jsx("option", { value: "color", children: t('renderExport.solidColor', 'Solid Color') }), jsx("option", { value: "scene", children: t('renderExport.sceneBackground', 'Scene Background') })] })] }), config.backgroundMode === 'color' && (jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: ["Color:", jsx("input", { type: "color", value: config.backgroundColor, onChange: (e) => setConfig({ backgroundColor: e.target.value }) })] })), jsxs("label", { children: ["Supersampling: ", config.samples, "x", jsxs("select", { value: config.samples, onChange: (e) => setConfig({ samples: parseInt(e.target.value, 10) }), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: 1, children: "1x (none)" }), jsx("option", { value: 2, children: "2x" }), jsx("option", { value: 4, children: "4x (best quality)" })] })] }), jsx("button", { type: "button", onClick: handleRender, disabled: isRendering, style: {
|
|
326
329
|
background: isRendering
|
|
327
330
|
? 'var(--nice-bg-tertiary, #94a3b8)'
|
|
328
331
|
: 'var(--nice-color-accent, #6366f1)',
|
|
329
|
-
color: 'var(--nice-
|
|
332
|
+
color: 'var(--nice-color-white, #fff)',
|
|
330
333
|
border: 'none',
|
|
331
334
|
borderRadius: 4,
|
|
332
335
|
padding: '8px 12px',
|
|
333
336
|
cursor: isRendering ? 'wait' : 'pointer',
|
|
334
|
-
}, children: isRendering ? (jsxs(Fragment, { children: [jsx(NiceIcon, { name: "clock", size: 14 }), " Rendering\u2026"] })) : (jsxs(Fragment, { children: [jsx(NiceIcon, { name: "camera", size: 14 }),
|
|
337
|
+
}, children: isRendering ? (jsxs(Fragment, { children: [jsx(NiceIcon, { name: "clock", size: 14 }), " Rendering\u2026"] })) : (jsxs(Fragment, { children: [jsx(NiceIcon, { name: "camera", size: 14 }), ' ', t('renderExport.render', 'Render {width}×{height} {format}')
|
|
338
|
+
.replace('{width}', String(config.width))
|
|
339
|
+
.replace('{height}', String(config.height))
|
|
340
|
+
.replace('{format}', config.format.toUpperCase())] })) })] }));
|
|
335
341
|
};
|
|
336
342
|
|
|
337
343
|
export { NiceRenderExport, useRenderExport };
|
|
@@ -2,6 +2,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { NiceIcon } from '@nice2dev/ui';
|
|
3
3
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
5
6
|
|
|
6
7
|
/* ── SSAO Shaders ── */
|
|
7
8
|
const SSAO_VERTEX = `
|
|
@@ -336,8 +337,9 @@ function useSSAO(renderer, scene, camera, configOverride) {
|
|
|
336
337
|
}
|
|
337
338
|
/* ── Component ── */
|
|
338
339
|
const NiceSSAO = ({ renderer, scene, camera, config: configProp, className = '', }) => {
|
|
340
|
+
const { t } = useNiceTranslation();
|
|
339
341
|
const { config, setConfig } = useSSAO(renderer, scene, camera, configProp);
|
|
340
|
-
return (jsxs("div", { className: `nice-ssao ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "circle", size: 14 }), " Ambient Occlusion
|
|
342
|
+
return (jsxs("div", { className: `nice-ssao ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "circle", size: 14 }), " ", t('ssao.ambientOcclusion', 'Ambient Occlusion')] }), jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 11 }, children: [jsx("input", { type: "checkbox", checked: config.enabled, onChange: (e) => setConfig({ enabled: e.target.checked }) }), t('ssao.enabled', 'Enabled')] })] }), jsxs("label", { children: ["Mode:", jsxs("select", { value: config.mode, onChange: (e) => setConfig({ mode: e.target.value }), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: "ssao", children: t('ssao.ssao', 'SSAO') }), jsx("option", { value: "hbao", children: t('ssao.hbao', 'HBAO') })] })] }), jsxs("label", { children: ["Samples: ", config.samples, jsx("input", { type: "range", min: 8, max: 128, step: 8, value: config.samples, onChange: (e) => setConfig({ samples: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Radius: ", config.radius.toFixed(2), jsx("input", { type: "range", min: 0.1, max: 5, step: 0.1, value: config.radius, onChange: (e) => setConfig({ radius: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Intensity: ", config.intensity.toFixed(2), jsx("input", { type: "range", min: 0, max: 3, step: 0.1, value: config.intensity, onChange: (e) => setConfig({ intensity: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Bias: ", config.bias.toFixed(3), jsx("input", { type: "range", min: 0.001, max: 0.1, step: 0.001, value: config.bias, onChange: (e) => setConfig({ bias: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Resolution: ", config.resolution.toFixed(2), jsx("input", { type: "range", min: 0.25, max: 1, step: 0.25, value: config.resolution, onChange: (e) => setConfig({ resolution: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsx("input", { type: "checkbox", checked: config.blurEnabled, onChange: (e) => setConfig({ blurEnabled: e.target.checked }) }), "Bilateral Blur (size: ", config.blurSize, ")"] })] }));
|
|
341
343
|
};
|
|
342
344
|
|
|
343
345
|
export { NiceSSAO, useSSAO };
|
|
@@ -2,6 +2,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { NiceIcon } from '@nice2dev/ui';
|
|
3
3
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
5
6
|
|
|
6
7
|
/* ── SSR Shader ── */
|
|
7
8
|
const SSR_VERTEX = `
|
|
@@ -253,8 +254,9 @@ function useSSR(renderer, scene, camera, configOverride) {
|
|
|
253
254
|
}
|
|
254
255
|
/* ── Component ── */
|
|
255
256
|
const NiceSSR = ({ renderer, scene, camera, config: configProp, className = '', }) => {
|
|
257
|
+
const { t } = useNiceTranslation();
|
|
256
258
|
const { config, setConfig } = useSSR(renderer, scene, camera, configProp);
|
|
257
|
-
return (jsxs("div", { className: `nice-ssr ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "copy", size: 14 }),
|
|
259
|
+
return (jsxs("div", { className: `nice-ssr ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "copy", size: 14 }), ' ', t('ssr.screenSpaceReflections', 'Screen-Space Reflections')] }), jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 11 }, children: [jsx("input", { type: "checkbox", checked: config.enabled, onChange: (e) => setConfig({ enabled: e.target.checked }) }), t('ssr.enabled', 'Enabled')] })] }), jsxs("label", { children: ["Max Steps: ", config.maxSteps, jsx("input", { type: "range", min: 16, max: 256, step: 16, value: config.maxSteps, onChange: (e) => setConfig({ maxSteps: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Max Distance: ", config.maxDistance, jsx("input", { type: "range", min: 5, max: 200, step: 5, value: config.maxDistance, onChange: (e) => setConfig({ maxDistance: parseInt(e.target.value, 10) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Intensity: ", config.intensity.toFixed(2), jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: config.intensity, onChange: (e) => setConfig({ intensity: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Thickness: ", config.thickness.toFixed(2), jsx("input", { type: "range", min: 0.05, max: 2, step: 0.05, value: config.thickness, onChange: (e) => setConfig({ thickness: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Jitter: ", config.jitter.toFixed(2), jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: config.jitter, onChange: (e) => setConfig({ jitter: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Resolution Scale: ", config.resolution.toFixed(2), jsx("input", { type: "range", min: 0.25, max: 1, step: 0.25, value: config.resolution, onChange: (e) => setConfig({ resolution: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxs("label", { children: ["Fresnel: ", config.fresnelExponent.toFixed(1), jsx("input", { type: "range", min: 1, max: 8, step: 0.5, value: config.fresnelExponent, onChange: (e) => setConfig({ fresnelExponent: parseFloat(e.target.value) }), style: { width: '100%' } })] })] }));
|
|
258
260
|
};
|
|
259
261
|
|
|
260
262
|
export { NiceSSR, useSSR };
|
|
@@ -2,6 +2,8 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import { NiceIcon } from '@nice2dev/ui';
|
|
3
3
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
6
|
+
import { SOFT_SHADOW_MAP } from '../core/threeCompat.js';
|
|
5
7
|
|
|
6
8
|
/* ── WebGPU detection ── */
|
|
7
9
|
async function detectWebGPU() {
|
|
@@ -116,7 +118,7 @@ function useWebGPURenderer(canvas, scene, camera, width = 800, height = 600, onR
|
|
|
116
118
|
r.toneMappingExposure = toneMappingExposure;
|
|
117
119
|
r.outputColorSpace = outputColorSpace;
|
|
118
120
|
r.shadowMap.enabled = true;
|
|
119
|
-
r.shadowMap.type =
|
|
121
|
+
r.shadowMap.type = SOFT_SHADOW_MAP;
|
|
120
122
|
rendererRef.current = r;
|
|
121
123
|
setRenderer(r);
|
|
122
124
|
setIsWebGPU(usingWebGPU);
|
|
@@ -208,14 +210,15 @@ const TONE_MAPPINGS = [
|
|
|
208
210
|
];
|
|
209
211
|
/* ── Component ── */
|
|
210
212
|
const NiceWebGPURenderer = ({ canvas, scene, camera, width, height, onRendererReady, className = '', }) => {
|
|
213
|
+
const { t } = useNiceTranslation();
|
|
211
214
|
const { isWebGPU, capabilities, isReady, toneMapping, setToneMapping, toneMappingExposure, setToneMappingExposure, outputColorSpace, setOutputColorSpace, pixelRatio, setPixelRatio, } = useWebGPURenderer(canvas, scene, camera, width, height, onRendererReady);
|
|
212
|
-
return (jsxs("div", { className: `nice-webgpu-renderer ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "monitor", size: 14 }), " Renderer
|
|
215
|
+
return (jsxs("div", { className: `nice-webgpu-renderer ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "monitor", size: 14 }), " ", t('webGPURenderer.renderer', 'Renderer')] }), jsx("span", { style: {
|
|
213
216
|
fontSize: 10,
|
|
214
217
|
padding: '2px 6px',
|
|
215
218
|
borderRadius: 4,
|
|
216
219
|
background: isWebGPU ? 'var(--nice-success, #22c55e)' : 'var(--nice-warning, #f59e0b)',
|
|
217
|
-
color: 'var(--nice-
|
|
218
|
-
}, children: isWebGPU ? 'WebGPU' : 'WebGL' })] }), !isReady && jsx("div", { style: { opacity: 0.6, fontSize: 11 }, children: "Initializing\u2026" }), capabilities && (jsxs("div", { style: { fontSize: 10, opacity: 0.7, lineHeight: 1.5 }, children: ["GPU: ", capabilities.adapterName, jsx("br", {}), "Vendor: ", capabilities.vendor, jsx("br", {}), "Max Texture: ", capabilities.maxTextureDimension2D, "px", capabilities.features.length > 0 && (jsxs(Fragment, { children: [jsx("br", {}), "Features: ", capabilities.features.length] }))] })), jsxs("label", { children: ["Tone Mapping:", jsx("select", { value: toneMapping, onChange: (e) => setToneMapping(parseInt(e.target.value, 10)), style: { width: '100%', marginTop: 2 }, children: TONE_MAPPINGS.map((tm) => (jsx("option", { value: tm.value, children: tm.label }, tm.value))) })] }), jsxs("label", { children: ["Exposure: ", toneMappingExposure.toFixed(2), jsx("input", { type: "range", min: 0.1, max: 3, step: 0.05, value: toneMappingExposure, onChange: (e) => setToneMappingExposure(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxs("label", { children: ["Output Color Space:", jsxs("select", { value: outputColorSpace, onChange: (e) => setOutputColorSpace(e.target.value), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: THREE.SRGBColorSpace, children: "sRGB" }), jsx("option", { value: THREE.LinearSRGBColorSpace, children:
|
|
220
|
+
color: 'var(--nice-color-white, #fff)',
|
|
221
|
+
}, children: isWebGPU ? 'WebGPU' : 'WebGL' })] }), !isReady && jsx("div", { style: { opacity: 0.6, fontSize: 11 }, children: "Initializing\u2026" }), capabilities && (jsxs("div", { style: { fontSize: 10, opacity: 0.7, lineHeight: 1.5 }, children: ["GPU: ", capabilities.adapterName, jsx("br", {}), "Vendor: ", capabilities.vendor, jsx("br", {}), "Max Texture: ", capabilities.maxTextureDimension2D, "px", capabilities.features.length > 0 && (jsxs(Fragment, { children: [jsx("br", {}), "Features: ", capabilities.features.length] }))] })), jsxs("label", { children: ["Tone Mapping:", jsx("select", { value: toneMapping, onChange: (e) => setToneMapping(parseInt(e.target.value, 10)), style: { width: '100%', marginTop: 2 }, children: TONE_MAPPINGS.map((tm) => (jsx("option", { value: tm.value, children: tm.label }, tm.value))) })] }), jsxs("label", { children: ["Exposure: ", toneMappingExposure.toFixed(2), jsx("input", { type: "range", min: 0.1, max: 3, step: 0.05, value: toneMappingExposure, onChange: (e) => setToneMappingExposure(parseFloat(e.target.value)), style: { width: '100%' } })] }), jsxs("label", { children: ["Output Color Space:", jsxs("select", { value: outputColorSpace, onChange: (e) => setOutputColorSpace(e.target.value), style: { width: '100%', marginTop: 2 }, children: [jsx("option", { value: THREE.SRGBColorSpace, children: "sRGB" }), jsx("option", { value: THREE.LinearSRGBColorSpace, children: t('webGPURenderer.linearSrgb', 'Linear sRGB') })] })] }), jsxs("label", { children: ["Pixel Ratio: ", pixelRatio.toFixed(1), jsx("input", { type: "range", min: 0.5, max: 3, step: 0.5, value: pixelRatio, onChange: (e) => setPixelRatio(parseFloat(e.target.value)), style: { width: '100%' } })] })] }));
|
|
219
222
|
};
|
|
220
223
|
|
|
221
224
|
export { NiceWebGPURenderer, useWebGPURenderer };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { resolveCssColor, NiceIcon, NiceTutorialButton, resolveTutorial, NiceAppShell } from '@nice2dev/ui';
|
|
3
3
|
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
import styles from './UVEditor.module.css.js';
|
|
5
6
|
import { DEFAULT_UV_STATE, DEFAULT_RELAX_OPTIONS, UV_TOOLS, UV_SELECTION_MODES, PROJECTION_TYPES, UV_CHANNELS, DEFAULT_PACKING_OPTIONS } from './uvEditorTypes.js';
|
|
6
7
|
import { extractUVMeshData, uvSub, uvAdd, uvRotate, selectVerticesInRect, syncToGeometry, packIslands, applyIslandTransforms, relaxUVs, applyProjection, getSelectedVertices } from './uvEditorUtils.js';
|
|
@@ -20,6 +21,7 @@ const UV_EDITOR_TOUR = [
|
|
|
20
21
|
Component
|
|
21
22
|
═══════════════════════════════════════════ */
|
|
22
23
|
const NiceUVEditor = ({ geometry, texture, channel = 'uv', onUVChange, onSelectionChange, width = '100%', height = 600, showToolbar = true, showSidebar = true, className, readOnly = false, tutorial, }) => {
|
|
24
|
+
const { t } = useNiceTranslation();
|
|
23
25
|
const canvasRef = useRef(null);
|
|
24
26
|
const containerRef = useRef(null);
|
|
25
27
|
// State
|
|
@@ -122,12 +124,14 @@ const NiceUVEditor = ({ geometry, texture, channel = 'uv', onUVChange, onSelecti
|
|
|
122
124
|
ctx.closePath();
|
|
123
125
|
ctx.stroke();
|
|
124
126
|
}
|
|
125
|
-
// Draw faces
|
|
127
|
+
// Draw faces — the two fills resolved once per frame, not once per face
|
|
128
|
+
const faceSelectedFill = resolveCssColor('var(--nice-warning-tint-30, rgba(255, 170, 0, 0.3))', ctx.canvas);
|
|
129
|
+
const faceFill = resolveCssColor('var(--nice-overlay-10, rgba(100, 100, 100, 0.1))', ctx.canvas);
|
|
126
130
|
for (const face of meshData.faces.values()) {
|
|
127
131
|
const verts = face.vertices.map((id) => meshData.vertices.get(id));
|
|
128
132
|
const screenVerts = verts.map((v) => toScreen(v.position));
|
|
129
133
|
// Fill
|
|
130
|
-
ctx.fillStyle = face.selected ?
|
|
134
|
+
ctx.fillStyle = face.selected ? faceSelectedFill : faceFill;
|
|
131
135
|
ctx.beginPath();
|
|
132
136
|
ctx.moveTo(screenVerts[0].x, screenVerts[0].y);
|
|
133
137
|
screenVerts.slice(1).forEach((sv) => ctx.lineTo(sv.x, sv.y));
|
|
@@ -528,33 +532,33 @@ const NiceUVEditor = ({ geometry, texture, channel = 'uv', onUVChange, onSelecti
|
|
|
528
532
|
setMeshData(newData);
|
|
529
533
|
onUVChange === null || onUVChange === void 0 ? void 0 : onUVChange(geometry);
|
|
530
534
|
}, [meshData, geometry, state.activeChannel, readOnly, pushUndo, onUVChange]);
|
|
531
|
-
const toolbarSlot = showToolbar ? (jsxs("div", { className: styles.toolbar, children: [jsx("div", { className: styles.toolGroup, children: UV_TOOLS.slice(0, 5).map((tool) => (jsx("button", { className: `${styles.toolBtn} ${state.activeTool === tool ? styles.active : ''}`, onClick: () => setState((prev) => ({ ...prev, activeTool: tool })), title: tool, disabled: readOnly, children: tool[0].toUpperCase() }, tool))) }), jsx("div", { className: styles.toolGroup, children: UV_SELECTION_MODES.map((mode) => (jsx("button", { className: `${styles.toolBtn} ${state.selectionMode === mode ? styles.active : ''}`, onClick: () => setState((prev) => ({ ...prev, selectionMode: mode })), title: mode, children: mode[0].toUpperCase() }, mode))) }), jsxs("div", { className: styles.toolGroup, children: [jsx("button", { className: styles.toolBtn, onClick: handlePack, disabled: readOnly || !meshData, title:
|
|
535
|
+
const toolbarSlot = showToolbar ? (jsxs("div", { className: styles.toolbar, children: [jsx("div", { className: styles.toolGroup, children: UV_TOOLS.slice(0, 5).map((tool) => (jsx("button", { className: `${styles.toolBtn} ${state.activeTool === tool ? styles.active : ''}`, onClick: () => setState((prev) => ({ ...prev, activeTool: tool })), title: tool, disabled: readOnly, children: tool[0].toUpperCase() }, tool))) }), jsx("div", { className: styles.toolGroup, children: UV_SELECTION_MODES.map((mode) => (jsx("button", { className: `${styles.toolBtn} ${state.selectionMode === mode ? styles.active : ''}`, onClick: () => setState((prev) => ({ ...prev, selectionMode: mode })), title: mode, children: mode[0].toUpperCase() }, mode))) }), jsxs("div", { className: styles.toolGroup, children: [jsx("button", { className: styles.toolBtn, onClick: handlePack, disabled: readOnly || !meshData, title: t('uvEditor.packIslandsP', 'Pack Islands (P)'), children: t('uvEditor.pack', 'Pack') }), jsx("button", { className: styles.toolBtn, onClick: handleRelax, disabled: readOnly || !meshData, title: t('uvEditor.relaxUvs', 'Relax UVs'), children: t('uvEditor.relax', 'Relax') })] }), jsx("div", { className: styles.toolGroup, children: jsxs("select", { className: styles.select, onChange: (e) => handleProject(e.target.value), disabled: readOnly || !meshData, defaultValue: "", children: [jsx("option", { value: "", disabled: true, children: t('uvEditor.project', 'Project...') }), PROJECTION_TYPES.map((type) => (jsx("option", { value: type, children: type }, type)))] }) }), jsx("div", { className: styles.toolGroup, children: jsx("select", { className: styles.select, value: state.activeChannel, onChange: (e) => setState((prev) => ({
|
|
532
536
|
...prev,
|
|
533
537
|
activeChannel: e.target.value,
|
|
534
|
-
})), children: UV_CHANNELS.map((ch) => (jsx("option", { value: ch, children: ch.toUpperCase() }, ch))) }) }), jsxs("div", { className: styles.toolGroup, children: [jsx("button", { className: styles.toolBtn, onClick: undo, disabled: undoStack.length === 0, title:
|
|
535
|
-
const sidebarSlot = showSidebar && meshData ? (jsxs("div", { className: styles.sidebar, children: [jsxs("div", { className: styles.panel, children: [jsx("h4", { className: styles.panelTitle, children:
|
|
538
|
+
})), children: UV_CHANNELS.map((ch) => (jsx("option", { value: ch, children: ch.toUpperCase() }, ch))) }) }), jsxs("div", { className: styles.toolGroup, children: [jsx("button", { className: styles.toolBtn, onClick: undo, disabled: undoStack.length === 0, title: t('uvEditor.undoCtrlZ', 'Undo (Ctrl+Z)'), "aria-label": t('uvEditor.undo', 'Undo'), children: jsx(NiceIcon, { name: "arrow-left", size: 16 }) }), jsx("button", { className: styles.toolBtn, onClick: redo, disabled: redoStack.length === 0, title: t('uvEditor.redoCtrlY', 'Redo (Ctrl+Y)'), "aria-label": t('uvEditor.redo', 'Redo'), children: jsx(NiceIcon, { name: "arrow-right", size: 16 }) })] })] })) : undefined;
|
|
539
|
+
const sidebarSlot = showSidebar && meshData ? (jsxs("div", { className: styles.sidebar, children: [jsxs("div", { className: styles.panel, children: [jsx("h4", { className: styles.panelTitle, children: t('uvEditor.display', 'Display') }), jsxs("label", { className: styles.checkbox, children: [jsx("input", { type: "checkbox", checked: state.display.showGrid, onChange: (e) => setState((prev) => ({
|
|
536
540
|
...prev,
|
|
537
541
|
display: { ...prev.display, showGrid: e.target.checked },
|
|
538
|
-
})) }),
|
|
542
|
+
})) }), t('uvEditor.showGrid', 'Show Grid')] }), jsxs("label", { className: styles.checkbox, children: [jsx("input", { type: "checkbox", checked: state.display.showTexture, onChange: (e) => setState((prev) => ({
|
|
539
543
|
...prev,
|
|
540
544
|
display: { ...prev.display, showTexture: e.target.checked },
|
|
541
|
-
})) }),
|
|
545
|
+
})) }), t('uvEditor.showTexture', 'Show Texture')] }), jsxs("label", { className: styles.checkbox, children: [jsx("input", { type: "checkbox", checked: state.display.showSeams, onChange: (e) => setState((prev) => ({
|
|
542
546
|
...prev,
|
|
543
547
|
display: { ...prev.display, showSeams: e.target.checked },
|
|
544
|
-
})) }),
|
|
548
|
+
})) }), t('uvEditor.showSeams', 'Show Seams')] }), jsxs("label", { className: styles.checkbox, children: [jsx("input", { type: "checkbox", checked: state.display.showDistortion, onChange: (e) => setState((prev) => ({
|
|
545
549
|
...prev,
|
|
546
550
|
display: { ...prev.display, showDistortion: e.target.checked },
|
|
547
|
-
})) }),
|
|
551
|
+
})) }), t('uvEditor.showDistortion', 'Show Distortion')] }), jsxs("label", { className: styles.checkbox, children: [jsx("input", { type: "checkbox", checked: state.display.showIslandBounds, onChange: (e) => setState((prev) => ({
|
|
548
552
|
...prev,
|
|
549
553
|
display: { ...prev.display, showIslandBounds: e.target.checked },
|
|
550
|
-
})) }),
|
|
554
|
+
})) }), t('uvEditor.showIslandBounds', 'Show Island Bounds')] })] }), jsxs("div", { className: styles.panel, children: [jsx("h4", { className: styles.panelTitle, children: t('uvEditor.statistics', 'Statistics') }), jsxs("div", { className: styles.stat, children: [jsx("span", { children: "Vertices:" }), jsx("span", { children: meshData.vertices.size })] }), jsxs("div", { className: styles.stat, children: [jsx("span", { children: "Faces:" }), jsx("span", { children: meshData.faces.size })] }), jsxs("div", { className: styles.stat, children: [jsx("span", { children: "Islands:" }), jsx("span", { children: meshData.islands.size })] }), jsxs("div", { className: styles.stat, children: [jsx("span", { children: "Selected:" }), jsx("span", { children: getSelectedVertices(meshData).length })] })] }), jsxs("div", { className: styles.panel, children: [jsx("h4", { className: styles.panelTitle, children: t('uvEditor.snapping', 'Snapping') }), jsxs("label", { className: styles.checkbox, children: [jsx("input", { type: "checkbox", checked: state.snapping, onChange: (e) => setState((prev) => ({
|
|
551
555
|
...prev,
|
|
552
556
|
snapping: e.target.checked,
|
|
553
|
-
})) }),
|
|
557
|
+
})) }), t('uvEditor.enableSnapping', 'Enable Snapping')] }), jsxs("label", { className: styles.rangeLabel, children: ["Snap Size: ", state.snapSize.toFixed(4), jsx("input", { type: "range", min: "0.001", max: "0.25", step: "0.001", value: state.snapSize, onChange: (e) => setState((prev) => ({
|
|
554
558
|
...prev,
|
|
555
559
|
snapSize: parseFloat(e.target.value),
|
|
556
560
|
})) })] })] })] })) : undefined;
|
|
557
|
-
return (jsxs("div", { style: { position: 'relative', width, height }, children: [jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 30 }, children: jsx(NiceTutorialButton, { steps: resolveTutorial(tutorial, UV_EDITOR_TOUR) }) }), jsx(NiceAppShell, { className: `nice-uv-editor ${styles.uvEditor} ${className !== null && className !== void 0 ? className : ''}`, flush: true, toolbar: toolbarSlot, rightSidebar: sidebarSlot, statusBar: jsxs("div", { className: styles.statusBar, children: [jsxs("span", { children: ["Tool: ", state.activeTool] }), jsxs("span", { children: ["Mode: ", state.selectionMode] }), jsxs("span", { children: ["Zoom: ", (state.zoom * 100).toFixed(0), "%"] }), jsxs("span", { children: ["Channel: ", state.activeChannel] })] }), children: jsxs("div", { ref: containerRef, className: styles.viewport, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onWheel: handleWheel, children: [jsx("canvas", { ref: canvasRef, className: styles.canvas }), !meshData && jsx("div", { className: styles.noGeometry, children:
|
|
561
|
+
return (jsxs("div", { style: { position: 'relative', width, height }, children: [jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 30 }, children: jsx(NiceTutorialButton, { steps: resolveTutorial(tutorial, UV_EDITOR_TOUR) }) }), jsx(NiceAppShell, { className: `nice-uv-editor ${styles.uvEditor} ${className !== null && className !== void 0 ? className : ''}`, flush: true, toolbar: toolbarSlot, rightSidebar: sidebarSlot, statusBar: jsxs("div", { className: styles.statusBar, children: [jsxs("span", { children: ["Tool: ", state.activeTool] }), jsxs("span", { children: ["Mode: ", state.selectionMode] }), jsxs("span", { children: ["Zoom: ", (state.zoom * 100).toFixed(0), "%"] }), jsxs("span", { children: ["Channel: ", state.activeChannel] })] }), children: jsxs("div", { ref: containerRef, className: styles.viewport, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onWheel: handleWheel, children: [jsx("canvas", { ref: canvasRef, className: styles.canvas }), !meshData && (jsx("div", { className: styles.noGeometry, children: t('uvEditor.noGeometrySelected', 'No geometry selected') }))] }) })] }));
|
|
558
562
|
};
|
|
559
563
|
|
|
560
564
|
export { NiceUVEditor };
|
|
@@ -48,6 +48,7 @@ const DEFAULT_UV_DISPLAY = {
|
|
|
48
48
|
showDistortion: false,
|
|
49
49
|
distortionGradient: { low: 'var(--nice-success, #00ff00)', high: 'var(--nice-danger, #ff0000)' },
|
|
50
50
|
showSeams: true,
|
|
51
|
+
// audit-allow: literal-color — the seam colour setting's default
|
|
51
52
|
seamColor: '#ff6600',
|
|
52
53
|
highlightSelected: true,
|
|
53
54
|
selectionColor: 'var(--nice-warning, #ffaa00)',
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/** A question an editor hook waits on; {@link EditorAskDialog} shows it. */
|
|
3
|
+
export interface EditorAsk {
|
|
4
|
+
/** `prompt` asks for a value, `confirm` for a yes / no. */
|
|
5
|
+
kind: 'prompt' | 'confirm';
|
|
6
|
+
/** The prompt's title, or the question to confirm. */
|
|
7
|
+
text: string;
|
|
8
|
+
/** Initial value of a prompt. */
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
/** Settles it: the entered value (`''` for an accepted confirm), or `null` when cancelled. */
|
|
11
|
+
answer: (value: string | null) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The library's dialogs in place of `window.prompt` / `window.confirm` for the editor hooks,
|
|
15
|
+
* which hold state but render nothing: `ask` resolves with the answer (`null` when cancelled)
|
|
16
|
+
* and `pendingAsk` is the open question, for the editor component to pass to
|
|
17
|
+
* {@link EditorAskDialog}. One question at a time: a new one settles the open one as cancelled.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useEditorAsk(): {
|
|
20
|
+
pendingAsk: EditorAsk | null;
|
|
21
|
+
ask: (kind: EditorAsk["kind"], text: string, defaultValue?: string) => Promise<string | null>;
|
|
22
|
+
};
|
|
23
|
+
/** Shows the question {@link useEditorAsk} is waiting on, if any. */
|
|
24
|
+
export declare const EditorAskDialog: React.FC<{
|
|
25
|
+
ask: EditorAsk | null;
|
|
26
|
+
}>;
|
|
@@ -1,26 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* i18n — Lightweight translation context for @nice2dev/ui-3d.
|
|
3
|
-
*
|
|
4
|
-
* Drop-in replacement for react-i18next's useTranslation, requires NO
|
|
5
|
-
* external dependencies. Consumers can optionally wrap with NiceI18nProvider
|
|
6
|
-
* to supply their own translation function. Falls back to the default
|
|
7
|
-
* (English key strings) when no provider is present.
|
|
8
|
-
*
|
|
9
|
-
* Usage in components:
|
|
10
|
-
* const { t } = useNiceTranslation();
|
|
11
|
-
* <button>{t('actions.save', 'Save')}</button>
|
|
12
|
-
*
|
|
13
|
-
* Usage in apps:
|
|
14
|
-
* <NiceI18nProvider t={(key, fallback) => myI18n(key) ?? fallback}>
|
|
15
|
-
* <App />
|
|
16
|
-
* </NiceI18nProvider>
|
|
17
|
-
*/
|
|
18
1
|
import React from 'react';
|
|
19
2
|
export type NiceTranslateFn = (key: string, defaultValue: string) => string;
|
|
20
3
|
export interface NiceI18nProviderProps {
|
|
21
4
|
t: NiceTranslateFn;
|
|
22
5
|
children: React.ReactNode;
|
|
23
6
|
}
|
|
7
|
+
/** Overrides the app's global translator for the 3D controls below it. */
|
|
24
8
|
export declare const NiceI18nProvider: React.FC<NiceI18nProviderProps>;
|
|
25
9
|
export declare function useNiceTranslation(): {
|
|
26
10
|
t: NiceTranslateFn;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* three.js r183 deprecated `Clock` (for `Timer`) and folds `PCFSoftShadowMap` into
|
|
3
|
+
* `PCFShadowMap`; both log a console warning there. The peer range starts at r150, which has
|
|
4
|
+
* neither `Timer` in its core nor the softer `PCFShadowMap`, so the choice follows the revision.
|
|
5
|
+
*/
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
/** Soft shadows without the r183 deprecation warning; older three keeps `PCFSoftShadowMap`. */
|
|
8
|
+
export declare const SOFT_SHADOW_MAP: THREE.ShadowMapType;
|
|
9
|
+
/** Seconds since the previous call — what an animation loop feeds its mixer. */
|
|
10
|
+
export interface FrameClock {
|
|
11
|
+
getDelta(): number;
|
|
12
|
+
}
|
|
13
|
+
/** `Timer` where three has deprecated `Clock`, `Clock` before. */
|
|
14
|
+
export declare function createFrameClock(): FrameClock;
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DanceScoreEngine — Compares detected poses against reference choreography
|
|
3
|
-
*
|
|
4
|
-
* Calculates real-time dance accuracy by comparing camera-detected Pose3DFrames
|
|
5
|
-
* against a reference sequence (from choreography DSL or recorded poses).
|
|
6
|
-
*/
|
|
7
1
|
import type { Pose3DFrame, Pose3DSequenceResult } from '../model/modelEditorTypes';
|
|
8
2
|
export interface DanceScoreConfig {
|
|
9
3
|
/** Weight per keypoint category (0–1). Keys not listed default to 1. */
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { Pose3DFrame } from '../model/modelEditorTypes';
|
|
2
|
+
import type { PosePerson3D, Pose3DFrame } from '../model/modelEditorTypes';
|
|
3
3
|
export type PoseBackend = 'mediapipe' | 'tfjs';
|
|
4
|
+
/**
|
|
5
|
+
* Finds the people in one video frame; the model is yours — TF.js pose-detection (MoveNet,
|
|
6
|
+
* BlazePose), MediaPipe Pose Landmarker… Return keypoints in video pixels, named as in COCO-17
|
|
7
|
+
* (`nose`, `left_shoulder`, `right_ankle`, …): the overlay connects them by name. It may be
|
|
8
|
+
* async; the next detection waits for it.
|
|
9
|
+
*/
|
|
10
|
+
export type NicePoseDetectorFn = (video: HTMLVideoElement, timestampMs: number) => PosePerson3D[] | Promise<PosePerson3D[]>;
|
|
4
11
|
export interface NicePoseDetectorConfig {
|
|
5
|
-
/**
|
|
12
|
+
/** Finds the poses in each frame. Without it the camera runs, but nothing is detected. */
|
|
13
|
+
detector?: NicePoseDetectorFn;
|
|
14
|
+
/** Which backend your `detector` uses — informational; the component loads no model. */
|
|
6
15
|
backend?: PoseBackend;
|
|
7
16
|
/** Maximum number of persons to track */
|
|
8
17
|
maxPersons?: number;
|
|
@@ -17,8 +26,6 @@ export interface NicePoseDetectorConfig {
|
|
|
17
26
|
/** Show confidence dots on keypoints */
|
|
18
27
|
showConfidence?: boolean;
|
|
19
28
|
}
|
|
20
|
-
/** @deprecated Use NicePoseDetectorConfig */
|
|
21
|
-
export type PoseDetectorConfig = NicePoseDetectorConfig;
|
|
22
29
|
export interface NicePoseDetectorHandle {
|
|
23
30
|
/** Start detection */
|
|
24
31
|
start(): void;
|
|
@@ -29,8 +36,6 @@ export interface NicePoseDetectorHandle {
|
|
|
29
36
|
/** Check if actively detecting */
|
|
30
37
|
readonly isDetecting: boolean;
|
|
31
38
|
}
|
|
32
|
-
/** @deprecated Use NicePoseDetectorHandle */
|
|
33
|
-
export type PoseDetectorHandle = NicePoseDetectorHandle;
|
|
34
39
|
export interface NicePoseDetectorProps extends NicePoseDetectorConfig {
|
|
35
40
|
/** Called each frame with detected poses */
|
|
36
41
|
onFrame?: (frame: Pose3DFrame) => void;
|
|
@@ -38,7 +43,7 @@ export interface NicePoseDetectorProps extends NicePoseDetectorConfig {
|
|
|
38
43
|
onStart?: () => void;
|
|
39
44
|
/** Called when detection stops */
|
|
40
45
|
onStop?: () => void;
|
|
41
|
-
/** Called
|
|
46
|
+
/** Called when the camera cannot start or the detector fails (detection then stops) */
|
|
42
47
|
onError?: (error: Error) => void;
|
|
43
48
|
/** CSS class for the container */
|
|
44
49
|
className?: string;
|
|
@@ -49,14 +54,10 @@ export interface NicePoseDetectorProps extends NicePoseDetectorConfig {
|
|
|
49
54
|
/** Height of the video feed */
|
|
50
55
|
height?: number;
|
|
51
56
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
export declare function usePoseDetection(config?: PoseDetectorConfig): {
|
|
55
|
-
start: (video: HTMLVideoElement, onFrame?: (frame: Pose3DFrame) => void) => Promise<void>;
|
|
57
|
+
export declare function usePoseDetection(config?: NicePoseDetectorConfig): {
|
|
58
|
+
start: (video: HTMLVideoElement, onFrame?: (frame: Pose3DFrame) => void, onError?: (error: Error) => void) => Promise<void>;
|
|
56
59
|
stop: () => void;
|
|
57
60
|
isDetecting: boolean;
|
|
58
61
|
getLatestFrame: () => Pose3DFrame | null;
|
|
59
62
|
};
|
|
60
63
|
export declare const NicePoseDetector: React.ForwardRefExoticComponent<NicePoseDetectorProps & React.RefAttributes<NicePoseDetectorHandle>>;
|
|
61
|
-
/** @deprecated Use NicePoseDetector */
|
|
62
|
-
export declare const PoseDetector: React.ForwardRefExoticComponent<NicePoseDetectorProps & React.RefAttributes<NicePoseDetectorHandle>>;
|