@nice2dev/ui-3d 1.3.14 → 1.3.16

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