@map-gesture-controls/core 0.1.8 → 0.2.0
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/README.md +14 -5
- package/dist/GestureController.d.ts +2 -0
- package/dist/GestureController.js +16 -2
- package/dist/GestureStateMachine.d.ts +3 -0
- package/dist/GestureStateMachine.js +68 -11
- package/dist/WebcamOverlay.d.ts +6 -2
- package/dist/WebcamOverlay.js +40 -4
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +6 -0
- package/dist/gestureClassifier.d.ts +18 -3
- package/dist/gestureClassifier.js +73 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +162 -118
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/GestureStateMachine.test.d.ts +0 -1
- package/dist/GestureStateMachine.test.js +0 -232
- package/dist/gestureClassifier.test.d.ts +0 -1
- package/dist/gestureClassifier.test.js +0 -98
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var l = (
|
|
4
|
-
import { FilesetResolver as
|
|
5
|
-
const
|
|
1
|
+
var O = Object.defineProperty;
|
|
2
|
+
var x = (i, t, e) => t in i ? O(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
|
|
3
|
+
var l = (i, t, e) => x(i, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import { FilesetResolver as H } from "@mediapipe/tasks-vision";
|
|
5
|
+
const Y = {
|
|
6
6
|
enabled: !0,
|
|
7
7
|
mode: "corner",
|
|
8
8
|
opacity: 0.85,
|
|
9
9
|
position: "bottom-right",
|
|
10
10
|
width: 320,
|
|
11
11
|
height: 240
|
|
12
|
-
},
|
|
12
|
+
}, Z = {
|
|
13
13
|
actionDwellMs: 80,
|
|
14
14
|
releaseGraceMs: 150,
|
|
15
15
|
panDeadzonePx: 10,
|
|
@@ -29,17 +29,17 @@ const L = {
|
|
|
29
29
|
RING_MCP: 13,
|
|
30
30
|
PINKY_TIP: 20,
|
|
31
31
|
PINKY_MCP: 17
|
|
32
|
-
},
|
|
32
|
+
}, L = 0.25, z = 0.35, b = [
|
|
33
33
|
r.INDEX_TIP,
|
|
34
34
|
r.MIDDLE_TIP,
|
|
35
35
|
r.RING_TIP,
|
|
36
36
|
r.PINKY_TIP
|
|
37
|
-
],
|
|
37
|
+
], N = [
|
|
38
38
|
r.INDEX_MCP,
|
|
39
39
|
r.MIDDLE_MCP,
|
|
40
40
|
r.RING_MCP,
|
|
41
41
|
r.PINKY_MCP
|
|
42
|
-
],
|
|
42
|
+
], w = {
|
|
43
43
|
idle: "#888888",
|
|
44
44
|
panning: "#00ccff",
|
|
45
45
|
zooming: "#00ffcc",
|
|
@@ -47,49 +47,67 @@ const L = {
|
|
|
47
47
|
landmark: "rgba(255,255,255,0.6)",
|
|
48
48
|
connection: "rgba(255,255,255,0.3)",
|
|
49
49
|
fingertipGlow: "#4488ff"
|
|
50
|
-
},
|
|
51
|
-
function T(
|
|
52
|
-
const e =
|
|
53
|
-
return Math.sqrt(e * e +
|
|
50
|
+
}, B = "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14/wasm";
|
|
51
|
+
function T(i, t) {
|
|
52
|
+
const e = i.x - t.x, n = i.y - t.y;
|
|
53
|
+
return Math.sqrt(e * e + n * n);
|
|
54
54
|
}
|
|
55
|
-
function
|
|
56
|
-
const t =
|
|
57
|
-
for (let
|
|
58
|
-
const
|
|
59
|
-
if (T(
|
|
55
|
+
function _(i) {
|
|
56
|
+
const t = i[r.WRIST];
|
|
57
|
+
for (let s = 0; s < b.length; s++) {
|
|
58
|
+
const u = i[b[s]], c = i[N[s]];
|
|
59
|
+
if (T(u, t) < T(c, t) * 0.9)
|
|
60
60
|
return !1;
|
|
61
61
|
}
|
|
62
|
-
const e =
|
|
62
|
+
const e = D(i);
|
|
63
63
|
if (e === 0) return !1;
|
|
64
|
-
const
|
|
65
|
-
let
|
|
66
|
-
for (let
|
|
67
|
-
const
|
|
68
|
-
|
|
64
|
+
const n = b.map((s) => i[s]);
|
|
65
|
+
let o = 1 / 0;
|
|
66
|
+
for (let s = 0; s < n.length - 1; s++) {
|
|
67
|
+
const u = T(n[s], n[s + 1]);
|
|
68
|
+
u < o && (o = u);
|
|
69
69
|
}
|
|
70
|
-
return
|
|
70
|
+
return o >= e * 0.18;
|
|
71
71
|
}
|
|
72
|
-
function
|
|
73
|
-
const t =
|
|
72
|
+
function R(i) {
|
|
73
|
+
const t = i[r.WRIST];
|
|
74
74
|
let e = 0;
|
|
75
|
-
for (let
|
|
76
|
-
const
|
|
77
|
-
T(
|
|
75
|
+
for (let n = 0; n < b.length; n++) {
|
|
76
|
+
const o = i[b[n]], a = i[N[n]];
|
|
77
|
+
T(o, t) < T(a, t) * 1.1 && e++;
|
|
78
78
|
}
|
|
79
79
|
return e >= 3;
|
|
80
80
|
}
|
|
81
|
-
function
|
|
82
|
-
const t =
|
|
81
|
+
function D(i) {
|
|
82
|
+
const t = i[r.WRIST], e = i[r.MIDDLE_MCP];
|
|
83
83
|
return !t || !e ? 0 : T(t, e);
|
|
84
84
|
}
|
|
85
|
-
function k(
|
|
86
|
-
const e =
|
|
87
|
-
return !e || !
|
|
85
|
+
function k(i, t) {
|
|
86
|
+
const e = i[r.INDEX_TIP], n = t[r.INDEX_TIP];
|
|
87
|
+
return !e || !n ? 0 : T(e, n);
|
|
88
88
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
89
|
+
function F(i) {
|
|
90
|
+
const t = D(i);
|
|
91
|
+
if (t === 0) return !1;
|
|
92
|
+
const e = i[r.THUMB_TIP], n = i[r.INDEX_TIP];
|
|
93
|
+
return T(e, n) < t * L;
|
|
91
94
|
}
|
|
92
|
-
|
|
95
|
+
function G(i) {
|
|
96
|
+
const t = D(i);
|
|
97
|
+
if (t === 0) return !1;
|
|
98
|
+
const e = i[r.THUMB_TIP], n = i[r.INDEX_TIP];
|
|
99
|
+
return T(e, n) < t * z;
|
|
100
|
+
}
|
|
101
|
+
function j(i) {
|
|
102
|
+
return i.length < 21 ? "none" : R(i) ? "fist" : F(i) ? "pinch" : _(i) ? "openPalm" : "none";
|
|
103
|
+
}
|
|
104
|
+
function M() {
|
|
105
|
+
let i = !1;
|
|
106
|
+
return function(e) {
|
|
107
|
+
return e.length < 21 ? (i = !1, "none") : R(e) ? (i = !1, "fist") : (i ? i = G(e) : i = F(e), i ? "pinch" : _(e) ? "openPalm" : "none");
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
class $ {
|
|
93
111
|
constructor(t, e) {
|
|
94
112
|
l(this, "landmarker", null);
|
|
95
113
|
l(this, "videoEl", null);
|
|
@@ -99,6 +117,9 @@ class B {
|
|
|
99
117
|
l(this, "onFrame");
|
|
100
118
|
l(this, "tuning");
|
|
101
119
|
l(this, "lastVideoTime", -1);
|
|
120
|
+
// One stateful classifier per hand label; persists pinch hysteresis across frames.
|
|
121
|
+
l(this, "leftClassifier", M());
|
|
122
|
+
l(this, "rightClassifier", M());
|
|
102
123
|
this.tuning = t, this.onFrame = e;
|
|
103
124
|
}
|
|
104
125
|
/**
|
|
@@ -106,7 +127,7 @@ class B {
|
|
|
106
127
|
* Returns the video element so the overlay can render it.
|
|
107
128
|
*/
|
|
108
129
|
async init() {
|
|
109
|
-
const t = await
|
|
130
|
+
const t = await H.forVisionTasks(B), { HandLandmarker: e } = await import("@mediapipe/tasks-vision");
|
|
110
131
|
return this.landmarker = await e.createFromOptions(t, {
|
|
111
132
|
baseOptions: {
|
|
112
133
|
modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task",
|
|
@@ -119,8 +140,8 @@ class B {
|
|
|
119
140
|
minTrackingConfidence: this.tuning.minTrackingConfidence
|
|
120
141
|
}), this.videoEl = document.createElement("video"), this.videoEl.setAttribute("playsinline", ""), this.videoEl.setAttribute("autoplay", ""), this.videoEl.muted = !0, this.videoEl.width = 640, this.videoEl.height = 480, this.stream = await navigator.mediaDevices.getUserMedia({
|
|
121
142
|
video: { width: 640, height: 480, facingMode: "user" }
|
|
122
|
-
}), this.videoEl.srcObject = this.stream, await new Promise((
|
|
123
|
-
this.videoEl.addEventListener("loadeddata", () =>
|
|
143
|
+
}), this.videoEl.srcObject = this.stream, await new Promise((n) => {
|
|
144
|
+
this.videoEl.addEventListener("loadeddata", () => n(), { once: !0 });
|
|
124
145
|
}), this.videoEl;
|
|
125
146
|
}
|
|
126
147
|
start() {
|
|
@@ -131,7 +152,7 @@ class B {
|
|
|
131
152
|
}
|
|
132
153
|
destroy() {
|
|
133
154
|
var t, e;
|
|
134
|
-
this.stop(), (t = this.stream) == null || t.getTracks().forEach((
|
|
155
|
+
this.stop(), (t = this.stream) == null || t.getTracks().forEach((n) => n.stop()), (e = this.landmarker) == null || e.close(), this.landmarker = null, this.videoEl = null, this.stream = null;
|
|
135
156
|
}
|
|
136
157
|
loop() {
|
|
137
158
|
this.running && (this.rafHandle = requestAnimationFrame(() => this.loop()), this.processFrame());
|
|
@@ -139,31 +160,31 @@ class B {
|
|
|
139
160
|
processFrame() {
|
|
140
161
|
const t = this.videoEl, e = this.landmarker;
|
|
141
162
|
if (!t || !e || t.readyState < 2) return;
|
|
142
|
-
const
|
|
163
|
+
const n = performance.now();
|
|
143
164
|
if (t.currentTime === this.lastVideoTime) return;
|
|
144
165
|
this.lastVideoTime = t.currentTime;
|
|
145
|
-
let
|
|
166
|
+
let o;
|
|
146
167
|
try {
|
|
147
|
-
|
|
168
|
+
o = e.detectForVideo(t, n);
|
|
148
169
|
} catch {
|
|
149
170
|
return;
|
|
150
171
|
}
|
|
151
|
-
const a = this.buildFrame(
|
|
172
|
+
const a = this.buildFrame(o, n);
|
|
152
173
|
this.onFrame(a);
|
|
153
174
|
}
|
|
154
175
|
buildFrame(t, e) {
|
|
155
|
-
const
|
|
176
|
+
const n = t.landmarks.map((s, u) => {
|
|
156
177
|
var p, g;
|
|
157
|
-
const c = t.handedness[
|
|
158
|
-
return { handedness:
|
|
159
|
-
}),
|
|
160
|
-
return { timestamp: e, hands:
|
|
178
|
+
const c = t.handedness[u], d = ((p = c == null ? void 0 : c[0]) == null ? void 0 : p.categoryName) === "Left" ? "Left" : "Right", P = ((g = c == null ? void 0 : c[0]) == null ? void 0 : g.score) ?? 0, f = (d === "Left" ? this.leftClassifier : this.rightClassifier)(s);
|
|
179
|
+
return { handedness: d, score: P, landmarks: s, gesture: f };
|
|
180
|
+
}), o = n.find((s) => s.handedness === "Left") ?? null, a = n.find((s) => s.handedness === "Right") ?? null;
|
|
181
|
+
return { timestamp: e, hands: n, leftHand: o, rightHand: a };
|
|
161
182
|
}
|
|
162
183
|
getVideoElement() {
|
|
163
184
|
return this.videoEl;
|
|
164
185
|
}
|
|
165
186
|
}
|
|
166
|
-
class
|
|
187
|
+
class U {
|
|
167
188
|
constructor(t) {
|
|
168
189
|
l(this, "value", null);
|
|
169
190
|
this.alpha = t;
|
|
@@ -178,7 +199,7 @@ class A {
|
|
|
178
199
|
this.value = null;
|
|
179
200
|
}
|
|
180
201
|
}
|
|
181
|
-
class
|
|
202
|
+
class C {
|
|
182
203
|
constructor(t) {
|
|
183
204
|
l(this, "value", null);
|
|
184
205
|
this.alpha = t;
|
|
@@ -190,7 +211,7 @@ class D {
|
|
|
190
211
|
this.value = null;
|
|
191
212
|
}
|
|
192
213
|
}
|
|
193
|
-
class
|
|
214
|
+
const E = class E {
|
|
194
215
|
constructor(t) {
|
|
195
216
|
l(this, "mode", "idle");
|
|
196
217
|
l(this, "actionDwell", null);
|
|
@@ -201,69 +222,83 @@ class U {
|
|
|
201
222
|
l(this, "prevZoomDist", null);
|
|
202
223
|
l(this, "rotateSmoother");
|
|
203
224
|
l(this, "prevRotateAngle", null);
|
|
204
|
-
|
|
225
|
+
// Tracks how many consecutive frames each hand has been active.
|
|
226
|
+
// Used to require the secondary hand to be stable before escalating
|
|
227
|
+
// the mode (e.g. pan → rotate), preventing a single noisy frame from
|
|
228
|
+
// interrupting an ongoing single-hand gesture.
|
|
229
|
+
l(this, "leftActiveFrames", 0);
|
|
230
|
+
l(this, "rightActiveFrames", 0);
|
|
231
|
+
this.tuning = t, this.panSmoother = new U(t.smoothingAlpha), this.zoomSmoother = new C(t.smoothingAlpha), this.rotateSmoother = new C(t.smoothingAlpha);
|
|
205
232
|
}
|
|
206
233
|
getMode() {
|
|
207
234
|
return this.mode;
|
|
208
235
|
}
|
|
209
236
|
update(t) {
|
|
210
|
-
const e = t.timestamp, { actionDwellMs:
|
|
237
|
+
const e = t.timestamp, { actionDwellMs: n, releaseGraceMs: o } = this.tuning, { leftHand: a, rightHand: s } = t, u = (f) => f !== null && (f.gesture === "fist" || f.gesture === "pinch"), c = u(s), h = u(a);
|
|
238
|
+
this.leftActiveFrames = h ? this.leftActiveFrames + 1 : 0, this.rightActiveFrames = c ? this.rightActiveFrames + 1 : 0;
|
|
239
|
+
const d = this.leftActiveFrames >= E.ESCALATION_FRAMES && this.rightActiveFrames >= E.ESCALATION_FRAMES, m = d ? "rotating" : h && c && !d && (this.mode === "panning" || this.mode === "zooming") ? this.mode : c ? "zooming" : h ? "panning" : "idle";
|
|
211
240
|
if (this.mode === "idle")
|
|
212
|
-
return
|
|
241
|
+
return m !== "idle" ? this.actionDwell === null || this.actionDwell.gesture !== m ? this.actionDwell = { gesture: m, startMs: e } : e - this.actionDwell.startMs >= n && this.transitionTo(m) : this.actionDwell = null, this.buildOutput(null, null, null);
|
|
213
242
|
if (this.mode === "panning") {
|
|
214
|
-
if (
|
|
215
|
-
return this.
|
|
243
|
+
if (d)
|
|
244
|
+
return this.transitionTo("rotating"), this.buildOutput(null, null, null);
|
|
245
|
+
if (m !== "panning")
|
|
246
|
+
return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= o && this.transitionTo("idle"), this.buildOutput(null, null, null);
|
|
216
247
|
this.releaseTimer = null;
|
|
217
|
-
const
|
|
218
|
-
if (!
|
|
248
|
+
const f = u(a) ? a : null;
|
|
249
|
+
if (!f)
|
|
219
250
|
return this.transitionTo("idle"), this.buildOutput(null, null, null);
|
|
220
|
-
const
|
|
221
|
-
let
|
|
251
|
+
const p = f.landmarks[0], g = this.panSmoother.update(p.x, p.y);
|
|
252
|
+
let I = null;
|
|
222
253
|
if (this.prevPanPos !== null) {
|
|
223
|
-
const
|
|
224
|
-
(Math.abs(
|
|
254
|
+
const y = g.x - this.prevPanPos.x, v = g.y - this.prevPanPos.y, A = this.tuning.panDeadzonePx / 640;
|
|
255
|
+
(Math.abs(y) > A || Math.abs(v) > A) && (I = { x: y, y: v });
|
|
225
256
|
}
|
|
226
|
-
return this.prevPanPos =
|
|
257
|
+
return this.prevPanPos = g, this.buildOutput(I, null, null);
|
|
227
258
|
}
|
|
228
259
|
if (this.mode === "zooming") {
|
|
229
|
-
if (
|
|
230
|
-
return this.
|
|
231
|
-
if (
|
|
260
|
+
if (d)
|
|
261
|
+
return this.transitionTo("rotating"), this.buildOutput(null, null, null);
|
|
262
|
+
if (m !== "zooming")
|
|
263
|
+
return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= o && this.transitionTo("idle"), this.buildOutput(null, null, null);
|
|
264
|
+
if (this.releaseTimer = null, !s)
|
|
232
265
|
return this.transitionTo("idle"), this.buildOutput(null, null, null);
|
|
233
|
-
const
|
|
234
|
-
let
|
|
266
|
+
const f = s.landmarks[0], p = this.zoomSmoother.update(f.y);
|
|
267
|
+
let g = null;
|
|
235
268
|
if (this.prevZoomDist !== null) {
|
|
236
|
-
const
|
|
237
|
-
Math.abs(
|
|
269
|
+
const I = p - this.prevZoomDist;
|
|
270
|
+
Math.abs(I) > this.tuning.zoomDeadzoneRatio && (g = -I);
|
|
238
271
|
}
|
|
239
|
-
return this.prevZoomDist =
|
|
272
|
+
return this.prevZoomDist = p, this.buildOutput(null, g, null);
|
|
240
273
|
}
|
|
241
274
|
if (this.mode === "rotating") {
|
|
242
|
-
if (
|
|
243
|
-
return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >=
|
|
244
|
-
if (this.releaseTimer = null, !a || !
|
|
275
|
+
if (m !== "rotating")
|
|
276
|
+
return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= o && this.transitionTo("idle"), this.buildOutput(null, null, null);
|
|
277
|
+
if (this.releaseTimer = null, !a || !s)
|
|
245
278
|
return this.transitionTo("idle"), this.buildOutput(null, null, null);
|
|
246
|
-
const
|
|
247
|
-
let
|
|
279
|
+
const f = a.landmarks[0], p = s.landmarks[0], g = Math.atan2(p.y - f.y, p.x - f.x), I = this.rotateSmoother.update(g);
|
|
280
|
+
let y = null;
|
|
248
281
|
if (this.prevRotateAngle !== null) {
|
|
249
|
-
let v =
|
|
250
|
-
v > Math.PI && (v -= 2 * Math.PI), v < -Math.PI && (v += 2 * Math.PI), Math.abs(v) > 5e-3 && (
|
|
282
|
+
let v = I - this.prevRotateAngle;
|
|
283
|
+
v > Math.PI && (v -= 2 * Math.PI), v < -Math.PI && (v += 2 * Math.PI), Math.abs(v) > 5e-3 && (y = v);
|
|
251
284
|
}
|
|
252
|
-
return this.prevRotateAngle =
|
|
285
|
+
return this.prevRotateAngle = I, this.buildOutput(null, null, y);
|
|
253
286
|
}
|
|
254
287
|
return this.buildOutput(null, null, null);
|
|
255
288
|
}
|
|
256
289
|
transitionTo(t) {
|
|
257
|
-
this.mode = t, this.releaseTimer = null, this.actionDwell = null, t !== "panning" && (this.panSmoother.reset(), this.prevPanPos = null), t !== "zooming" && (this.zoomSmoother.reset(), this.prevZoomDist = null), t !== "rotating" && (this.rotateSmoother.reset(), this.prevRotateAngle = null);
|
|
290
|
+
this.mode = t, this.releaseTimer = null, this.actionDwell = null, t !== "panning" && t !== "rotating" && (this.leftActiveFrames = 0), t !== "zooming" && t !== "rotating" && (this.rightActiveFrames = 0), t !== "panning" && (this.panSmoother.reset(), this.prevPanPos = null), t !== "zooming" && (this.zoomSmoother.reset(), this.prevZoomDist = null), t !== "rotating" && (this.rotateSmoother.reset(), this.prevRotateAngle = null);
|
|
258
291
|
}
|
|
259
|
-
buildOutput(t, e,
|
|
260
|
-
return { mode: this.mode, panDelta: t, zoomDelta: e, rotateDelta:
|
|
292
|
+
buildOutput(t, e, n) {
|
|
293
|
+
return { mode: this.mode, panDelta: t, zoomDelta: e, rotateDelta: n };
|
|
261
294
|
}
|
|
262
295
|
reset() {
|
|
263
296
|
this.transitionTo("idle");
|
|
264
297
|
}
|
|
265
|
-
}
|
|
266
|
-
|
|
298
|
+
};
|
|
299
|
+
l(E, "ESCALATION_FRAMES", 3);
|
|
300
|
+
let S = E;
|
|
301
|
+
const X = [
|
|
267
302
|
[0, 1],
|
|
268
303
|
[1, 2],
|
|
269
304
|
[2, 3],
|
|
@@ -293,21 +328,24 @@ const F = [
|
|
|
293
328
|
[9, 13],
|
|
294
329
|
[13, 17]
|
|
295
330
|
// palm cross
|
|
296
|
-
],
|
|
331
|
+
], V = [
|
|
297
332
|
r.THUMB_TIP,
|
|
298
333
|
r.INDEX_TIP,
|
|
299
334
|
r.MIDDLE_TIP,
|
|
300
335
|
r.RING_TIP,
|
|
301
336
|
r.PINKY_TIP
|
|
302
337
|
];
|
|
303
|
-
class
|
|
338
|
+
class q {
|
|
304
339
|
constructor(t) {
|
|
305
340
|
l(this, "container");
|
|
306
341
|
l(this, "canvas");
|
|
307
342
|
l(this, "ctx");
|
|
308
343
|
l(this, "badge");
|
|
344
|
+
l(this, "resetBar");
|
|
345
|
+
l(this, "resetFill");
|
|
309
346
|
l(this, "config");
|
|
310
|
-
|
|
347
|
+
l(this, "lastMode", null);
|
|
348
|
+
this.config = t, this.container = document.createElement("div"), this.container.className = "ol-gesture-overlay", this.applyContainerStyles(), this.canvas = document.createElement("canvas"), this.canvas.className = "ol-gesture-canvas", this.canvas.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;", this.badge = document.createElement("div"), this.badge.className = "ol-gesture-badge ol-gesture-badge--idle", this.badge.textContent = "Idle", this.resetBar = document.createElement("div"), this.resetBar.className = "ol-gesture-reset", this.resetBar.innerHTML = '<span class="ol-gesture-reset-label">Reset</span><div class="ol-gesture-reset-track"><div class="ol-gesture-reset-fill"></div></div>', this.resetBar.style.opacity = "0", this.resetFill = this.resetBar.querySelector(".ol-gesture-reset-fill"), this.container.appendChild(this.canvas), this.container.appendChild(this.badge), this.container.appendChild(this.resetBar);
|
|
311
349
|
const e = this.canvas.getContext("2d");
|
|
312
350
|
if (!e) throw new Error("Cannot get 2D canvas context");
|
|
313
351
|
this.ctx = e;
|
|
@@ -324,25 +362,30 @@ class V {
|
|
|
324
362
|
var t;
|
|
325
363
|
(t = this.container.parentElement) == null || t.removeChild(this.container);
|
|
326
364
|
}
|
|
327
|
-
/** Called each frame with the latest gesture frame and
|
|
328
|
-
render(t, e) {
|
|
329
|
-
this.updateBadge(e);
|
|
330
|
-
const
|
|
331
|
-
if (this.canvas.width =
|
|
332
|
-
for (const
|
|
333
|
-
this.drawSkeleton(
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
365
|
+
/** Called each frame with the latest gesture frame, mode, and optional reset progress (0 to 1). */
|
|
366
|
+
render(t, e, n = 0) {
|
|
367
|
+
this.updateBadge(e), this.updateResetBar(n);
|
|
368
|
+
const o = this.config.width, a = this.config.height;
|
|
369
|
+
if ((this.canvas.width !== o || this.canvas.height !== a) && (this.canvas.width = o, this.canvas.height = a), this.ctx.clearRect(0, 0, o, a), t !== null)
|
|
370
|
+
for (const s of t.hands)
|
|
371
|
+
this.drawSkeleton(s.landmarks, e, s.gesture === "fist");
|
|
372
|
+
}
|
|
373
|
+
updateResetBar(t) {
|
|
374
|
+
this.resetFill.style.width = `${t * 100}%`, this.resetBar.style.opacity = t > 0 ? "1" : "0";
|
|
375
|
+
}
|
|
376
|
+
drawSkeleton(t, e, n) {
|
|
377
|
+
const { ctx: o } = this, a = this.config.width, s = this.config.height, u = (h) => (1 - h.x) * a, c = (h) => h.y * s;
|
|
378
|
+
o.strokeStyle = w.connection, o.lineWidth = 1.5;
|
|
379
|
+
for (const [h, d] of X)
|
|
380
|
+
!t[h] || !t[d] || (o.beginPath(), o.moveTo(u(t[h]), c(t[h])), o.lineTo(u(t[d]), c(t[d])), o.stroke());
|
|
381
|
+
for (let h = 0; h < t.length; h++) {
|
|
382
|
+
const d = t[h], P = V.includes(h), m = e !== "idle" && P ? w.fingertipGlow : w.landmark;
|
|
383
|
+
o.beginPath(), o.arc(u(d), c(d), P ? 5 : 3, 0, Math.PI * 2), o.fillStyle = m, o.fill(), e !== "idle" && P && (o.shadowBlur = n ? 12 : 6, o.shadowColor = w.fingertipGlow, o.fill(), o.shadowBlur = 0);
|
|
343
384
|
}
|
|
344
385
|
}
|
|
345
386
|
updateBadge(t) {
|
|
387
|
+
if (t === this.lastMode) return;
|
|
388
|
+
this.lastMode = t;
|
|
346
389
|
const e = {
|
|
347
390
|
idle: "Idle",
|
|
348
391
|
panning: "Pan",
|
|
@@ -352,11 +395,11 @@ class V {
|
|
|
352
395
|
this.badge.textContent = e[t], this.badge.className = `ol-gesture-badge ol-gesture-badge--${t}`;
|
|
353
396
|
}
|
|
354
397
|
applyContainerStyles() {
|
|
355
|
-
const { mode: t, position: e, width:
|
|
398
|
+
const { mode: t, position: e, width: n, height: o, opacity: a } = this.config;
|
|
356
399
|
if (this.container.style.cssText = "", this.container.style.position = "fixed", this.container.style.zIndex = "9999", this.container.style.overflow = "hidden", this.container.style.borderRadius = "8px", this.container.style.opacity = String(a), this.container.style.display = t === "hidden" ? "none" : "block", t === "corner") {
|
|
357
|
-
this.container.style.width = `${
|
|
358
|
-
const
|
|
359
|
-
e === "bottom-right" ? (this.container.style.bottom =
|
|
400
|
+
this.container.style.width = `${n}px`, this.container.style.height = `${o}px`;
|
|
401
|
+
const s = "16px";
|
|
402
|
+
e === "bottom-right" ? (this.container.style.bottom = s, this.container.style.right = s) : e === "bottom-left" ? (this.container.style.bottom = s, this.container.style.left = s) : e === "top-right" ? (this.container.style.top = s, this.container.style.right = s) : (this.container.style.top = s, this.container.style.left = s);
|
|
360
403
|
} else t === "full" && (this.container.style.top = "0", this.container.style.left = "0", this.container.style.width = "100vw", this.container.style.height = "100vh", this.container.style.borderRadius = "0");
|
|
361
404
|
}
|
|
362
405
|
updateConfig(t) {
|
|
@@ -364,14 +407,15 @@ class V {
|
|
|
364
407
|
}
|
|
365
408
|
}
|
|
366
409
|
export {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
410
|
+
w as COLORS,
|
|
411
|
+
Z as DEFAULT_TUNING_CONFIG,
|
|
412
|
+
Y as DEFAULT_WEBCAM_CONFIG,
|
|
413
|
+
$ as GestureController,
|
|
414
|
+
S as GestureStateMachine,
|
|
372
415
|
r as LANDMARKS,
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
416
|
+
q as WebcamOverlay,
|
|
417
|
+
j as classifyGesture,
|
|
418
|
+
M as createHandClassifier,
|
|
419
|
+
D as getHandSize,
|
|
376
420
|
k as getTwoHandDistance
|
|
377
421
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type GestureMode = 'idle' | 'panning' | 'zooming' | 'rotating';
|
|
2
2
|
export type HandednessLabel = 'Left' | 'Right';
|
|
3
|
-
export type GestureType = 'openPalm' | 'fist' | 'none';
|
|
3
|
+
export type GestureType = 'openPalm' | 'fist' | 'pinch' | 'none';
|
|
4
4
|
export interface Point2D {
|
|
5
5
|
x: number;
|
|
6
6
|
y: number;
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|