@nice2dev/ui-3d 1.3.15 → 1.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/collaborative/collaborativeScene.js +160 -206
  3. package/dist/cjs/core/editorAsk.js +51 -41
  4. package/dist/cjs/core/i18n.js +13 -14
  5. package/dist/cjs/core/threeCompat.js +29 -42
  6. package/dist/cjs/dance/DanceBridge.js +148 -177
  7. package/dist/cjs/dance/DanceScoreEngine.js +209 -221
  8. package/dist/cjs/dance/PoseDetector.js +302 -275
  9. package/dist/cjs/index.js +110 -113
  10. package/dist/cjs/material/MaterialEditor.module.css.js +242 -5
  11. package/dist/cjs/material/NiceMaterialEditor.js +1213 -757
  12. package/dist/cjs/material/materialEditorTypes.js +35 -49
  13. package/dist/cjs/material/materialEditorUtils.js +684 -747
  14. package/dist/cjs/material/materialNodeDefinitions.js +1411 -1459
  15. package/dist/cjs/model/ModelEditor.js +90 -40
  16. package/dist/cjs/model/ModelEditor.module.css.js +257 -5
  17. package/dist/cjs/model/ModelEditorLeftPanel.js +126 -47
  18. package/dist/cjs/model/ModelEditorMenuBar.js +359 -32
  19. package/dist/cjs/model/ModelEditorRightPanel.js +647 -131
  20. package/dist/cjs/model/ModelEditorSubComponents.js +277 -112
  21. package/dist/cjs/model/ModelEditorTimeline.js +232 -34
  22. package/dist/cjs/model/ModelEditorToolbar.js +170 -14
  23. package/dist/cjs/model/ModelEditorViewport.js +231 -39
  24. package/dist/cjs/model/ModelViewer.js +388 -63
  25. package/dist/cjs/model/ModelViewer.module.css.js +77 -5
  26. package/dist/cjs/model/NiceArmatureEditor.js +473 -274
  27. package/dist/cjs/model/NiceMorphTargetEditor.js +333 -227
  28. package/dist/cjs/model/NiceOctree.js +372 -320
  29. package/dist/cjs/model/NicePhysicsSimulation.js +528 -298
  30. package/dist/cjs/model/NiceProceduralGeometry.js +673 -300
  31. package/dist/cjs/model/NiceTerrainEditor.js +595 -385
  32. package/dist/cjs/model/NiceWeightPainter.js +384 -261
  33. package/dist/cjs/model/NiceXRPreview.js +385 -308
  34. package/dist/cjs/model/cadModeUtils.js +103 -106
  35. package/dist/cjs/model/editorShortcuts.js +171 -194
  36. package/dist/cjs/model/modelEditorTypes.js +100 -118
  37. package/dist/cjs/model/modelEditorUtils.js +1061 -1146
  38. package/dist/cjs/model/simsModeUtils.js +1023 -1053
  39. package/dist/cjs/model/useModelEditor.js +1977 -1933
  40. package/dist/cjs/model/useModelViewer.js +633 -665
  41. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  42. package/dist/cjs/particle/NiceParticleEditor.js +1186 -571
  43. package/dist/cjs/particle/ParticleEditor.module.css.js +122 -5
  44. package/dist/cjs/particle/particleEditorTypes.js +62 -70
  45. package/dist/cjs/particle/particleEditorUtils.js +998 -1013
  46. package/dist/cjs/rendering/NiceCascadedShadows.js +381 -259
  47. package/dist/cjs/rendering/NiceRenderExport.js +424 -337
  48. package/dist/cjs/rendering/NiceSSAO.js +454 -337
  49. package/dist/cjs/rendering/NiceSSR.js +387 -261
  50. package/dist/cjs/rendering/NiceWebGPURenderer.js +359 -230
  51. package/dist/cjs/uv/NiceUVEditor.js +787 -550
  52. package/dist/cjs/uv/UVEditor.module.css.js +53 -5
  53. package/dist/cjs/uv/uvEditorTypes.js +59 -76
  54. package/dist/cjs/uv/uvEditorUtils.js +501 -575
  55. package/dist/esm/collaborative/collaborativeScene.js +163 -205
  56. package/dist/esm/core/editorAsk.js +53 -40
  57. package/dist/esm/core/i18n.js +15 -13
  58. package/dist/esm/core/threeCompat.js +18 -26
  59. package/dist/esm/dance/DanceBridge.js +151 -176
  60. package/dist/esm/dance/DanceScoreEngine.js +211 -220
  61. package/dist/esm/dance/PoseDetector.js +304 -274
  62. package/dist/esm/index.js +258 -36
  63. package/dist/esm/material/MaterialEditor.module.css.js +242 -3
  64. package/dist/esm/material/NiceMaterialEditor.js +1215 -756
  65. package/dist/esm/material/materialEditorTypes.js +45 -48
  66. package/dist/esm/material/materialEditorUtils.js +713 -746
  67. package/dist/esm/material/materialNodeDefinitions.js +1415 -1458
  68. package/dist/esm/model/ModelEditor.js +91 -39
  69. package/dist/esm/model/ModelEditor.module.css.js +257 -3
  70. package/dist/esm/model/ModelEditorLeftPanel.js +126 -45
  71. package/dist/esm/model/ModelEditorMenuBar.js +359 -30
  72. package/dist/esm/model/ModelEditorRightPanel.js +634 -114
  73. package/dist/esm/model/ModelEditorSubComponents.js +265 -94
  74. package/dist/esm/model/ModelEditorTimeline.js +232 -32
  75. package/dist/esm/model/ModelEditorToolbar.js +170 -12
  76. package/dist/esm/model/ModelEditorViewport.js +231 -37
  77. package/dist/esm/model/ModelViewer.js +389 -62
  78. package/dist/esm/model/ModelViewer.module.css.js +77 -3
  79. package/dist/esm/model/NiceArmatureEditor.js +462 -258
  80. package/dist/esm/model/NiceMorphTargetEditor.js +322 -211
  81. package/dist/esm/model/NiceOctree.js +361 -304
  82. package/dist/esm/model/NicePhysicsSimulation.js +517 -282
  83. package/dist/esm/model/NiceProceduralGeometry.js +667 -284
  84. package/dist/esm/model/NiceTerrainEditor.js +584 -369
  85. package/dist/esm/model/NiceWeightPainter.js +373 -245
  86. package/dist/esm/model/NiceXRPreview.js +374 -292
  87. package/dist/esm/model/cadModeUtils.js +95 -88
  88. package/dist/esm/model/editorShortcuts.js +172 -193
  89. package/dist/esm/model/modelEditorTypes.js +90 -100
  90. package/dist/esm/model/modelEditorUtils.js +1056 -1128
  91. package/dist/esm/model/simsModeUtils.js +1021 -1035
  92. package/dist/esm/model/useModelEditor.js +1946 -1920
  93. package/dist/esm/model/useModelViewer.js +619 -647
  94. package/dist/esm/nice2dev-ui-3d.css +1 -1
  95. package/dist/esm/particle/NiceParticleEditor.js +1188 -570
  96. package/dist/esm/particle/ParticleEditor.module.css.js +122 -3
  97. package/dist/esm/particle/particleEditorTypes.js +69 -69
  98. package/dist/esm/particle/particleEditorUtils.js +1027 -1012
  99. package/dist/esm/rendering/NiceCascadedShadows.js +370 -243
  100. package/dist/esm/rendering/NiceRenderExport.js +413 -321
  101. package/dist/esm/rendering/NiceSSAO.js +450 -328
  102. package/dist/esm/rendering/NiceSSR.js +383 -252
  103. package/dist/esm/rendering/NiceWebGPURenderer.js +326 -214
  104. package/dist/esm/uv/NiceUVEditor.js +788 -549
  105. package/dist/esm/uv/UVEditor.module.css.js +53 -3
  106. package/dist/esm/uv/uvEditorTypes.js +68 -75
  107. package/dist/esm/uv/uvEditorUtils.js +515 -557
  108. package/package.json +7 -12
