@map-gesture-controls/core 0.1.3
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/dist/GestureController.d.ts +32 -0
- package/dist/GestureController.js +159 -0
- package/dist/GestureStateMachine.d.ts +35 -0
- package/dist/GestureStateMachine.js +243 -0
- package/dist/GestureStateMachine.test.d.ts +1 -0
- package/dist/GestureStateMachine.test.js +163 -0
- package/dist/WebcamOverlay.d.ts +30 -0
- package/dist/WebcamOverlay.js +194 -0
- package/dist/constants.d.ts +26 -0
- package/dist/constants.js +54 -0
- package/dist/gestureClassifier.d.ts +22 -0
- package/dist/gestureClassifier.js +110 -0
- package/dist/gestureClassifier.test.d.ts +1 -0
- package/dist/gestureClassifier.test.js +98 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +360 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
var w = Object.defineProperty;
|
|
2
|
+
var b = (o, t, e) => t in o ? w(o, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[t] = e;
|
|
3
|
+
var a = (o, t, e) => b(o, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import { FilesetResolver as E } from "@mediapipe/tasks-vision";
|
|
5
|
+
const L = {
|
|
6
|
+
enabled: !0,
|
|
7
|
+
mode: "corner",
|
|
8
|
+
opacity: 0.85,
|
|
9
|
+
position: "bottom-right",
|
|
10
|
+
width: 320,
|
|
11
|
+
height: 240
|
|
12
|
+
}, H = {
|
|
13
|
+
actionDwellMs: 80,
|
|
14
|
+
releaseGraceMs: 150,
|
|
15
|
+
panDeadzonePx: 10,
|
|
16
|
+
zoomDeadzoneRatio: 5e-3,
|
|
17
|
+
smoothingAlpha: 0.5,
|
|
18
|
+
minDetectionConfidence: 0.65,
|
|
19
|
+
minTrackingConfidence: 0.65,
|
|
20
|
+
minPresenceConfidence: 0.6
|
|
21
|
+
}, h = {
|
|
22
|
+
WRIST: 0,
|
|
23
|
+
THUMB_TIP: 4,
|
|
24
|
+
INDEX_TIP: 8,
|
|
25
|
+
INDEX_MCP: 5,
|
|
26
|
+
MIDDLE_TIP: 12,
|
|
27
|
+
MIDDLE_MCP: 9,
|
|
28
|
+
RING_TIP: 16,
|
|
29
|
+
RING_MCP: 13,
|
|
30
|
+
PINKY_TIP: 20,
|
|
31
|
+
PINKY_MCP: 17
|
|
32
|
+
}, I = [
|
|
33
|
+
h.INDEX_TIP,
|
|
34
|
+
h.MIDDLE_TIP,
|
|
35
|
+
h.RING_TIP,
|
|
36
|
+
h.PINKY_TIP
|
|
37
|
+
], y = [
|
|
38
|
+
h.INDEX_MCP,
|
|
39
|
+
h.MIDDLE_MCP,
|
|
40
|
+
h.RING_MCP,
|
|
41
|
+
h.PINKY_MCP
|
|
42
|
+
], T = {
|
|
43
|
+
idle: "#888888",
|
|
44
|
+
panning: "#00ccff",
|
|
45
|
+
zooming: "#00ffcc",
|
|
46
|
+
landmark: "rgba(255,255,255,0.6)",
|
|
47
|
+
connection: "rgba(255,255,255,0.3)",
|
|
48
|
+
fingertipGlow: "#4488ff"
|
|
49
|
+
}, C = "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14/wasm";
|
|
50
|
+
function g(o, t) {
|
|
51
|
+
const e = o.x - t.x, n = o.y - t.y;
|
|
52
|
+
return Math.sqrt(e * e + n * n);
|
|
53
|
+
}
|
|
54
|
+
function M(o) {
|
|
55
|
+
const t = o[h.WRIST];
|
|
56
|
+
for (let i = 0; i < I.length; i++) {
|
|
57
|
+
const d = o[I[i]], c = o[y[i]];
|
|
58
|
+
if (g(d, t) < g(c, t) * 0.9)
|
|
59
|
+
return !1;
|
|
60
|
+
}
|
|
61
|
+
const e = N(o);
|
|
62
|
+
if (e === 0) return !1;
|
|
63
|
+
const n = I.map((i) => o[i]);
|
|
64
|
+
let s = 1 / 0;
|
|
65
|
+
for (let i = 0; i < n.length - 1; i++) {
|
|
66
|
+
const d = g(n[i], n[i + 1]);
|
|
67
|
+
d < s && (s = d);
|
|
68
|
+
}
|
|
69
|
+
return s >= e * 0.18;
|
|
70
|
+
}
|
|
71
|
+
function _(o) {
|
|
72
|
+
const t = o[h.WRIST];
|
|
73
|
+
let e = 0;
|
|
74
|
+
for (let n = 0; n < I.length; n++) {
|
|
75
|
+
const s = o[I[n]], l = o[y[n]];
|
|
76
|
+
g(s, t) < g(l, t) * 1.1 && e++;
|
|
77
|
+
}
|
|
78
|
+
return e >= 3;
|
|
79
|
+
}
|
|
80
|
+
function N(o) {
|
|
81
|
+
const t = o[h.WRIST], e = o[h.MIDDLE_MCP];
|
|
82
|
+
return !t || !e ? 0 : g(t, e);
|
|
83
|
+
}
|
|
84
|
+
function S(o, t) {
|
|
85
|
+
const e = o[h.INDEX_TIP], n = t[h.INDEX_TIP];
|
|
86
|
+
return !e || !n ? 0 : g(e, n);
|
|
87
|
+
}
|
|
88
|
+
function x(o) {
|
|
89
|
+
return o.length < 21 ? "none" : _(o) ? "fist" : M(o) ? "openPalm" : "none";
|
|
90
|
+
}
|
|
91
|
+
class k {
|
|
92
|
+
constructor(t, e) {
|
|
93
|
+
a(this, "landmarker", null);
|
|
94
|
+
a(this, "videoEl", null);
|
|
95
|
+
a(this, "stream", null);
|
|
96
|
+
a(this, "rafHandle", null);
|
|
97
|
+
a(this, "running", !1);
|
|
98
|
+
a(this, "onFrame");
|
|
99
|
+
a(this, "tuning");
|
|
100
|
+
a(this, "lastVideoTime", -1);
|
|
101
|
+
this.tuning = t, this.onFrame = e;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Initialise MediaPipe and request webcam access.
|
|
105
|
+
* Returns the video element so the overlay can render it.
|
|
106
|
+
*/
|
|
107
|
+
async init() {
|
|
108
|
+
const t = await E.forVisionTasks(C), { HandLandmarker: e } = await import("@mediapipe/tasks-vision");
|
|
109
|
+
return this.landmarker = await e.createFromOptions(t, {
|
|
110
|
+
baseOptions: {
|
|
111
|
+
modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task",
|
|
112
|
+
delegate: "GPU"
|
|
113
|
+
},
|
|
114
|
+
runningMode: "VIDEO",
|
|
115
|
+
numHands: 2,
|
|
116
|
+
minHandDetectionConfidence: this.tuning.minDetectionConfidence,
|
|
117
|
+
minHandPresenceConfidence: this.tuning.minPresenceConfidence,
|
|
118
|
+
minTrackingConfidence: this.tuning.minTrackingConfidence
|
|
119
|
+
}), 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({
|
|
120
|
+
video: { width: 640, height: 480, facingMode: "user" }
|
|
121
|
+
}), this.videoEl.srcObject = this.stream, await new Promise((n) => {
|
|
122
|
+
this.videoEl.addEventListener("loadeddata", () => n(), { once: !0 });
|
|
123
|
+
}), this.videoEl;
|
|
124
|
+
}
|
|
125
|
+
start() {
|
|
126
|
+
this.running || (this.running = !0, this.loop());
|
|
127
|
+
}
|
|
128
|
+
stop() {
|
|
129
|
+
this.running = !1, this.rafHandle !== null && (cancelAnimationFrame(this.rafHandle), this.rafHandle = null);
|
|
130
|
+
}
|
|
131
|
+
destroy() {
|
|
132
|
+
var t, e;
|
|
133
|
+
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;
|
|
134
|
+
}
|
|
135
|
+
loop() {
|
|
136
|
+
this.running && (this.rafHandle = requestAnimationFrame(() => this.loop()), this.processFrame());
|
|
137
|
+
}
|
|
138
|
+
processFrame() {
|
|
139
|
+
const t = this.videoEl, e = this.landmarker;
|
|
140
|
+
if (!t || !e || t.readyState < 2) return;
|
|
141
|
+
const n = performance.now();
|
|
142
|
+
if (t.currentTime === this.lastVideoTime) return;
|
|
143
|
+
this.lastVideoTime = t.currentTime;
|
|
144
|
+
let s;
|
|
145
|
+
try {
|
|
146
|
+
s = e.detectForVideo(t, n);
|
|
147
|
+
} catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const l = this.buildFrame(s, n);
|
|
151
|
+
this.onFrame(l);
|
|
152
|
+
}
|
|
153
|
+
buildFrame(t, e) {
|
|
154
|
+
const n = t.landmarks.map((i, d) => {
|
|
155
|
+
var f, v;
|
|
156
|
+
const c = t.handedness[d], u = ((f = c == null ? void 0 : c[0]) == null ? void 0 : f.categoryName) === "Left" ? "Left" : "Right", m = ((v = c == null ? void 0 : c[0]) == null ? void 0 : v.score) ?? 0, p = x(i);
|
|
157
|
+
return { handedness: u, score: m, landmarks: i, gesture: p };
|
|
158
|
+
}), s = n.find((i) => i.handedness === "Left") ?? null, l = n.find((i) => i.handedness === "Right") ?? null;
|
|
159
|
+
return { timestamp: e, hands: n, leftHand: s, rightHand: l };
|
|
160
|
+
}
|
|
161
|
+
getVideoElement() {
|
|
162
|
+
return this.videoEl;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
class R {
|
|
166
|
+
constructor(t) {
|
|
167
|
+
a(this, "value", null);
|
|
168
|
+
this.alpha = t;
|
|
169
|
+
}
|
|
170
|
+
update(t, e) {
|
|
171
|
+
return this.value === null ? this.value = { x: t, y: e } : this.value = {
|
|
172
|
+
x: this.alpha * t + (1 - this.alpha) * this.value.x,
|
|
173
|
+
y: this.alpha * e + (1 - this.alpha) * this.value.y
|
|
174
|
+
}, this.value;
|
|
175
|
+
}
|
|
176
|
+
reset() {
|
|
177
|
+
this.value = null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
class F {
|
|
181
|
+
constructor(t) {
|
|
182
|
+
a(this, "value", null);
|
|
183
|
+
this.alpha = t;
|
|
184
|
+
}
|
|
185
|
+
update(t) {
|
|
186
|
+
return this.value === null ? this.value = t : this.value = this.alpha * t + (1 - this.alpha) * this.value, this.value;
|
|
187
|
+
}
|
|
188
|
+
reset() {
|
|
189
|
+
this.value = null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
class B {
|
|
193
|
+
constructor(t) {
|
|
194
|
+
a(this, "mode", "idle");
|
|
195
|
+
a(this, "actionDwell", null);
|
|
196
|
+
a(this, "releaseTimer", null);
|
|
197
|
+
a(this, "panSmoother");
|
|
198
|
+
a(this, "prevPanPos", null);
|
|
199
|
+
a(this, "zoomSmoother");
|
|
200
|
+
a(this, "prevZoomDist", null);
|
|
201
|
+
this.tuning = t, this.panSmoother = new R(t.smoothingAlpha), this.zoomSmoother = new F(t.smoothingAlpha);
|
|
202
|
+
}
|
|
203
|
+
getMode() {
|
|
204
|
+
return this.mode;
|
|
205
|
+
}
|
|
206
|
+
update(t) {
|
|
207
|
+
const e = t.timestamp, { actionDwellMs: n, releaseGraceMs: s } = this.tuning, { leftHand: l, rightHand: i } = t, d = l !== null && i !== null && l.gesture === "openPalm" && i.gesture === "openPalm", c = l !== null && l.gesture === "fist" && i === null || i !== null && i.gesture === "fist" && l === null, r = d ? "zooming" : c ? "panning" : "idle";
|
|
208
|
+
if (this.mode === "idle")
|
|
209
|
+
return r !== "idle" ? this.actionDwell === null || this.actionDwell.gesture !== r ? this.actionDwell = { gesture: r, startMs: e } : e - this.actionDwell.startMs >= n && this.transitionTo(r) : this.actionDwell = null, this.buildOutput(null, null);
|
|
210
|
+
if (this.mode === "panning") {
|
|
211
|
+
if (r !== "panning")
|
|
212
|
+
return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= s && this.transitionTo("idle"), this.buildOutput(null, null);
|
|
213
|
+
this.releaseTimer = null;
|
|
214
|
+
const u = (l == null ? void 0 : l.gesture) === "fist" ? l : i;
|
|
215
|
+
if (!u)
|
|
216
|
+
return this.transitionTo("idle"), this.buildOutput(null, null);
|
|
217
|
+
const m = u.landmarks[0], p = this.panSmoother.update(m.x, m.y);
|
|
218
|
+
let f = null;
|
|
219
|
+
if (this.prevPanPos !== null) {
|
|
220
|
+
const v = p.x - this.prevPanPos.x, P = p.y - this.prevPanPos.y, D = this.tuning.panDeadzonePx / 640;
|
|
221
|
+
(Math.abs(v) > D || Math.abs(P) > D) && (f = { x: v, y: P });
|
|
222
|
+
}
|
|
223
|
+
return this.prevPanPos = p, this.buildOutput(f, null);
|
|
224
|
+
}
|
|
225
|
+
if (this.mode === "zooming") {
|
|
226
|
+
if (r !== "zooming")
|
|
227
|
+
return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= s && this.transitionTo("idle"), this.buildOutput(null, null);
|
|
228
|
+
if (this.releaseTimer = null, !l || !i)
|
|
229
|
+
return this.transitionTo("idle"), this.buildOutput(null, null);
|
|
230
|
+
const u = S(l.landmarks, i.landmarks), m = this.zoomSmoother.update(u);
|
|
231
|
+
let p = null;
|
|
232
|
+
if (this.prevZoomDist !== null) {
|
|
233
|
+
const f = m - this.prevZoomDist;
|
|
234
|
+
Math.abs(f) > this.tuning.zoomDeadzoneRatio && (p = f);
|
|
235
|
+
}
|
|
236
|
+
return this.prevZoomDist = m, this.buildOutput(null, p);
|
|
237
|
+
}
|
|
238
|
+
return this.buildOutput(null, null);
|
|
239
|
+
}
|
|
240
|
+
transitionTo(t) {
|
|
241
|
+
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);
|
|
242
|
+
}
|
|
243
|
+
buildOutput(t, e) {
|
|
244
|
+
return { mode: this.mode, panDelta: t, zoomDelta: e };
|
|
245
|
+
}
|
|
246
|
+
reset() {
|
|
247
|
+
this.transitionTo("idle");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const O = [
|
|
251
|
+
[0, 1],
|
|
252
|
+
[1, 2],
|
|
253
|
+
[2, 3],
|
|
254
|
+
[3, 4],
|
|
255
|
+
// thumb
|
|
256
|
+
[0, 5],
|
|
257
|
+
[5, 6],
|
|
258
|
+
[6, 7],
|
|
259
|
+
[7, 8],
|
|
260
|
+
// index
|
|
261
|
+
[0, 9],
|
|
262
|
+
[9, 10],
|
|
263
|
+
[10, 11],
|
|
264
|
+
[11, 12],
|
|
265
|
+
// middle
|
|
266
|
+
[0, 13],
|
|
267
|
+
[13, 14],
|
|
268
|
+
[14, 15],
|
|
269
|
+
[15, 16],
|
|
270
|
+
// ring
|
|
271
|
+
[0, 17],
|
|
272
|
+
[17, 18],
|
|
273
|
+
[18, 19],
|
|
274
|
+
[19, 20],
|
|
275
|
+
// pinky
|
|
276
|
+
[5, 9],
|
|
277
|
+
[9, 13],
|
|
278
|
+
[13, 17]
|
|
279
|
+
// palm cross
|
|
280
|
+
], z = [
|
|
281
|
+
h.THUMB_TIP,
|
|
282
|
+
h.INDEX_TIP,
|
|
283
|
+
h.MIDDLE_TIP,
|
|
284
|
+
h.RING_TIP,
|
|
285
|
+
h.PINKY_TIP
|
|
286
|
+
];
|
|
287
|
+
class U {
|
|
288
|
+
constructor(t) {
|
|
289
|
+
a(this, "container");
|
|
290
|
+
a(this, "canvas");
|
|
291
|
+
a(this, "ctx");
|
|
292
|
+
a(this, "badge");
|
|
293
|
+
a(this, "config");
|
|
294
|
+
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.container.appendChild(this.canvas), this.container.appendChild(this.badge);
|
|
295
|
+
const e = this.canvas.getContext("2d");
|
|
296
|
+
if (!e) throw new Error("Cannot get 2D canvas context");
|
|
297
|
+
this.ctx = e;
|
|
298
|
+
}
|
|
299
|
+
/** Attach video element produced by GestureController */
|
|
300
|
+
attachVideo(t) {
|
|
301
|
+
t.className = "ol-gesture-video", t.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;transform:scaleX(-1);", this.container.insertBefore(t, this.canvas);
|
|
302
|
+
}
|
|
303
|
+
/** Mount the overlay into the given parent (usually document.body or map container) */
|
|
304
|
+
mount(t) {
|
|
305
|
+
t.appendChild(this.container);
|
|
306
|
+
}
|
|
307
|
+
unmount() {
|
|
308
|
+
var t;
|
|
309
|
+
(t = this.container.parentElement) == null || t.removeChild(this.container);
|
|
310
|
+
}
|
|
311
|
+
/** Called each frame with the latest gesture frame and mode. */
|
|
312
|
+
render(t, e) {
|
|
313
|
+
this.updateBadge(e);
|
|
314
|
+
const n = this.config.width, s = this.config.height;
|
|
315
|
+
if (this.canvas.width = n, this.canvas.height = s, this.ctx.clearRect(0, 0, n, s), t !== null)
|
|
316
|
+
for (const l of t.hands)
|
|
317
|
+
this.drawSkeleton(l.landmarks, e, l.gesture === "fist");
|
|
318
|
+
}
|
|
319
|
+
drawSkeleton(t, e, n) {
|
|
320
|
+
const { ctx: s } = this, l = this.config.width, i = this.config.height, d = (r) => (1 - r.x) * l, c = (r) => r.y * i;
|
|
321
|
+
s.strokeStyle = T.connection, s.lineWidth = 1.5;
|
|
322
|
+
for (const [r, u] of O)
|
|
323
|
+
!t[r] || !t[u] || (s.beginPath(), s.moveTo(d(t[r]), c(t[r])), s.lineTo(d(t[u]), c(t[u])), s.stroke());
|
|
324
|
+
for (let r = 0; r < t.length; r++) {
|
|
325
|
+
const u = t[r], m = z.includes(r), p = e !== "idle" && m ? T.fingertipGlow : T.landmark;
|
|
326
|
+
s.beginPath(), s.arc(d(u), c(u), m ? 5 : 3, 0, Math.PI * 2), s.fillStyle = p, s.fill(), e !== "idle" && m && (s.shadowBlur = n ? 12 : 6, s.shadowColor = T.fingertipGlow, s.fill(), s.shadowBlur = 0);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
updateBadge(t) {
|
|
330
|
+
const e = {
|
|
331
|
+
idle: "Idle",
|
|
332
|
+
panning: "Pan",
|
|
333
|
+
zooming: "Zoom"
|
|
334
|
+
};
|
|
335
|
+
this.badge.textContent = e[t], this.badge.className = `ol-gesture-badge ol-gesture-badge--${t}`;
|
|
336
|
+
}
|
|
337
|
+
applyContainerStyles() {
|
|
338
|
+
const { mode: t, position: e, width: n, height: s, opacity: l } = this.config;
|
|
339
|
+
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(l), this.container.style.display = t === "hidden" ? "none" : "block", t === "corner") {
|
|
340
|
+
this.container.style.width = `${n}px`, this.container.style.height = `${s}px`;
|
|
341
|
+
const i = "16px";
|
|
342
|
+
e === "bottom-right" ? (this.container.style.bottom = i, this.container.style.right = i) : e === "bottom-left" ? (this.container.style.bottom = i, this.container.style.left = i) : e === "top-right" ? (this.container.style.top = i, this.container.style.right = i) : (this.container.style.top = i, this.container.style.left = i);
|
|
343
|
+
} 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");
|
|
344
|
+
}
|
|
345
|
+
updateConfig(t) {
|
|
346
|
+
Object.assign(this.config, t), this.applyContainerStyles();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
export {
|
|
350
|
+
T as COLORS,
|
|
351
|
+
H as DEFAULT_TUNING_CONFIG,
|
|
352
|
+
L as DEFAULT_WEBCAM_CONFIG,
|
|
353
|
+
k as GestureController,
|
|
354
|
+
B as GestureStateMachine,
|
|
355
|
+
h as LANDMARKS,
|
|
356
|
+
U as WebcamOverlay,
|
|
357
|
+
x as classifyGesture,
|
|
358
|
+
N as getHandSize,
|
|
359
|
+
S as getTwoHandDistance
|
|
360
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type GestureMode = 'idle' | 'panning' | 'zooming';
|
|
2
|
+
export type HandednessLabel = 'Left' | 'Right';
|
|
3
|
+
export type GestureType = 'openPalm' | 'fist' | 'none';
|
|
4
|
+
export interface Point2D {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
export interface HandLandmark {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
z: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DetectedHand {
|
|
14
|
+
handedness: HandednessLabel;
|
|
15
|
+
score: number;
|
|
16
|
+
landmarks: HandLandmark[];
|
|
17
|
+
gesture: GestureType;
|
|
18
|
+
}
|
|
19
|
+
export interface GestureFrame {
|
|
20
|
+
timestamp: number;
|
|
21
|
+
hands: DetectedHand[];
|
|
22
|
+
leftHand: DetectedHand | null;
|
|
23
|
+
rightHand: DetectedHand | null;
|
|
24
|
+
}
|
|
25
|
+
export interface WebcamConfig {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
/** 'full' | 'corner' | 'hidden' */
|
|
28
|
+
mode: 'full' | 'corner' | 'hidden';
|
|
29
|
+
opacity: number;
|
|
30
|
+
position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}
|
|
34
|
+
export interface TuningConfig {
|
|
35
|
+
actionDwellMs: number;
|
|
36
|
+
releaseGraceMs: number;
|
|
37
|
+
panDeadzonePx: number;
|
|
38
|
+
zoomDeadzoneRatio: number;
|
|
39
|
+
smoothingAlpha: number;
|
|
40
|
+
minDetectionConfidence: number;
|
|
41
|
+
minTrackingConfidence: number;
|
|
42
|
+
minPresenceConfidence: number;
|
|
43
|
+
}
|
|
44
|
+
export interface SmoothedPoint {
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@map-gesture-controls/core",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Map-agnostic hand gesture detection and state machine (MediaPipe)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./style.css": "./dist/style.css"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build:lib": "tsc -p tsconfig.lib.json && vite build --config vite.lib.config.ts",
|
|
21
|
+
"type-check": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@mediapipe/tasks-vision": "^0.10.14"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.4.5",
|
|
28
|
+
"vite": "^5.2.11"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mediapipe",
|
|
32
|
+
"hand-gestures",
|
|
33
|
+
"gesture-detection"
|
|
34
|
+
],
|
|
35
|
+
"overrides": {
|
|
36
|
+
"esbuild": "^0.25.0"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT"
|
|
39
|
+
}
|