openfree commited on
Commit
1496237
ยท
verified ยท
1 Parent(s): 02ed9d7

Create holistic-backup.js

Browse files
Files changed (1) hide show
  1. holistic-backup.js +891 -0
holistic-backup.js ADDED
@@ -0,0 +1,891 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import DeviceDetector from "https://cdn.skypack.dev/[email protected]";
2
+ // ์‚ฌ์šฉ ๋ฐฉ๋ฒ•: testSupport({client?: string, os?: string}[])
3
+ // client์™€ os๋Š” ์ •๊ทœ ํ‘œํ˜„์‹์ž…๋‹ˆ๋‹ค.
4
+ // ์ฐธ๊ณ : https://cdn.jsdelivr.net/npm/[email protected]/README.md
5
+ // client์™€ os์˜ ์œ ํšจ ๊ฐ’ ํ™•์ธ
6
+
7
+ // ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž„ํฌํŠธ
8
+
9
+ // ์†๋„์™€ ๊ฐ€์†๋„ ์ฐจํŠธ๋ฅผ ์ดˆ๊ธฐํ™”
10
+ let speedChart, accelerationChart;
11
+ let previousPoseData = null;
12
+ let lastTimestamp = 0;
13
+
14
+ testSupport([
15
+ { client: 'Chrome' },
16
+ ]);
17
+
18
+ // ์ฐจํŠธ ๊ด€๋ จ ์ƒ์ˆ˜ ์„ค์ •
19
+ const CHART_CONFIG = {
20
+ maxDataPoints: 50,
21
+ updateInterval: 100, // ์ฐจํŠธ ์—…๋ฐ์ดํŠธ ๊ฐ„๊ฒฉ(ms)
22
+ colors: {
23
+ speed: {
24
+ primary: 'rgba(75, 192, 192, 1)',
25
+ background: 'rgba(75, 192, 192, 0.1)'
26
+ },
27
+ acceleration: {
28
+ primary: 'rgba(255, 99, 132, 1)',
29
+ background: 'rgba(255, 99, 132, 0.1)'
30
+ }
31
+ }
32
+ };
33
+
34
+ // ์ฐจํŠธ ์ดˆ๊ธฐํ™”
35
+ function initCharts() {
36
+ // ์ฐจํŠธ๋ฅผ ๋‹ด์„ ์ปจํ…Œ์ด๋„ˆ ์ƒ์„ฑ
37
+ const chartsContainer = document.createElement('div');
38
+ chartsContainer.className = 'charts-container';
39
+ document.querySelector('.container').appendChild(chartsContainer);
40
+
41
+ // ์†๋„ ์ฐจํŠธ ์ปจํ…Œ์ด๋„ˆ
42
+ const speedChartContainer = document.createElement('div');
43
+ speedChartContainer.className = 'chart-card';
44
+ const speedCanvas = document.createElement('canvas');
45
+ speedCanvas.id = 'speedChart';
46
+ speedChartContainer.appendChild(speedCanvas);
47
+ chartsContainer.appendChild(speedChartContainer);
48
+
49
+ // ๊ฐ€์†๋„ ์ฐจํŠธ ์ปจํ…Œ์ด๋„ˆ
50
+ const accelerationChartContainer = document.createElement('div');
51
+ accelerationChartContainer.className = 'chart-card';
52
+ const accelerationCanvas = document.createElement('canvas');
53
+ accelerationCanvas.id = 'accelerationChart';
54
+ accelerationChartContainer.appendChild(accelerationCanvas);
55
+ chartsContainer.appendChild(accelerationChartContainer);
56
+
57
+ // ์†๋„ ์ฐจํŠธ ์„ค์ •
58
+ speedChart = new Chart(speedCanvas.getContext('2d'), {
59
+ type: 'line',
60
+ data: {
61
+ labels: [],
62
+ datasets: [{
63
+ label: '์šด๋™ ์†๋„ (ํ”ฝ์…€/์ดˆ)',
64
+ data: [],
65
+ borderColor: CHART_CONFIG.colors.speed.primary,
66
+ backgroundColor: CHART_CONFIG.colors.speed.background,
67
+ tension: 0.4,
68
+ borderWidth: 2,
69
+ fill: true,
70
+ pointRadius: 0,
71
+ pointHitRadius: 10
72
+ }]
73
+ },
74
+ options: {
75
+ responsive: true,
76
+ maintainAspectRatio: false,
77
+ animation: {
78
+ duration: 0
79
+ },
80
+ interaction: {
81
+ intersect: false,
82
+ mode: 'index'
83
+ },
84
+ plugins: {
85
+ legend: {
86
+ position: 'top',
87
+ labels: {
88
+ font: {
89
+ family: '"Titillium Web", sans-serif',
90
+ size: 14
91
+ },
92
+ color: '#333'
93
+ }
94
+ },
95
+ tooltip: {
96
+ enabled: true,
97
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
98
+ titleFont: {
99
+ family: '"Titillium Web", sans-serif'
100
+ },
101
+ bodyFont: {
102
+ family: '"Titillium Web", sans-serif'
103
+ }
104
+ }
105
+ },
106
+ scales: {
107
+ y: {
108
+ beginAtZero: true,
109
+ grid: {
110
+ color: 'rgba(0, 0, 0, 0.1)'
111
+ },
112
+ ticks: {
113
+ font: {
114
+ family: '"Titillium Web", sans-serif'
115
+ }
116
+ }
117
+ },
118
+ x: {
119
+ grid: {
120
+ display: false
121
+ },
122
+ ticks: {
123
+ maxRotation: 0,
124
+ maxTicksLimit: 5,
125
+ font: {
126
+ family: '"Titillium Web", sans-serif'
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ });
133
+
134
+ // ๊ฐ€์†๋„ ์ฐจํŠธ ์„ค์ •
135
+ accelerationChart = new Chart(accelerationCanvas.getContext('2d'), {
136
+ type: 'line',
137
+ data: {
138
+ labels: [],
139
+ datasets: [{
140
+ label: '๊ฐ€์†๋„ (ํ”ฝ์…€/์ดˆยฒ)',
141
+ data: [],
142
+ borderColor: CHART_CONFIG.colors.acceleration.primary,
143
+ backgroundColor: CHART_CONFIG.colors.acceleration.background,
144
+ tension: 0.4,
145
+ borderWidth: 2,
146
+ fill: true,
147
+ pointRadius: 0,
148
+ pointHitRadius: 10
149
+ }]
150
+ },
151
+ options: {
152
+ responsive: true,
153
+ maintainAspectRatio: false,
154
+ animation: {
155
+ duration: 0
156
+ },
157
+ interaction: {
158
+ intersect: false,
159
+ mode: 'index'
160
+ },
161
+ plugins: {
162
+ legend: {
163
+ position: 'top',
164
+ labels: {
165
+ font: {
166
+ family: '"Titillium Web", sans-serif',
167
+ size: 14
168
+ },
169
+ color: '#333'
170
+ }
171
+ },
172
+ tooltip: {
173
+ enabled: true,
174
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
175
+ titleFont: {
176
+ family: '"Titillium Web", sans-serif'
177
+ },
178
+ bodyFont: {
179
+ family: '"Titillium Web", sans-serif'
180
+ }
181
+ }
182
+ },
183
+ scales: {
184
+ y: {
185
+ beginAtZero: true,
186
+ grid: {
187
+ color: 'rgba(0, 0, 0, 0.1)'
188
+ },
189
+ ticks: {
190
+ font: {
191
+ family: '"Titillium Web", sans-serif'
192
+ }
193
+ }
194
+ },
195
+ x: {
196
+ grid: {
197
+ display: false
198
+ },
199
+ ticks: {
200
+ maxRotation: 0,
201
+ maxTicksLimit: 5,
202
+ font: {
203
+ family: '"Titillium Web", sans-serif'
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ });
210
+
211
+ // ๋ฐ˜์‘ํ˜• ์ฒ˜๋ฆฌ
212
+ window.addEventListener('resize', () => {
213
+ speedChart.resize();
214
+ accelerationChart.resize();
215
+ });
216
+ }
217
+
218
+ // ์ž์„ธ ๋ณ€ํ™”์˜ ์†๋„์™€ ๊ฐ€์†๋„๋ฅผ ๊ณ„์‚ฐ
219
+ function calculateMotionMetrics(results, timestamp) {
220
+ // ๊ธฐ๋ณธ ๊ฒ€์ฆ
221
+ if (!results || !results.poseLandmarks || !Array.isArray(results.poseLandmarks)) {
222
+ return { speed: 0, acceleration: 0 };
223
+ }
224
+
225
+ // ์ดˆ๊ธฐ ์ƒํƒœ
226
+ if (!previousPoseData || !previousPoseData.poseLandmarks) {
227
+ previousPoseData = {
228
+ poseLandmarks: [...results.poseLandmarks]
229
+ };
230
+ lastTimestamp = timestamp;
231
+ return { speed: 0, acceleration: 0 };
232
+ }
233
+
234
+ const deltaTime = (timestamp - lastTimestamp) / 1000; // ์ดˆ ๋‹จ์œ„๋กœ ๋ณ€ํ™˜
235
+ if (deltaTime === 0) {
236
+ return { speed: 0, acceleration: 0 };
237
+ }
238
+
239
+ // ํ‚คํฌ์ธํŠธ ํ‰๊ท  ์ด๋™๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
240
+ let totalDisplacement = 0;
241
+ let validPoints = 0;
242
+
243
+ try {
244
+ // ์œ ํšจํ•œ ํ‚คํฌ์ธํŠธ๋งŒ ์‚ฌ์šฉ
245
+ results.poseLandmarks.forEach((landmark, index) => {
246
+ const prevLandmark = previousPoseData.poseLandmarks[index];
247
+
248
+ if (
249
+ landmark && prevLandmark &&
250
+ typeof landmark.x === 'number' &&
251
+ typeof landmark.y === 'number' &&
252
+ typeof prevLandmark.x === 'number' &&
253
+ typeof prevLandmark.y === 'number' &&
254
+ // ์„ ํƒ ์‚ฌํ•ญ: ๊ฐ€์‹œ์„ฑ(visibility) ๊ฐ’์ด ์ผ์ • ๊ธฐ์ค€ ์ด์ƒ์ผ ๋•Œ๋งŒ
255
+ (!landmark.visibility || landmark.visibility > 0.5) &&
256
+ (!prevLandmark.visibility || prevLandmark.visibility > 0.5)
257
+ ) {
258
+ const dx = landmark.x - prevLandmark.x;
259
+ const dy = landmark.y - prevLandmark.y;
260
+ const displacement = Math.sqrt(dx * dx + dy * dy);
261
+
262
+ // ๋น„์ •์ƒ์ ์œผ๋กœ ํฐ ์ด๋™ ๊ฑฐ๋ฆฌ๋Š” ํ•„ํ„ฐ๋ง
263
+ if (displacement < 1.0) {
264
+ totalDisplacement += displacement;
265
+ validPoints++;
266
+ }
267
+ }
268
+ });
269
+ } catch (error) {
270
+ console.warn('์ด๋™ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ ์ค‘ ์˜ค๋ฅ˜:', error);
271
+ return { speed: 0, acceleration: 0 };
272
+ }
273
+
274
+ // ์œ ํšจ ํฌ์ธํŠธ๊ฐ€ ์—†๋‹ค๋ฉด 0 ๋ฐ˜ํ™˜
275
+ if (validPoints === 0) {
276
+ return { speed: 0, acceleration: 0 };
277
+ }
278
+
279
+ // ํ‰๊ท  ์ด๋™๊ฑฐ๋ฆฌ ๋ฐ ์†๋„ ๊ณ„์‚ฐ
280
+ const averageDisplacement = totalDisplacement / validPoints;
281
+ const currentSpeed = averageDisplacement / deltaTime;
282
+
283
+ // ์ด์ „ ์†๋„๊ฐ€ ์—†์œผ๋ฉด 0
284
+ let previousSpeed = 0;
285
+ try {
286
+ previousSpeed = speedChart.data.datasets[0].data[
287
+ speedChart.data.datasets[0].data.length - 1
288
+ ] || 0;
289
+ } catch (error) {
290
+ console.warn('์ด์ „ ์†๋„ ์ ‘๊ทผ ์ค‘ ์˜ค๋ฅ˜:', error);
291
+ }
292
+
293
+ // ๊ฐ€์†๋„ ๊ณ„์‚ฐ
294
+ const acceleration = (currentSpeed - previousSpeed) / deltaTime;
295
+
296
+ // ๋‹ค์Œ ํ”„๋ ˆ์ž„ ๊ณ„์‚ฐ์„ ์œ„ํ•ด ์ด์ „ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
297
+ previousPoseData = {
298
+ poseLandmarks: [...results.poseLandmarks]
299
+ };
300
+ lastTimestamp = timestamp;
301
+
302
+ // ๊ฐ’ ๊ฒ€์ฆ ๋ฐ ์ œํ•œ
303
+ const metrics = {
304
+ speed: isFinite(currentSpeed) ? Math.min(Math.max(currentSpeed, 0), 1000) : 0,
305
+ acceleration: isFinite(acceleration) ? Math.min(Math.max(acceleration, -1000), 1000) : 0
306
+ };
307
+
308
+ return metrics;
309
+ }
310
+
311
+ // ์ฐจํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ํ•จ์ˆ˜ (์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํฌํ•จ)
312
+ function updateCharts(metrics) {
313
+ if (!metrics || typeof metrics.speed !== 'number' || typeof metrics.acceleration !== 'number') {
314
+ console.warn('์ž˜๋ชป๋œ metrics ๋ฐ์ดํ„ฐ:', metrics);
315
+ return;
316
+ }
317
+
318
+ try {
319
+ const timestamp = new Date().toLocaleTimeString('ko-KR', {
320
+ hour: '2-digit',
321
+ minute: '2-digit',
322
+ second: '2-digit'
323
+ });
324
+
325
+ // ์ฐจํŠธ ๊ฐ์ฒด๊ฐ€ ์ œ๋Œ€๋กœ ์ดˆ๊ธฐํ™”๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
326
+ if (!speedChart || !speedChart.data || !speedChart.data.labels) {
327
+ console.warn('์†๋„ ์ฐจํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.');
328
+ return;
329
+ }
330
+ if (!accelerationChart || !accelerationChart.data || !accelerationChart.data.labels) {
331
+ console.warn('๊ฐ€์†๋„ ์ฐจํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.');
332
+ return;
333
+ }
334
+
335
+ // ์†๋„ ์ฐจํŠธ ์—…๋ฐ์ดํŠธ
336
+ speedChart.data.labels.push(timestamp);
337
+ speedChart.data.datasets[0].data.push(metrics.speed);
338
+ if (speedChart.data.labels.length > CHART_CONFIG.maxDataPoints) {
339
+ speedChart.data.labels.shift();
340
+ speedChart.data.datasets[0].data.shift();
341
+ }
342
+
343
+ // ๊ฐ€์†๋„ ์ฐจํŠธ ์—…๋ฐ์ดํŠธ
344
+ accelerationChart.data.labels.push(timestamp);
345
+ accelerationChart.data.datasets[0].data.push(metrics.acceleration);
346
+ if (accelerationChart.data.labels.length > CHART_CONFIG.maxDataPoints) {
347
+ accelerationChart.data.labels.shift();
348
+ accelerationChart.data.datasets[0].data.shift();
349
+ }
350
+
351
+ // requestAnimationFrame์„ ์‚ฌ์šฉํ•ด ์ฐจํŠธ ์—…๋ฐ์ดํŠธ ์ตœ์ ํ™”
352
+ requestAnimationFrame(() => {
353
+ try {
354
+ speedChart.update('none');
355
+ accelerationChart.update('none');
356
+ } catch (error) {
357
+ console.warn('์ฐจํŠธ ์—…๋ฐ์ดํŠธ ์ค‘ ์˜ค๋ฅ˜:', error);
358
+ }
359
+ });
360
+ } catch (error) {
361
+ console.warn('updateCharts์—์„œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
362
+ }
363
+ }
364
+
365
+ function testSupport(supportedDevices) {
366
+ const deviceDetector = new DeviceDetector();
367
+ const detectedDevice = deviceDetector.parse(navigator.userAgent);
368
+ let isSupported = false;
369
+ for (const device of supportedDevices) {
370
+ if (device.client !== undefined) {
371
+ const re = new RegExp(`^${device.client}$`);
372
+ if (!re.test(detectedDevice.client.name)) {
373
+ continue;
374
+ }
375
+ }
376
+ if (device.os !== undefined) {
377
+ const re = new RegExp(`^${device.os}$`);
378
+ if (!re.test(detectedDevice.os.name)) {
379
+ continue;
380
+ }
381
+ }
382
+ isSupported = true;
383
+ break;
384
+ }
385
+ if (!isSupported) {
386
+ alert(`์ด ๋ฐ๋ชจ๋Š” ${detectedDevice.client.name}/${detectedDevice.os.name} ์—์„œ ์‹คํ–‰๋  ๋•Œ ` +
387
+ `์™„์ „ํžˆ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ณ„์† ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋ณธ์ธ ์ฑ…์ž„ ํ•˜์— ์ง„ํ–‰ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.`);
388
+ }
389
+ }
390
+
391
+ const controls = window;
392
+ const mpHolistic = window;
393
+ const drawingUtils = window;
394
+ const config = { locateFile: (file) => {
395
+ return `https://cdn.jsdelivr.net/npm/@mediapipe/holistic@` +
396
+ `${mpHolistic.VERSION}/${file}`;
397
+ } };
398
+
399
+ // ์ž…๋ ฅ ํ”„๋ ˆ์ž„์€ ์—ฌ๊ธฐ์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
400
+ const videoElement = document.getElementsByClassName('input_video')[0];
401
+ const canvasElement = document.getElementsByClassName('output_canvas')[0];
402
+ const controlsElement = document.getElementsByClassName('control-panel')[0];
403
+ const canvasCtx = canvasElement.getContext('2d');
404
+
405
+ // ์ดํ›„ tick()์ด ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ์ฐธ์กฐํ•  FPS ์ปจํŠธ๋กค
406
+ const fpsControl = new controls.FPS();
407
+
408
+ // ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ๋ฅผ ์ˆจ๊ธฐ๋Š” ์ตœ์ ํ™”
409
+ const spinner = document.querySelector('.loading');
410
+ spinner.ontransitionend = () => {
411
+ spinner.style.display = 'none';
412
+ };
413
+
414
+ function removeElements(landmarks, elements) {
415
+ for (const element of elements) {
416
+ delete landmarks[element];
417
+ }
418
+ }
419
+
420
+ function removeLandmarks(results) {
421
+ if (results.poseLandmarks) {
422
+ removeElements(results.poseLandmarks, [
423
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
424
+ 15, 16, 17, 18, 19, 20, 21, 22
425
+ ]);
426
+ }
427
+ }
428
+
429
+ function connect(ctx, connectors) {
430
+ const canvas = ctx.canvas;
431
+ for (const connector of connectors) {
432
+ const from = connector[0];
433
+ const to = connector[1];
434
+ if (from && to) {
435
+ if (from.visibility && to.visibility &&
436
+ (from.visibility < 0.1 || to.visibility < 0.1)) {
437
+ continue;
438
+ }
439
+ ctx.beginPath();
440
+ ctx.moveTo(from.x * canvas.width, from.y * canvas.height);
441
+ ctx.lineTo(to.x * canvas.width, to.y * canvas.height);
442
+ ctx.stroke();
443
+ }
444
+ }
445
+ }
446
+
447
+ let activeEffect = 'mask';
448
+
449
+ function onResults(results) {
450
+ // ๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ ์ˆจ๊ธฐ๊ธฐ
451
+ document.body.classList.add('loaded');
452
+
453
+ // ๊ทธ๋ฆด ํ•„์š” ์—†๋Š” ๋žœ๋“œ๋งˆํฌ ์ œ๊ฑฐ
454
+ removeLandmarks(results);
455
+
456
+ // FPS ์—…๋ฐ์ดํŠธ
457
+ fpsControl.tick();
458
+
459
+ // ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฌ๊ธฐ
460
+ canvasCtx.save();
461
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
462
+
463
+ if (results.segmentationMask) {
464
+ canvasCtx.drawImage(
465
+ results.segmentationMask,
466
+ 0, 0, canvasElement.width, canvasElement.height
467
+ );
468
+
469
+ // ๊ธฐ์กด ํ”ฝ์…€๋งŒ ๋ฎ์–ด์“ฐ๊ธฐ
470
+ if (activeEffect === 'mask' || activeEffect === 'both') {
471
+ canvasCtx.globalCompositeOperation = 'source-in';
472
+ canvasCtx.fillStyle = '#00FF007F';
473
+ canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
474
+ } else {
475
+ canvasCtx.globalCompositeOperation = 'source-out';
476
+ canvasCtx.fillStyle = '#0000FF7F';
477
+ canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height);
478
+ }
479
+
480
+ // ๋ˆ„๋ฝ๋œ ํ”ฝ์…€๋งŒ ๋ฎ์–ด์“ฐ๊ธฐ
481
+ canvasCtx.globalCompositeOperation = 'destination-atop';
482
+ canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
483
+ canvasCtx.globalCompositeOperation = 'source-over';
484
+ } else {
485
+ canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
486
+ }
487
+
488
+ // ๋™์ž‘ ์ง€ํ‘œ ๊ณ„์‚ฐ ํ›„ ์ฐจํŠธ ์—…๋ฐ์ดํŠธ
489
+ const metrics = calculateMotionMetrics(results, performance.now());
490
+ updateCharts(metrics);
491
+
492
+ // ์•ˆ์ „ ๊ฒ€์‚ฌ
493
+ if (!results.poseLandmarks || !mpHolistic.POSE_LANDMARKS) {
494
+ canvasCtx.restore();
495
+ return;
496
+ }
497
+
498
+ // ํŒ”๊ฟˆ์น˜์—์„œ ์†๊นŒ์ง€ ์—ฐ๊ฒฐ
499
+ canvasCtx.lineWidth = 5;
500
+ if (results.poseLandmarks) {
501
+ if (results.rightHandLandmarks && results.poseLandmarks[mpHolistic.POSE_LANDMARKS.RIGHT_ELBOW]) {
502
+ canvasCtx.strokeStyle = 'white';
503
+ connect(canvasCtx, [[
504
+ results.poseLandmarks[mpHolistic.POSE_LANDMARKS.RIGHT_ELBOW],
505
+ results.rightHandLandmarks[0]
506
+ ]]);
507
+ }
508
+ if (results.leftHandLandmarks && results.poseLandmarks[mpHolistic.POSE_LANDMARKS.LEFT_ELBOW]) {
509
+ canvasCtx.strokeStyle = 'white';
510
+ connect(canvasCtx, [[
511
+ results.poseLandmarks[mpHolistic.POSE_LANDMARKS.LEFT_ELBOW],
512
+ results.leftHandLandmarks[0]
513
+ ]]);
514
+ }
515
+ }
516
+
517
+ // ์ „์‹  ์ž์„ธ ์—ฐ๊ฒฐ
518
+ if (results.poseLandmarks && mpHolistic.POSE_CONNECTIONS) {
519
+ drawingUtils.drawConnectors(
520
+ canvasCtx,
521
+ results.poseLandmarks,
522
+ mpHolistic.POSE_CONNECTIONS,
523
+ { color: 'white' }
524
+ );
525
+ }
526
+
527
+ // ์™ผ์ชฝ ์ž์„ธ ๋žœ๋“œ๋งˆํฌ
528
+ if (results.poseLandmarks && mpHolistic.POSE_LANDMARKS_LEFT) {
529
+ const leftLandmarks = Object.values(mpHolistic.POSE_LANDMARKS_LEFT)
530
+ .map(index => results.poseLandmarks[index])
531
+ .filter(landmark => landmark !== undefined);
532
+
533
+ if (leftLandmarks.length > 0) {
534
+ drawingUtils.drawLandmarks(canvasCtx, leftLandmarks, {
535
+ visibilityMin: 0.65,
536
+ color: 'white',
537
+ fillColor: 'rgb(255,138,0)'
538
+ });
539
+ }
540
+ }
541
+
542
+ // ์˜ค๋ฅธ์ชฝ ์ž์„ธ ๋žœ๋“œ๋งˆํฌ
543
+ if (results.poseLandmarks && mpHolistic.POSE_LANDMARKS_RIGHT) {
544
+ const rightLandmarks = Object.values(mpHolistic.POSE_LANDMARKS_RIGHT)
545
+ .map(index => results.poseLandmarks[index])
546
+ .filter(landmark => landmark !== undefined);
547
+
548
+ if (rightLandmarks.length > 0) {
549
+ drawingUtils.drawLandmarks(canvasCtx, rightLandmarks, {
550
+ visibilityMin: 0.65,
551
+ color: 'white',
552
+ fillColor: 'rgb(0,217,231)'
553
+ });
554
+ }
555
+ }
556
+
557
+ // ์† ๋žœ๋“œ๋งˆํฌ
558
+ if (results.rightHandLandmarks && mpHolistic.HAND_CONNECTIONS) {
559
+ drawingUtils.drawConnectors(
560
+ canvasCtx,
561
+ results.rightHandLandmarks,
562
+ mpHolistic.HAND_CONNECTIONS,
563
+ { color: 'white' }
564
+ );
565
+ drawingUtils.drawLandmarks(canvasCtx, results.rightHandLandmarks, {
566
+ color: 'white',
567
+ fillColor: 'rgb(0,217,231)',
568
+ lineWidth: 2,
569
+ radius: (data) => {
570
+ return drawingUtils.lerp(data.from.z, -0.15, .1, 10, 1);
571
+ }
572
+ });
573
+ }
574
+
575
+ if (results.leftHandLandmarks && mpHolistic.HAND_CONNECTIONS) {
576
+ drawingUtils.drawConnectors(
577
+ canvasCtx,
578
+ results.leftHandLandmarks,
579
+ mpHolistic.HAND_CONNECTIONS,
580
+ { color: 'white' }
581
+ );
582
+ drawingUtils.drawLandmarks(canvasCtx, results.leftHandLandmarks, {
583
+ color: 'white',
584
+ fillColor: 'rgb(255,138,0)',
585
+ lineWidth: 2,
586
+ radius: (data) => {
587
+ return drawingUtils.lerp(data.from.z, -0.15, .1, 10, 1);
588
+ }
589
+ });
590
+ }
591
+
592
+ // ์–ผ๊ตด ๋žœ๋“œ๋งˆํฌ
593
+ if (results.faceLandmarks && mpHolistic.FACEMESH_TESSELATION) {
594
+ drawingUtils.drawConnectors(
595
+ canvasCtx,
596
+ results.faceLandmarks,
597
+ mpHolistic.FACEMESH_TESSELATION,
598
+ { color: '#C0C0C070', lineWidth: 1 }
599
+ );
600
+ if (mpHolistic.FACEMESH_RIGHT_EYE) {
601
+ drawingUtils.drawConnectors(
602
+ canvasCtx,
603
+ results.faceLandmarks,
604
+ mpHolistic.FACEMESH_RIGHT_EYE,
605
+ { color: 'rgb(0,217,231)' }
606
+ );
607
+ }
608
+ if (mpHolistic.FACEMESH_RIGHT_EYEBROW) {
609
+ drawingUtils.drawConnectors(
610
+ canvasCtx,
611
+ results.faceLandmarks,
612
+ mpHolistic.FACEMESH_RIGHT_EYEBROW,
613
+ { color: 'rgb(0,217,231)' }
614
+ );
615
+ }
616
+ if (mpHolistic.FACEMESH_LEFT_EYE) {
617
+ drawingUtils.drawConnectors(
618
+ canvasCtx,
619
+ results.faceLandmarks,
620
+ mpHolistic.FACEMESH_LEFT_EYE,
621
+ { color: 'rgb(255,138,0)' }
622
+ );
623
+ }
624
+ if (mpHolistic.FACEMESH_LEFT_EYEBROW) {
625
+ drawingUtils.drawConnectors(
626
+ canvasCtx,
627
+ results.faceLandmarks,
628
+ mpHolistic.FACEMESH_LEFT_EYEBROW,
629
+ { color: 'rgb(255,138,0)' }
630
+ );
631
+ }
632
+ if (mpHolistic.FACEMESH_FACE_OVAL) {
633
+ drawingUtils.drawConnectors(
634
+ canvasCtx,
635
+ results.faceLandmarks,
636
+ mpHolistic.FACEMESH_FACE_OVAL,
637
+ { color: '#E0E0E0', lineWidth: 5 }
638
+ );
639
+ }
640
+ if (mpHolistic.FACEMESH_LIPS) {
641
+ drawingUtils.drawConnectors(
642
+ canvasCtx,
643
+ results.faceLandmarks,
644
+ mpHolistic.FACEMESH_LIPS,
645
+ { color: '#E0E0E0', lineWidth: 5 }
646
+ );
647
+ }
648
+ }
649
+
650
+ canvasCtx.restore();
651
+ }
652
+
653
+ // ์˜์ƒ ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ
654
+ function handleVideoUpload(file) {
655
+ // ๋น„๋””์˜ค URL ์ƒ์„ฑ
656
+ const videoUrl = URL.createObjectURL(file);
657
+
658
+ // ์ฐจํŠธ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
659
+ speedChart.data.labels = [];
660
+ speedChart.data.datasets[0].data = [];
661
+ accelerationChart.data.labels = [];
662
+ accelerationChart.data.datasets[0].data = [];
663
+ previousPoseData = null;
664
+ lastTimestamp = 0;
665
+
666
+ // ์ž์„ธ ๊ฐ์ง€ ๋ฆฌ์…‹
667
+ holistic.reset();
668
+
669
+ // ๋น„๋””์˜ค ์†Œ์Šค ์—…๋ฐ์ดํŠธ
670
+ videoElement.src = videoUrl;
671
+
672
+ // ๋น„๋””์˜ค ๋กœ๋“œ ์™„๋ฃŒ ํ›„ ์ฒ˜๋ฆฌ
673
+ videoElement.onloadedmetadata = () => {
674
+ // ๋น„๋””์˜ค ๋น„์œจ์— ๋งž์ถฐ ์บ”๋ฒ„์Šค ์‚ฌ์ด์ฆˆ ์กฐ์ •
675
+ const aspect = videoElement.videoHeight / videoElement.videoWidth;
676
+ let width, height;
677
+ if (window.innerWidth > window.innerHeight) {
678
+ height = window.innerHeight;
679
+ width = height / aspect;
680
+ } else {
681
+ width = window.innerWidth;
682
+ height = width * aspect;
683
+ }
684
+ canvasElement.width = width;
685
+ canvasElement.height = height;
686
+
687
+ // ์˜์ƒ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ animation frame ์ƒ์„ฑ
688
+ let animationId;
689
+
690
+ async function processFrame() {
691
+ if (videoElement.paused || videoElement.ended) {
692
+ cancelAnimationFrame(animationId);
693
+ return;
694
+ }
695
+ // ํ˜„์žฌ ํ”„๋ ˆ์ž„์„ ์ž์„ธ ๊ฐ์ง€์— ์ „์†ก
696
+ await holistic.send({
697
+ image: videoElement
698
+ });
699
+ animationId = requestAnimationFrame(processFrame);
700
+ }
701
+
702
+ // ๋น„๋””์˜ค ์žฌ์ƒ ์ฒ˜๋ฆฌ
703
+ videoElement.onplay = () => {
704
+ processFrame();
705
+ };
706
+
707
+ // ์žฌ์ƒ/์ผ์‹œ์ •์ง€ ๋ฒ„ํŠผ
708
+ const playPauseBtn = document.createElement('button');
709
+ playPauseBtn.textContent = '์žฌ์ƒ/์ผ์‹œ์ •์ง€';
710
+ playPauseBtn.className = 'control-button';
711
+ playPauseBtn.onclick = () => {
712
+ if (videoElement.paused) {
713
+ videoElement.play();
714
+ } else {
715
+ videoElement.pause();
716
+ }
717
+ };
718
+
719
+ // ๋‹ค์‹œ ์‹œ์ž‘ ๋ฒ„ํŠผ
720
+ const restartBtn = document.createElement('button');
721
+ restartBtn.textContent = '์ฒ˜์Œ๋ถ€ํ„ฐ ์žฌ์ƒ';
722
+ restartBtn.className = 'control-button';
723
+ restartBtn.onclick = () => {
724
+ videoElement.currentTime = 0;
725
+ if (videoElement.paused) {
726
+ videoElement.play();
727
+ }
728
+ };
729
+
730
+ // ๋ฒ„ํŠผ์„ ํ™”๋ฉด์— ์ถ”๊ฐ€
731
+ const controlsContainer = document.createElement('div');
732
+ controlsContainer.className = 'video-controls';
733
+ controlsContainer.appendChild(playPauseBtn);
734
+ controlsContainer.appendChild(restartBtn);
735
+
736
+ // ์ ์ ˆํ•œ ์œ„์น˜์— ๋ฒ„ํŠผ ์‚ฝ์ž…
737
+ const container = document.querySelector('.container') || document.body;
738
+ container.appendChild(controlsContainer);
739
+ };
740
+
741
+ // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
742
+ videoElement.onerror = () => {
743
+ console.error('๋น„๋””์˜ค ๋กœ๋“œ ์‹คํŒจ');
744
+ alert('๋น„๋””์˜ค ๋กœ๋“œ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋น„๋””์˜ค ํŒŒ์ผ์„ ์‹œ๋„ํ•ด๋ณด์„ธ์š”.');
745
+ };
746
+ }
747
+
748
+ // ์ผ๋ถ€ ๊ธฐ๋ณธ ์Šคํƒ€์ผ ์ถ”๊ฐ€
749
+ const style = document.createElement('style');
750
+ style.textContent = `
751
+ .video-controls {
752
+ position: fixed;
753
+ bottom: 20px;
754
+ left: 50%;
755
+ transform: translateX(-50%);
756
+ z-index: 1000;
757
+ display: flex;
758
+ gap: 10px;
759
+ }
760
+
761
+ .control-button {
762
+ padding: 10px 20px;
763
+ background-color: rgba(0, 0, 0, 0.7);
764
+ color: white;
765
+ border: none;
766
+ border-radius: 5px;
767
+ cursor: pointer;
768
+ font-size: 14px;
769
+ }
770
+
771
+ .control-button:hover {
772
+ background-color: rgba(0, 0, 0, 0.9);
773
+ }
774
+ `;
775
+ document.head.appendChild(style);
776
+
777
+ const holistic = new mpHolistic.Holistic(config);
778
+ holistic.onResults(onResults);
779
+
780
+ // ์ปจํŠธ๋กค ํŒจ๋„์„ ๊ทธ๋ ค์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ต์…˜ ์ œ์–ด ์ œ๊ณต
781
+ new controls
782
+ .ControlPanel(controlsElement, {
783
+ selfieMode: true,
784
+ modelComplexity: 1,
785
+ smoothLandmarks: true,
786
+ enableSegmentation: false,
787
+ smoothSegmentation: true,
788
+ minDetectionConfidence: 0.5,
789
+ minTrackingConfidence: 0.5,
790
+ effect: 'background',
791
+ })
792
+ .add([
793
+ new controls.StaticText({ title: 'MediaPipe ์ „์‹  ์ž์„ธ ๊ฐ์ง€' }),
794
+ fpsControl,
795
+ new controls.Toggle({ title: '์…€ํ”ผ ๋ชจ๋“œ', field: 'selfieMode' }),
796
+ new controls.SourcePicker({
797
+ onSourceChanged: () => {
798
+ // ์†Œ์Šค ๋ณ€๊ฒฝ ์‹œ ๋ฆฌ์…‹. ๋ฆฌ์…‹ ํ›„ ์ž์„ธ๋ฅผ ๋” ์ž˜ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Œ
799
+ holistic.reset();
800
+ },
801
+ onFrame: async (input, size) => {
802
+ const aspect = size.height / size.width;
803
+ let width, height;
804
+ if (window.innerWidth > window.innerHeight) {
805
+ height = window.innerHeight;
806
+ width = height / aspect;
807
+ } else {
808
+ width = window.innerWidth;
809
+ height = width * aspect;
810
+ }
811
+ canvasElement.width = width;
812
+ canvasElement.height = height;
813
+ await holistic.send({ image: input });
814
+ },
815
+ }),
816
+ new controls.Slider({
817
+ title: '๋ชจ๋ธ ๋ณต์žก๋„',
818
+ field: 'modelComplexity',
819
+ discrete: ['๊ฒฝ๋Ÿ‰', '์™„์ „', '๊ณ ๊ธ‰'],
820
+ }),
821
+ new controls.Toggle({ title: '๋žœ๋“œ๋งˆํฌ ํ‰ํ™œํ™”', field: 'smoothLandmarks' }),
822
+ new controls.Toggle({ title: '์„ธ๊ทธ๋จผํ…Œ์ด์…˜ ์‚ฌ์šฉ', field: 'enableSegmentation' }),
823
+ new controls.Toggle({ title: '์„ธ๊ทธ๋จผํ…Œ์ด์…˜ ํ‰ํ™œํ™”', field: 'smoothSegmentation' }),
824
+ new controls.Slider({
825
+ title: '์ตœ์†Œ ๊ฐ์ง€ ์‹ ๋ขฐ๋„',
826
+ field: 'minDetectionConfidence',
827
+ range: [0, 1],
828
+ step: 0.01
829
+ }),
830
+ new controls.Slider({
831
+ title: '์ตœ์†Œ ์ถ”์  ์‹ ๋ขฐ๋„',
832
+ field: 'minTrackingConfidence',
833
+ range: [0, 1],
834
+ step: 0.01
835
+ }),
836
+ new controls.Slider({
837
+ title: 'ํšจ๊ณผ',
838
+ field: 'effect',
839
+ discrete: { 'background': '๋ฐฐ๊ฒฝ', 'mask': '์ „๊ฒฝ' },
840
+ }),
841
+ ])
842
+ .on(x => {
843
+ const options = x;
844
+ videoElement.classList.toggle('selfie', options.selfieMode);
845
+ activeEffect = x['effect'];
846
+ holistic.setOptions(options);
847
+ });
848
+
849
+ // ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜
850
+ function initialize() {
851
+ // ์ฐจํŠธ ์ดˆ๊ธฐํ™”
852
+ initCharts();
853
+
854
+ // ๋น„๋””์˜ค ์—…๋กœ๋“œ ์ฒ˜๋ฆฌ
855
+ const videoUploadInput = document.querySelector('#video-upload');
856
+ if (videoUploadInput) {
857
+ videoUploadInput.addEventListener('change', (e) => {
858
+ if (e.target.files.length > 0) {
859
+ handleVideoUpload(e.target.files[0]);
860
+ }
861
+ });
862
+ }
863
+
864
+ // ์ž์„ธ ๊ฐ์ง€ ์ดˆ๊ธฐํ™”
865
+ const holistic = new mpHolistic.Holistic(config);
866
+ holistic.onResults(onResults);
867
+
868
+ // ... ๊ธฐ์กด ์ดˆ๊ธฐํ™” ๋กœ์ง์„ ์œ ์ง€ ...
869
+ }
870
+
871
+ // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘
872
+ window.addEventListener('load', initialize);
873
+
874
+ // ์ฐฝ ํฌ๊ธฐ ๋ณ€๊ฒฝ ์ฒ˜๋ฆฌ
875
+ window.addEventListener('resize', () => {
876
+ const aspect = videoElement.videoHeight / videoElement.videoWidth;
877
+ let width, height;
878
+ if (window.innerWidth > window.innerHeight) {
879
+ height = window.innerHeight;
880
+ width = height / aspect;
881
+ } else {
882
+ width = window.innerWidth;
883
+ height = width * aspect;
884
+ }
885
+ canvasElement.width = width;
886
+ canvasElement.height = height;
887
+
888
+ // ์ฐจํŠธ ํฌ๊ธฐ ์žฌ์กฐ์ •
889
+ speedChart.resize();
890
+ accelerationChart.resize();
891
+ });