@@ -1,291 +1,321 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { resolveCssColor } from '@nice2dev/ui';
3
- import { forwardRef, useRef, useImperativeHandle, useCallback, useEffect, useState } from 'react';
4
- import { useNiceTranslation } from '../core/i18n.js';
5
-
6
- // ── Standard keypoint definitions (COCO 17-keypoint model) ──────
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { resolveCssColor } from "@nice2dev/ui";
3
+ import { forwardRef, useRef, useImperativeHandle, useCallback, useEffect, useState } from "react";
4
+ import { useNiceTranslation } from "../core/i18n.js";
7
5
  const COCO_KEYPOINTS = [
8
- 'nose',
9
- 'left_eye',
10
- 'right_eye',
11
- 'left_ear',
12
- 'right_ear',
13
- 'left_shoulder',
14
- 'right_shoulder',
15
- 'left_elbow',
16
- 'right_elbow',
17
- 'left_wrist',
18
- 'right_wrist',
19
- 'left_hip',
20
- 'right_hip',
21
- 'left_knee',
22
- 'right_knee',
23
- 'left_ankle',
24
- 'right_ankle',
6
+ "nose",
7
+ "left_eye",
8
+ "right_eye",
9
+ "left_ear",
10
+ "right_ear",
11
+ "left_shoulder",
12
+ "right_shoulder",
13
+ "left_elbow",
14
+ "right_elbow",
15
+ "left_wrist",
16
+ "right_wrist",
17
+ "left_hip",
18
+ "right_hip",
19
+ "left_knee",
20
+ "right_knee",
21
+ "left_ankle",
22
+ "right_ankle"
25
23
  ];
26
24
  const SKELETON_CONNECTIONS = [
27
- [0, 1],
28
- [0, 2],
29
- [1, 3],
30
- [2, 4], // head
31
- [5, 6], // shoulders
32
- [5, 7],
33
- [7, 9], // left arm
34
- [6, 8],
35
- [8, 10], // right arm
36
- [5, 11],
37
- [6, 12], // torso
38
- [11, 12], // hips
39
- [11, 13],
40
- [13, 15], // left leg
41
- [12, 14],
42
- [14, 16], // right leg
25
+ [0, 1],
26
+ [0, 2],
27
+ [1, 3],
28
+ [2, 4],
29
+ // head
30
+ [5, 6],
31
+ // shoulders
32
+ [5, 7],
33
+ [7, 9],
34
+ // left arm
35
+ [6, 8],
36
+ [8, 10],
37
+ // right arm
38
+ [5, 11],
39
+ [6, 12],
40
+ // torso
41
+ [11, 12],
42
+ // hips
43
+ [11, 13],
44
+ [13, 15],
45
+ // left leg
46
+ [12, 14],
47
+ [14, 16]
48
+ // right leg
43
49
  ];
44
- /** A keypoint the detector reported without a score counts as found. */
45
- const confidenceOf = (kp) => { var _a; return (_a = kp.confidence) !== null && _a !== void 0 ? _a : 1; };
46
- // ── Hook: usePoseDetection ──────────────────────────────────────
50
+ const confidenceOf = (kp) => kp.confidence ?? 1;
47
51
  function usePoseDetection(config = {}) {
48
- // The detection loop outlives renders: it reads the latest settings from here.
49
- const configRef = useRef(config);
50
- useEffect(() => {
51
- configRef.current = config;
52
+ const configRef = useRef(config);
53
+ useEffect(() => {
54
+ configRef.current = config;
55
+ });
56
+ const [isDetecting, setIsDetecting] = useState(false);
57
+ const latestFrameRef = useRef(null);
58
+ const frameIndexRef = useRef(0);
59
+ const rafRef = useRef(0);
60
+ const streamRef = useRef(null);
61
+ const videoRef = useRef(null);
62
+ const callbackRef = useRef(null);
63
+ const errorRef = useRef(null);
64
+ const startedAtRef = useRef(0);
65
+ const lastRunRef = useRef(-Infinity);
66
+ const busyRef = useRef(false);
67
+ const runRef = useRef(0);
68
+ const stop = useCallback(() => {
69
+ runRef.current += 1;
70
+ cancelAnimationFrame(rafRef.current);
71
+ streamRef.current?.getTracks().forEach((t) => t.stop());
72
+ streamRef.current = null;
73
+ if (videoRef.current) {
74
+ videoRef.current.srcObject = null;
75
+ }
76
+ setIsDetecting(false);
77
+ }, []);
78
+ const processFrame = useCallback(() => {
79
+ rafRef.current = requestAnimationFrame(processFrame);
80
+ const video = videoRef.current;
81
+ const { detector, maxPersons = 1, minConfidence = 0.5, targetFps = 30 } = configRef.current;
82
+ if (!detector || !video || video.readyState < 2 || busyRef.current) {
83
+ return;
84
+ }
85
+ const now = performance.now();
86
+ if (now - lastRunRef.current < 1e3 / targetFps) {
87
+ return;
88
+ }
89
+ lastRunRef.current = now;
90
+ busyRef.current = true;
91
+ const run = runRef.current;
92
+ Promise.resolve().then(() => detector(video, now)).then((found) => {
93
+ if (run !== runRef.current) {
94
+ return;
95
+ }
96
+ const frame = {
97
+ frame_index: frameIndexRef.current++,
98
+ timestamp_sec: (now - startedAtRef.current) / 1e3,
99
+ persons: found.filter((person) => person.keypoints.some((kp) => confidenceOf(kp) >= minConfidence)).slice(0, maxPersons)
100
+ };
101
+ latestFrameRef.current = frame;
102
+ callbackRef.current?.(frame);
103
+ }).catch((err) => {
104
+ if (run !== runRef.current) {
105
+ return;
106
+ }
107
+ stop();
108
+ errorRef.current?.(err instanceof Error ? err : new Error(String(err)));
109
+ }).finally(() => {
110
+ busyRef.current = false;
52
111
  });
53
- const [isDetecting, setIsDetecting] = useState(false);
54
- const latestFrameRef = useRef(null);
55
- const frameIndexRef = useRef(0);
56
- const rafRef = useRef(0);
57
- const streamRef = useRef(null);
58
- const videoRef = useRef(null);
59
- const callbackRef = useRef(null);
60
- const errorRef = useRef(null);
61
- const startedAtRef = useRef(0);
62
- const lastRunRef = useRef(-Infinity);
63
- const busyRef = useRef(false);
64
- // Bumped by start() and stop(): a detection that resolves after them is dropped.
65
- const runRef = useRef(0);
66
- const stop = useCallback(() => {
67
- var _a;
112
+ }, [stop]);
113
+ const start = useCallback(
114
+ async (video, onFrame, onError) => {
115
+ try {
116
+ const stream = await navigator.mediaDevices.getUserMedia({
117
+ video: { facingMode: "user", width: { ideal: 640 }, height: { ideal: 480 } }
118
+ });
119
+ streamRef.current = stream;
120
+ videoRef.current = video;
121
+ video.srcObject = stream;
122
+ await video.play();
123
+ callbackRef.current = onFrame ?? null;
124
+ errorRef.current = onError ?? null;
125
+ frameIndexRef.current = 0;
126
+ startedAtRef.current = performance.now();
127
+ lastRunRef.current = -Infinity;
68
128
  runRef.current += 1;
69
- cancelAnimationFrame(rafRef.current);
70
- (_a = streamRef.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());
71
- streamRef.current = null;
72
- if (videoRef.current) {
73
- videoRef.current.srcObject = null;
129
+ if (!configRef.current.detector) {
130
+ console.warn(
131
+ "[NicePoseDetector] started without a `detector`: the camera runs, but no poses are detected."
132
+ );
74
133
  }
75
- setIsDetecting(false);
76
- }, []);
77
- const processFrame = useCallback(() => {
134
+ setIsDetecting(true);
78
135
  rafRef.current = requestAnimationFrame(processFrame);
79
- const video = videoRef.current;
80
- const { detector, maxPersons = 1, minConfidence = 0.5, targetFps = 30 } = configRef.current;
81
- if (!detector || !video || video.readyState < 2 || busyRef.current) {
82
- return;
83
- }
84
- const now = performance.now();
85
- if (now - lastRunRef.current < 1000 / targetFps) {
86
- return;
87
- }
88
- lastRunRef.current = now;
89
- busyRef.current = true;
90
- const run = runRef.current;
91
- Promise.resolve()
92
- .then(() => detector(video, now))
93
- .then((found) => {
94
- var _a;
95
- if (run !== runRef.current) {
96
- return;
97
- }
98
- const frame = {
99
- frame_index: frameIndexRef.current++,
100
- timestamp_sec: (now - startedAtRef.current) / 1000,
101
- persons: found
102
- .filter((person) => person.keypoints.some((kp) => confidenceOf(kp) >= minConfidence))
103
- .slice(0, maxPersons),
104
- };
105
- latestFrameRef.current = frame;
106
- (_a = callbackRef.current) === null || _a === void 0 ? void 0 : _a.call(callbackRef, frame);
107
- })
108
- .catch((err) => {
109
- var _a;
110
- if (run !== runRef.current) {
111
- return;
112
- }
113
- stop();
114
- (_a = errorRef.current) === null || _a === void 0 ? void 0 : _a.call(errorRef, err instanceof Error ? err : new Error(String(err)));
115
- })
116
- .finally(() => {
117
- busyRef.current = false;
118
- });
119
- }, [stop]);
120
- const start = useCallback(async (video, onFrame, onError) => {
121
- try {
122
- const stream = await navigator.mediaDevices.getUserMedia({
123
- video: { facingMode: 'user', width: { ideal: 640 }, height: { ideal: 480 } },
124
- });
125
- streamRef.current = stream;
126
- videoRef.current = video;
127
- video.srcObject = stream;
128
- await video.play();
129
- callbackRef.current = onFrame !== null && onFrame !== void 0 ? onFrame : null;
130
- errorRef.current = onError !== null && onError !== void 0 ? onError : null;
131
- frameIndexRef.current = 0;
132
- startedAtRef.current = performance.now();
133
- lastRunRef.current = -Infinity;
134
- runRef.current += 1;
135
- if (!configRef.current.detector) {
136
- console.warn('[NicePoseDetector] started without a `detector`: the camera runs, but no poses are detected.');
137
- }
138
- setIsDetecting(true);
139
- rafRef.current = requestAnimationFrame(processFrame);
140
- }
141
- catch (err) {
142
- throw new Error(`Camera access failed: ${err instanceof Error ? err.message : String(err)}`);
143
- }
144
- }, [processFrame]);
145
- useEffect(() => () => {
146
- stop();
147
- }, [stop]);
148
- return { start, stop, isDetecting, getLatestFrame: () => latestFrameRef.current };
136
+ } catch (err) {
137
+ throw new Error(
138
+ `Camera access failed: ${err instanceof Error ? err.message : String(err)}`
139
+ );
140
+ }
141
+ },
142
+ [processFrame]
143
+ );
144
+ useEffect(
145
+ () => () => {
146
+ stop();
147
+ },
148
+ [stop]
149
+ );
150
+ return { start, stop, isDetecting, getLatestFrame: () => latestFrameRef.current };
149
151
  }
150
- // ── Component: PoseDetector ─────────────────────────────────────
151
- const NicePoseDetector = /* @__PURE__ */ forwardRef(function NicePoseDetector(props, ref) {
152
- const { t } = useNiceTranslation();
153
- const { onFrame, onStart, onStop, onError, className, style, width = 640, height = 480, mirror = true, showSkeleton = true, showConfidence = false, ...config } = props;
154
- const videoRef = useRef(null);
155
- const canvasRef = useRef(null);
156
- const detection = usePoseDetection(config);
157
- useImperativeHandle(ref, () => ({
158
- start: handleStart,
159
- stop: handleStop,
160
- getLatestFrame: detection.getLatestFrame,
161
- get isDetecting() {
162
- return detection.isDetecting;
163
- },
164
- }));
165
- const drawSkeleton = useCallback((frame) => {
166
- var _a;
167
- const ctx = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getContext('2d');
168
- if (!ctx || !canvasRef.current) {
169
- return;
170
- }
171
- ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
172
- // Keypoints are in video pixels; the video fills the box with object-fit: cover.
173
- const video = videoRef.current;
174
- const videoW = (video === null || video === void 0 ? void 0 : video.videoWidth) || width;
175
- const videoH = (video === null || video === void 0 ? void 0 : video.videoHeight) || height;
176
- const scale = Math.max(width / videoW, height / videoH);
177
- const offsetX = (width - videoW * scale) / 2;
178
- const offsetY = (height - videoH * scale) / 2;
179
- const at = (kp) => [
180
- kp.x * scale + offsetX,
181
- kp.y * scale + offsetY,
182
- ];
183
- for (const person of frame.persons) {
184
- const kps = person.keypoints;
185
- const byName = new Map(kps.map((kp) => [kp.name, kp]));
186
- // Draw connections
187
- if (showSkeleton) {
188
- ctx.strokeStyle = resolveCssColor('var(--nice-success, #00ff88)', ctx.canvas);
189
- ctx.lineWidth = 2;
190
- for (const [a, b] of SKELETON_CONNECTIONS) {
191
- const ka = byName.get(COCO_KEYPOINTS[a]);
192
- const kb = byName.get(COCO_KEYPOINTS[b]);
193
- if (!ka || !kb) {
194
- continue;
195
- }
196
- if (confidenceOf(ka) < 0.3 || confidenceOf(kb) < 0.3) {
197
- continue;
198
- }
199
- ctx.beginPath();
200
- ctx.moveTo(...at(ka));
201
- ctx.lineTo(...at(kb));
202
- ctx.stroke();
203
- }
204
- }
205
- // Draw keypoints
206
- for (const kp of kps) {
207
- if (confidenceOf(kp) < 0.3) {
208
- continue;
209
- }
210
- const radius = showConfidence ? 3 + confidenceOf(kp) * 5 : 4;
211
- ctx.fillStyle = resolveCssColor(showConfidence
212
- ? `rgba(0, 255, 136, ${confidenceOf(kp)})`
213
- : 'var(--nice-success, #00ff88)', ctx.canvas);
214
- ctx.beginPath();
215
- ctx.arc(...at(kp), radius, 0, 2 * Math.PI);
216
- ctx.fill();
152
+ const NicePoseDetector = /* @__PURE__ */ forwardRef(function NicePoseDetector2(props, ref) {
153
+ const { t } = useNiceTranslation();
154
+ const {
155
+ onFrame,
156
+ onStart,
157
+ onStop,
158
+ onError,
159
+ className,
160
+ style,
161
+ width = 640,
162
+ height = 480,
163
+ mirror = true,
164
+ showSkeleton = true,
165
+ showConfidence = false,
166
+ ...config
167
+ } = props;
168
+ const videoRef = useRef(null);
169
+ const canvasRef = useRef(null);
170
+ const detection = usePoseDetection(config);
171
+ useImperativeHandle(ref, () => ({
172
+ start: handleStart,
173
+ stop: handleStop,
174
+ getLatestFrame: detection.getLatestFrame,
175
+ get isDetecting() {
176
+ return detection.isDetecting;
177
+ }
178
+ }));
179
+ const drawSkeleton = useCallback(
180
+ (frame) => {
181
+ const ctx = canvasRef.current?.getContext("2d");
182
+ if (!ctx || !canvasRef.current) {
183
+ return;
184
+ }
185
+ ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
186
+ const video = videoRef.current;
187
+ const videoW = video?.videoWidth || width;
188
+ const videoH = video?.videoHeight || height;
189
+ const scale = Math.max(width / videoW, height / videoH);
190
+ const offsetX = (width - videoW * scale) / 2;
191
+ const offsetY = (height - videoH * scale) / 2;
192
+ const at = (kp) => [
193
+ kp.x * scale + offsetX,
194
+ kp.y * scale + offsetY
195
+ ];
196
+ for (const person of frame.persons) {
197
+ const kps = person.keypoints;
198
+ const byName = new Map(kps.map((kp) => [kp.name, kp]));
199
+ if (showSkeleton) {
200
+ ctx.strokeStyle = resolveCssColor("var(--nice-success, #00ff88)", ctx.canvas);
201
+ ctx.lineWidth = 2;
202
+ for (const [a, b] of SKELETON_CONNECTIONS) {
203
+ const ka = byName.get(COCO_KEYPOINTS[a]);
204
+ const kb = byName.get(COCO_KEYPOINTS[b]);
205
+ if (!ka || !kb) {
206
+ continue;
217
207
  }
218
- }
219
- }, [showSkeleton, showConfidence, width, height]);
220
- // The detection loop is started once; it reaches this render's drawing and callbacks here.
221
- const latestRef = useRef({ drawSkeleton, onFrame, onError });
222
- useEffect(() => {
223
- latestRef.current = { drawSkeleton, onFrame, onError };
224
- });
225
- const handleFrame = useCallback((frame) => {
226
- var _a, _b;
227
- latestRef.current.drawSkeleton(frame);
228
- (_b = (_a = latestRef.current).onFrame) === null || _b === void 0 ? void 0 : _b.call(_a, frame);
229
- }, []);
230
- const handleDetectorError = useCallback((error) => {
231
- var _a, _b;
232
- (_b = (_a = latestRef.current).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error);
233
- }, []);
234
- const handleStart = useCallback(async () => {
235
- try {
236
- if (videoRef.current) {
237
- await detection.start(videoRef.current, handleFrame, handleDetectorError);
238
- onStart === null || onStart === void 0 ? void 0 : onStart();
208
+ if (confidenceOf(ka) < 0.3 || confidenceOf(kb) < 0.3) {
209
+ continue;
239
210
  }
211
+ ctx.beginPath();
212
+ ctx.moveTo(...at(ka));
213
+ ctx.lineTo(...at(kb));
214
+ ctx.stroke();
215
+ }
240
216
  }
241
- catch (err) {
242
- onError === null || onError === void 0 ? void 0 : onError(err instanceof Error ? err : new Error(String(err)));
217
+ for (const kp of kps) {
218
+ if (confidenceOf(kp) < 0.3) {
219
+ continue;
220
+ }
221
+ const radius = showConfidence ? 3 + confidenceOf(kp) * 5 : 4;
222
+ ctx.fillStyle = resolveCssColor(
223
+ showConfidence ? `rgba(0, 255, 136, ${confidenceOf(kp)})` : "var(--nice-success, #00ff88)",
224
+ ctx.canvas
225
+ );
226
+ ctx.beginPath();
227
+ ctx.arc(...at(kp), radius, 0, 2 * Math.PI);
228
+ ctx.fill();
243
229
  }
244
- }, [detection, handleFrame, handleDetectorError, onStart, onError]);
245
- const handleStop = useCallback(() => {
246
- var _a;
247
- detection.stop();
248
- const canvas = canvasRef.current;
249
- (_a = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.clearRect(0, 0, canvas.width, canvas.height);
250
- onStop === null || onStop === void 0 ? void 0 : onStop();
251
- }, [detection, onStop]);
252
- const containerStyle = {
253
- position: 'relative',
230
+ }
231
+ },
232
+ [showSkeleton, showConfidence, width, height]
233
+ );
234
+ const latestRef = useRef({ drawSkeleton, onFrame, onError });
235
+ useEffect(() => {
236
+ latestRef.current = { drawSkeleton, onFrame, onError };
237
+ });
238
+ const handleFrame = useCallback((frame) => {
239
+ latestRef.current.drawSkeleton(frame);
240
+ latestRef.current.onFrame?.(frame);
241
+ }, []);
242
+ const handleDetectorError = useCallback((error) => {
243
+ latestRef.current.onError?.(error);
244
+ }, []);
245
+ const handleStart = useCallback(async () => {
246
+ try {
247
+ if (videoRef.current) {
248
+ await detection.start(videoRef.current, handleFrame, handleDetectorError);
249
+ onStart?.();
250
+ }
251
+ } catch (err) {
252
+ onError?.(err instanceof Error ? err : new Error(String(err)));
253
+ }
254
+ }, [detection, handleFrame, handleDetectorError, onStart, onError]);
255
+ const handleStop = useCallback(() => {
256
+ detection.stop();
257
+ const canvas = canvasRef.current;
258
+ canvas?.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
259
+ onStop?.();
260
+ }, [detection, onStop]);
261
+ const containerStyle = {
262
+ position: "relative",
263
+ width,
264
+ height,
265
+ overflow: "hidden",
266
+ borderRadius: 8,
267
+ background: "var(--nice-bg-primary, #111)",
268
+ ...style
269
+ };
270
+ const videoStyle = {
271
+ width: "100%",
272
+ height: "100%",
273
+ objectFit: "cover",
274
+ transform: mirror ? "scaleX(-1)" : void 0
275
+ };
276
+ return /* @__PURE__ */ jsxs("div", { className, style: containerStyle, children: [
277
+ /* @__PURE__ */ jsx("video", { ref: videoRef, style: videoStyle, playsInline: true, muted: true }),
278
+ /* @__PURE__ */ jsx(
279
+ "canvas",
280
+ {
281
+ ref: canvasRef,
254
282
  width,
255
283
  height,
256
- overflow: 'hidden',
257
- borderRadius: 8,
258
- background: 'var(--nice-bg-primary, #111)',
259
- ...style,
260
- };
261
- const videoStyle = {
262
- width: '100%',
263
- height: '100%',
264
- objectFit: 'cover',
265
- transform: mirror ? 'scaleX(-1)' : undefined,
266
- };
267
- return (jsxs("div", { className: className, style: containerStyle, children: [jsx("video", { ref: videoRef, style: videoStyle, playsInline: true, muted: true }), jsx("canvas", { ref: canvasRef, width: width, height: height, style: {
268
- position: 'absolute',
269
- top: 0,
270
- left: 0,
271
- width: '100%',
272
- height: '100%',
273
- pointerEvents: 'none',
274
- transform: mirror ? 'scaleX(-1)' : undefined,
275
- } }), !detection.isDetecting && (jsx("button", { onClick: handleStart, style: {
276
- position: 'absolute',
277
- top: '50%',
278
- left: '50%',
279
- transform: 'translate(-50%, -50%)',
280
- padding: '12px 24px',
281
- borderRadius: 8,
282
- background: 'var(--nice-danger, #e94560)',
283
- color: 'var(--nice-text-on-primary, #fff)',
284
- border: 'none',
285
- cursor: 'pointer',
286
- fontSize: 14,
287
- fontWeight: 600,
288
- }, children: t('poseDetector.startCamera', 'Start Camera') }))] }));
284
+ style: {
285
+ position: "absolute",
286
+ top: 0,
287
+ left: 0,
288
+ width: "100%",
289
+ height: "100%",
290
+ pointerEvents: "none",
291
+ transform: mirror ? "scaleX(-1)" : void 0
292
+ }
293
+ }
294
+ ),
295
+ !detection.isDetecting && /* @__PURE__ */ jsx(
296
+ "button",
297
+ {
298
+ onClick: handleStart,
299
+ style: {
300
+ position: "absolute",
301
+ top: "50%",
302
+ left: "50%",
303
+ transform: "translate(-50%, -50%)",
304
+ padding: "12px 24px",
305
+ borderRadius: 8,
306
+ background: "var(--nice-danger, #e94560)",
307
+ color: "var(--nice-text-on-primary, #fff)",
308
+ border: "none",
309
+ cursor: "pointer",
310
+ fontSize: 14,
311
+ fontWeight: 600
312
+ },
313
+ children: t("poseDetector.startCamera", "Start Camera")
314
+ }
315
+ )
316
+ ] });
289
317
  });
290
-
291
- export { NicePoseDetector, usePoseDetection };
318
+ export {
319
+ NicePoseDetector,
320
+ usePoseDetection
321
+ };