mudra-skills 1.0.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/.claude-plugin/plugin.json +19 -0
- package/CLAUDE.md +35 -0
- package/bin/install.js +25 -0
- package/package.json +28 -0
- package/skills/mudra-master/SKILL.md +202 -0
- package/skills/mudra-master/mudra-preview/SKILL.md +151 -0
- package/skills/mudra-master/mudra-preview/assets/ar-menu.html +382 -0
- package/skills/mudra-master/mudra-preview/assets/document-scroller.html +468 -0
- package/skills/mudra-master/mudra-preview/assets/drum-machine.html +788 -0
- package/skills/mudra-master/mudra-preview/assets/emg-visualizer.html +390 -0
- package/skills/mudra-master/mudra-preview/assets/generative-art.html +408 -0
- package/skills/mudra-master/mudra-preview/assets/gesture-assistant.html +342 -0
- package/skills/mudra-master/mudra-preview/assets/gesture-speech.html +444 -0
- package/skills/mudra-master/mudra-preview/assets/hands-free-desktop.html +376 -0
- package/skills/mudra-master/mudra-preview/assets/model-rotator.html +365 -0
- package/skills/mudra-master/mudra-preview/assets/mudra-duel.html +3200 -0
- package/skills/mudra-master/mudra-preview/assets/mudra-monitor.html +1451 -0
- package/skills/mudra-master/mudra-preview/assets/mudra-ultimate-template.html +891 -0
- package/skills/mudra-master/mudra-preview/assets/music-sequencer.html +455 -0
- package/skills/mudra-master/mudra-preview/assets/neural-pong.html +380 -0
- package/skills/mudra-master/mudra-preview/assets/neural-snake.html +364 -0
- package/skills/mudra-master/mudra-preview/assets/presentation-controller.html +345 -0
- package/skills/mudra-master/mudra-preview/assets/pressure-painter.html +285 -0
- package/skills/mudra-master/mudra-preview/assets/runner.html +1812 -0
- package/skills/mudra-master/mudra-preview/assets/smart-home.html +461 -0
- package/skills/mudra-master/mudra-preview/assets/space-invaders.html +1154 -0
- package/skills/mudra-master/mudra-preview/assets/waterful-ring-toss.html +1624 -0
- package/skills/mudra-master/mudra-preview/references/agent_protocol.json +646 -0
- package/skills/mudra-master/mudra-preview/references/promt.md +16829 -0
- package/skills/mudra-master/mudra-xr/SKILL.md +245 -0
- package/skills/mudra-master/mudra-xr/assets/demos/3dgs-walkthrough.html +242 -0
- package/skills/mudra-master/mudra-xr/assets/demos/aisimulator.html +792 -0
- package/skills/mudra-master/mudra-xr/assets/demos/balloonpop.html +422 -0
- package/skills/mudra-master/mudra-xr/assets/demos/ballpit.html +284 -0
- package/skills/mudra-master/mudra-xr/assets/demos/drone.html +41 -0
- package/skills/mudra-master/mudra-xr/assets/demos/gemini-icebreakers.html +338 -0
- package/skills/mudra-master/mudra-xr/assets/demos/gemini-xrobject.html +302 -0
- package/skills/mudra-master/mudra-xr/assets/demos/math3d.html +188 -0
- package/skills/mudra-master/mudra-xr/assets/demos/measure.html +247 -0
- package/skills/mudra-master/mudra-xr/assets/demos/occlusion.html +251 -0
- package/skills/mudra-master/mudra-xr/assets/demos/rain.html +498 -0
- package/skills/mudra-master/mudra-xr/assets/demos/screenwiper.html +432 -0
- package/skills/mudra-master/mudra-xr/assets/demos/splash.html +519 -0
- package/skills/mudra-master/mudra-xr/assets/demos/webcam_gestures.html +253 -0
- package/skills/mudra-master/mudra-xr/assets/demos/xremoji.html +833 -0
- package/skills/mudra-master/mudra-xr/assets/demos/xrpoet.html +151 -0
- package/skills/mudra-master/mudra-xr/assets/samples/depthmap.html +266 -0
- package/skills/mudra-master/mudra-xr/assets/samples/depthmesh.html +100 -0
- package/skills/mudra-master/mudra-xr/assets/samples/game_rps.html +1099 -0
- package/skills/mudra-master/mudra-xr/assets/samples/gestures_custom.html +467 -0
- package/skills/mudra-master/mudra-xr/assets/samples/gestures_heuristic.html +256 -0
- package/skills/mudra-master/mudra-xr/assets/samples/lighting.html +163 -0
- package/skills/mudra-master/mudra-xr/assets/samples/mesh_detection.html +46 -0
- package/skills/mudra-master/mudra-xr/assets/samples/modelviewer.html +183 -0
- package/skills/mudra-master/mudra-xr/assets/samples/paint.html +133 -0
- package/skills/mudra-master/mudra-xr/assets/samples/planar-vst.html +45 -0
- package/skills/mudra-master/mudra-xr/assets/samples/reticle.html +95 -0
- package/skills/mudra-master/mudra-xr/assets/samples/skybox_agent.html +384 -0
- package/skills/mudra-master/mudra-xr/assets/samples/sound.html +390 -0
- package/skills/mudra-master/mudra-xr/assets/samples/ui.html +496 -0
- package/skills/mudra-master/mudra-xr/assets/samples/virtual-screens.html +808 -0
- package/skills/mudra-master/mudra-xr/assets/templates/0_basic.html +64 -0
- package/skills/mudra-master/mudra-xr/assets/templates/1_ui.html +107 -0
- package/skills/mudra-master/mudra-xr/assets/templates/2_hands.html +183 -0
- package/skills/mudra-master/mudra-xr/assets/templates/3_depth.html +89 -0
- package/skills/mudra-master/mudra-xr/assets/templates/4_stereo.html +84 -0
- package/skills/mudra-master/mudra-xr/assets/templates/5_camera.html +117 -0
- package/skills/mudra-master/mudra-xr/assets/templates/6_ai.html +154 -0
- package/skills/mudra-master/mudra-xr/assets/templates/7_ai_live.html +253 -0
- package/skills/mudra-master/mudra-xr/assets/templates/8_objects.html +95 -0
- package/skills/mudra-master/mudra-xr/assets/templates/9_xr-toggle.html +90 -0
- package/skills/mudra-master/mudra-xr/assets/templates/heuristic_hand_gestures.html +102 -0
- package/skills/mudra-master/mudra-xr/assets/templates/meshes.html +47 -0
- package/skills/mudra-master/mudra-xr/assets/templates/planes.html +47 -0
- package/skills/mudra-master/mudra-xr/assets/templates/uikit.html +187 -0
- package/skills/mudra-master/mudra-xr/references/agent_protocol.json +878 -0
- package/skills/mudra-master/mudra-xr/references/promt.md +1516 -0
- package/skills/mudra-preview/SKILL.md +151 -0
- package/skills/mudra-preview/assets/ar-menu.html +382 -0
- package/skills/mudra-preview/assets/document-scroller.html +468 -0
- package/skills/mudra-preview/assets/drum-machine.html +788 -0
- package/skills/mudra-preview/assets/emg-visualizer.html +390 -0
- package/skills/mudra-preview/assets/generative-art.html +408 -0
- package/skills/mudra-preview/assets/gesture-assistant.html +342 -0
- package/skills/mudra-preview/assets/gesture-speech.html +444 -0
- package/skills/mudra-preview/assets/hands-free-desktop.html +376 -0
- package/skills/mudra-preview/assets/model-rotator.html +365 -0
- package/skills/mudra-preview/assets/mudra-duel.html +3200 -0
- package/skills/mudra-preview/assets/mudra-monitor.html +1451 -0
- package/skills/mudra-preview/assets/mudra-ultimate-template.html +891 -0
- package/skills/mudra-preview/assets/music-sequencer.html +455 -0
- package/skills/mudra-preview/assets/neural-pong.html +380 -0
- package/skills/mudra-preview/assets/neural-snake.html +364 -0
- package/skills/mudra-preview/assets/presentation-controller.html +345 -0
- package/skills/mudra-preview/assets/pressure-painter.html +285 -0
- package/skills/mudra-preview/assets/runner.html +1812 -0
- package/skills/mudra-preview/assets/smart-home.html +461 -0
- package/skills/mudra-preview/assets/space-invaders.html +1154 -0
- package/skills/mudra-preview/assets/waterful-ring-toss.html +1624 -0
- package/skills/mudra-preview/references/agent_protocol.json +646 -0
- package/skills/mudra-preview/references/promt.md +16829 -0
- package/skills/mudra-xr/SKILL.md +245 -0
- package/skills/mudra-xr/assets/demos/3dgs-walkthrough.html +242 -0
- package/skills/mudra-xr/assets/demos/aisimulator.html +792 -0
- package/skills/mudra-xr/assets/demos/balloonpop.html +422 -0
- package/skills/mudra-xr/assets/demos/ballpit.html +284 -0
- package/skills/mudra-xr/assets/demos/drone.html +41 -0
- package/skills/mudra-xr/assets/demos/gemini-icebreakers.html +338 -0
- package/skills/mudra-xr/assets/demos/gemini-xrobject.html +302 -0
- package/skills/mudra-xr/assets/demos/math3d.html +188 -0
- package/skills/mudra-xr/assets/demos/measure.html +247 -0
- package/skills/mudra-xr/assets/demos/occlusion.html +251 -0
- package/skills/mudra-xr/assets/demos/rain.html +498 -0
- package/skills/mudra-xr/assets/demos/screenwiper.html +432 -0
- package/skills/mudra-xr/assets/demos/splash.html +519 -0
- package/skills/mudra-xr/assets/demos/webcam_gestures.html +253 -0
- package/skills/mudra-xr/assets/demos/xremoji.html +833 -0
- package/skills/mudra-xr/assets/demos/xrpoet.html +151 -0
- package/skills/mudra-xr/assets/samples/depthmap.html +266 -0
- package/skills/mudra-xr/assets/samples/depthmesh.html +100 -0
- package/skills/mudra-xr/assets/samples/game_rps.html +1099 -0
- package/skills/mudra-xr/assets/samples/gestures_custom.html +467 -0
- package/skills/mudra-xr/assets/samples/gestures_heuristic.html +256 -0
- package/skills/mudra-xr/assets/samples/lighting.html +163 -0
- package/skills/mudra-xr/assets/samples/mesh_detection.html +46 -0
- package/skills/mudra-xr/assets/samples/modelviewer.html +183 -0
- package/skills/mudra-xr/assets/samples/paint.html +133 -0
- package/skills/mudra-xr/assets/samples/planar-vst.html +45 -0
- package/skills/mudra-xr/assets/samples/reticle.html +95 -0
- package/skills/mudra-xr/assets/samples/skybox_agent.html +384 -0
- package/skills/mudra-xr/assets/samples/sound.html +390 -0
- package/skills/mudra-xr/assets/samples/ui.html +496 -0
- package/skills/mudra-xr/assets/samples/virtual-screens.html +808 -0
- package/skills/mudra-xr/assets/templates/0_basic.html +64 -0
- package/skills/mudra-xr/assets/templates/1_ui.html +107 -0
- package/skills/mudra-xr/assets/templates/2_hands.html +183 -0
- package/skills/mudra-xr/assets/templates/3_depth.html +89 -0
- package/skills/mudra-xr/assets/templates/4_stereo.html +84 -0
- package/skills/mudra-xr/assets/templates/5_camera.html +117 -0
- package/skills/mudra-xr/assets/templates/6_ai.html +154 -0
- package/skills/mudra-xr/assets/templates/7_ai_live.html +253 -0
- package/skills/mudra-xr/assets/templates/8_objects.html +95 -0
- package/skills/mudra-xr/assets/templates/9_xr-toggle.html +90 -0
- package/skills/mudra-xr/assets/templates/heuristic_hand_gestures.html +102 -0
- package/skills/mudra-xr/assets/templates/meshes.html +47 -0
- package/skills/mudra-xr/assets/templates/planes.html +47 -0
- package/skills/mudra-xr/assets/templates/uikit.html +187 -0
- package/skills/mudra-xr/references/agent_protocol.json +878 -0
- package/skills/mudra-xr/references/promt.md +1516 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!-- reference: samples/gestures_custom -->
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<title>Custom Hand Gestures | XR Blocks</title>
|
|
6
|
+
|
|
7
|
+
<meta
|
|
8
|
+
name="viewport"
|
|
9
|
+
content="width=device-width, initial-scale=1.0, user-scalable=no"
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
<link type="text/css" rel="stylesheet" href="https://xrblocks.github.io/css/xr.css" />
|
|
13
|
+
<script>
|
|
14
|
+
window.litDisableBundleWarning = true;
|
|
15
|
+
</script>
|
|
16
|
+
<script type="importmap">
|
|
17
|
+
{
|
|
18
|
+
"imports": {
|
|
19
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.182.0/build/three.module.js",
|
|
20
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.182.0/examples/jsm/",
|
|
21
|
+
"troika-three-text": "https://cdn.jsdelivr.net/gh/protectwise/troika@028b81cf308f0f22e5aa8e78196be56ec1997af5/packages/troika-three-text/src/index.js",
|
|
22
|
+
"troika-three-utils": "https://cdn.jsdelivr.net/gh/protectwise/troika@v0.52.4/packages/troika-three-utils/src/index.js",
|
|
23
|
+
"troika-worker-utils": "https://cdn.jsdelivr.net/gh/protectwise/troika@v0.52.4/packages/troika-worker-utils/src/index.js",
|
|
24
|
+
"bidi-js": "https://esm.sh/bidi-js@%5E1.0.2?target=es2022",
|
|
25
|
+
"webgl-sdf-generator": "https://esm.sh/webgl-sdf-generator@1.1.1/es2022/webgl-sdf-generator.mjs",
|
|
26
|
+
"@tensorflow/tfjs-core": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core/+esm",
|
|
27
|
+
"@tensorflow/tfjs": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/+esm",
|
|
28
|
+
"@tensorflow/tfjs-backend-webgpu": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgpu/+esm",
|
|
29
|
+
"@litertjs/core": "https://unpkg.com/@litertjs/core@0.2.1",
|
|
30
|
+
"@litertjs/tfjs-interop": "https://unpkg.com/@litertjs/tfjs-interop@1.0.1",
|
|
31
|
+
"@litertjs/wasm-utils": "https://unpkg.com/@litertjs/wasm-utils@0.2.1",
|
|
32
|
+
"lit": "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js",
|
|
33
|
+
"lit/": "https://esm.run/lit@3/",
|
|
34
|
+
"xrblocks": "https://cdn.jsdelivr.net/npm/xrblocks@0.11.0/build/xrblocks.js",
|
|
35
|
+
"xrblocks/addons/": "https://cdn.jsdelivr.net/npm/xrblocks@0.11.0/build/addons/"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
</head>
|
|
40
|
+
|
|
41
|
+
<body>
|
|
42
|
+
<script type="module">
|
|
43
|
+
import 'xrblocks/addons/simulator/SimulatorAddons.js';
|
|
44
|
+
|
|
45
|
+
// Imports LiteRt: https://ai.google.dev/edge/litert/web/get_started
|
|
46
|
+
import {loadLiteRt, setWebGpuDevice} from '@litertjs/core';
|
|
47
|
+
import {runWithTfjsTensors} from '@litertjs/tfjs-interop';
|
|
48
|
+
// TensorFlow.js + WebGPU backend
|
|
49
|
+
import * as tf from '@tensorflow/tfjs';
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
51
|
+
import {WebGPUBackend} from '@tensorflow/tfjs-backend-webgpu';
|
|
52
|
+
import * as THREE from 'three';
|
|
53
|
+
import * as xb from 'xrblocks';
|
|
54
|
+
|
|
55
|
+
// --- Inlined: CustomGestureDemo.js ---
|
|
56
|
+
const GESTURE_LABELS = [
|
|
57
|
+
'OTHER',
|
|
58
|
+
'FIST',
|
|
59
|
+
'THUMB UP',
|
|
60
|
+
'THUMB DOWN',
|
|
61
|
+
'POINT',
|
|
62
|
+
'VICTORY',
|
|
63
|
+
'ROCK',
|
|
64
|
+
'SHAKA',
|
|
65
|
+
'GESTURE_LABEL_MAX_ENUM',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const GESTURE_IMAGES = [
|
|
69
|
+
'images/empty.png',
|
|
70
|
+
'images/fist.png',
|
|
71
|
+
'images/thumb.png',
|
|
72
|
+
'images/thumb_down.png',
|
|
73
|
+
'images/point.png',
|
|
74
|
+
'images/victory.png',
|
|
75
|
+
'images/rock.png',
|
|
76
|
+
'images/shaka.png',
|
|
77
|
+
'images/error.png',
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const LEFT_HAND_INDEX = 0;
|
|
81
|
+
const RIGHT_HAND_INDEX = 1;
|
|
82
|
+
|
|
83
|
+
const UNKNOWN_GESTURE = 8;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A demo scene that uses a custom ML model to detect and display static hand
|
|
87
|
+
* gestures for both hands in real-time.
|
|
88
|
+
*/
|
|
89
|
+
class CustomGestureDemo extends xb.Script {
|
|
90
|
+
constructor() {
|
|
91
|
+
super();
|
|
92
|
+
|
|
93
|
+
// Initializes UI.
|
|
94
|
+
{
|
|
95
|
+
// Make a root panel>grid>row>controlPanel>grid
|
|
96
|
+
const panel = new xb.SpatialPanel({backgroundColor: '#00000000'});
|
|
97
|
+
this.add(panel);
|
|
98
|
+
|
|
99
|
+
const grid = panel.addGrid();
|
|
100
|
+
|
|
101
|
+
// Show user data
|
|
102
|
+
const dataRow = grid.addRow({weight: 0.3});
|
|
103
|
+
// Left hand image and text
|
|
104
|
+
const leftCol = dataRow.addCol({weight: 0.5});
|
|
105
|
+
const leftHandRow = leftCol.addRow({weight: 0.5});
|
|
106
|
+
// Indentation
|
|
107
|
+
leftHandRow.addCol({weight: 0.4});
|
|
108
|
+
this.leftHandImage = leftHandRow.addCol({weight: 0.2}).addImage({
|
|
109
|
+
src: GESTURE_IMAGES[0],
|
|
110
|
+
scaleFactor: 0.3,
|
|
111
|
+
});
|
|
112
|
+
this.leftHandLabel = leftCol.addRow({weight: 0.5}).addText({
|
|
113
|
+
text: 'Loading...',
|
|
114
|
+
fontColor: '#ffffff',
|
|
115
|
+
});
|
|
116
|
+
const rightCol = dataRow.addCol({weight: 0.5});
|
|
117
|
+
const rightHandRow = rightCol.addRow({weight: 0.5});
|
|
118
|
+
// Indentation
|
|
119
|
+
rightHandRow.addCol({weight: 0.4});
|
|
120
|
+
// Image
|
|
121
|
+
this.rightHandImage = rightHandRow.addCol({weight: 0.2}).addImage({
|
|
122
|
+
src: GESTURE_IMAGES[0],
|
|
123
|
+
scaleFactor: 0.3,
|
|
124
|
+
});
|
|
125
|
+
this.rightHandLabel = rightCol.addRow({weight: 0.4}).addText({
|
|
126
|
+
text: 'Loading...',
|
|
127
|
+
fontColor: '#ffffff',
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Indentation
|
|
131
|
+
grid.addRow({weight: 0.1});
|
|
132
|
+
|
|
133
|
+
// Control row
|
|
134
|
+
const controlRow = grid.addRow({weight: 0.6});
|
|
135
|
+
const ctrlPanel = controlRow.addPanel({backgroundColor: '#00000055'});
|
|
136
|
+
const ctrlGrid = ctrlPanel.addGrid();
|
|
137
|
+
{
|
|
138
|
+
// Left indentation
|
|
139
|
+
ctrlGrid.addCol({weight: 0.1});
|
|
140
|
+
|
|
141
|
+
// Middle column
|
|
142
|
+
const midColumn = ctrlGrid.addCol({weight: 0.8});
|
|
143
|
+
|
|
144
|
+
midColumn.addRow({weight: 0.1});
|
|
145
|
+
midColumn.addRow({weight: 0.2}).addText({
|
|
146
|
+
text: 'Perform one of these gestures',
|
|
147
|
+
fontColor: '#ffffff',
|
|
148
|
+
});
|
|
149
|
+
midColumn
|
|
150
|
+
.addRow({weight: 0.2})
|
|
151
|
+
.addText({text: '(either hand):', fontColor: '#ffffff'});
|
|
152
|
+
const gesturesRow = midColumn.addRow({weight: 0.5});
|
|
153
|
+
gesturesRow.addCol({weight: 0.1});
|
|
154
|
+
gesturesRow
|
|
155
|
+
.addCol({weight: 0.1})
|
|
156
|
+
.addImage({src: 'images/fist.png', scaleFactor: 0.3});
|
|
157
|
+
gesturesRow
|
|
158
|
+
.addCol({weight: 0.1})
|
|
159
|
+
.addImage({src: 'images/thumb.png', scaleFactor: 0.3});
|
|
160
|
+
gesturesRow
|
|
161
|
+
.addCol({weight: 0.1})
|
|
162
|
+
.addImage({src: 'images/thumb_down.png', scaleFactor: 0.3});
|
|
163
|
+
gesturesRow
|
|
164
|
+
.addCol({weight: 0.1})
|
|
165
|
+
.addImage({src: 'images/point.png', scaleFactor: 0.3});
|
|
166
|
+
gesturesRow
|
|
167
|
+
.addCol({weight: 0.1})
|
|
168
|
+
.addImage({src: 'images/victory.png', scaleFactor: 0.3});
|
|
169
|
+
gesturesRow
|
|
170
|
+
.addCol({weight: 0.1})
|
|
171
|
+
.addImage({src: 'images/rock.png', scaleFactor: 0.3});
|
|
172
|
+
gesturesRow
|
|
173
|
+
.addCol({weight: 0.1})
|
|
174
|
+
.addImage({src: 'images/shaka.png', scaleFactor: 0.3});
|
|
175
|
+
|
|
176
|
+
// Vertical alignment on the description text element.
|
|
177
|
+
midColumn.addRow({weight: 0.1});
|
|
178
|
+
|
|
179
|
+
// Right indentation.
|
|
180
|
+
ctrlGrid.addCol({weight: 0.1});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const orbiter = ctrlGrid.addOrbiter();
|
|
184
|
+
orbiter.addExitButton();
|
|
185
|
+
|
|
186
|
+
panel.updateLayouts();
|
|
187
|
+
|
|
188
|
+
this.panel = panel;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Model
|
|
192
|
+
this.modelPath = './custom_gestures_model.tflite';
|
|
193
|
+
this.modelState = 'None';
|
|
194
|
+
|
|
195
|
+
this.frameId = 0;
|
|
196
|
+
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
this.setBackendAndLoadModel();
|
|
199
|
+
}, 1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
init() {
|
|
203
|
+
// Adds light.
|
|
204
|
+
this.add(new THREE.HemisphereLight(0x888877, 0x777788, 3));
|
|
205
|
+
const light = new THREE.DirectionalLight(0xffffff, 1.5);
|
|
206
|
+
light.position.set(0, 4, 0);
|
|
207
|
+
this.add(light);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async setBackendAndLoadModel() {
|
|
211
|
+
this.modelState = 'Loading';
|
|
212
|
+
try {
|
|
213
|
+
await tf.setBackend('webgpu');
|
|
214
|
+
await tf.ready();
|
|
215
|
+
|
|
216
|
+
// Initializes LiteRT.js's WASM files.
|
|
217
|
+
const wasmPath = 'https://unpkg.com/@litertjs/core@0.2.1/wasm/';
|
|
218
|
+
const liteRt = await loadLiteRt(wasmPath);
|
|
219
|
+
|
|
220
|
+
// Makes LiteRt use the same GPU device as TF.js (for tensor conversion).
|
|
221
|
+
const backend = tf.backend();
|
|
222
|
+
setWebGpuDevice(backend.device);
|
|
223
|
+
|
|
224
|
+
// Loads model via LiteRt.
|
|
225
|
+
await this.loadModel(liteRt);
|
|
226
|
+
|
|
227
|
+
if (this.model) {
|
|
228
|
+
// Prints model details to the log.
|
|
229
|
+
console.log('Model Details: ', this.model.getInputDetails());
|
|
230
|
+
}
|
|
231
|
+
this.modelState = 'Ready';
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('Failed to load model or backend:', error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async loadModel(liteRt) {
|
|
238
|
+
try {
|
|
239
|
+
this.model = await liteRt.loadAndCompile(this.modelPath, {
|
|
240
|
+
// Currently, only 'webgpu' is supported.
|
|
241
|
+
accelerator: 'webgpu',
|
|
242
|
+
});
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.model = null;
|
|
245
|
+
console.error('Error loading model:', error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
calculateRelativeHandBoneAngles(jointPositions) {
|
|
250
|
+
// Reshape jointPositions
|
|
251
|
+
let jointPositionsReshaped = [];
|
|
252
|
+
|
|
253
|
+
jointPositionsReshaped = jointPositions.reshape([xb.HAND_JOINT_COUNT, 3]);
|
|
254
|
+
|
|
255
|
+
// Calculate bone vectors
|
|
256
|
+
const boneVectors = [];
|
|
257
|
+
xb.HAND_JOINT_IDX_CONNECTION_MAP.forEach(([joint1, joint2]) => {
|
|
258
|
+
const boneVector = jointPositionsReshaped
|
|
259
|
+
.slice([joint2, 0], [1, 3])
|
|
260
|
+
.sub(jointPositionsReshaped.slice([joint1, 0], [1, 3]))
|
|
261
|
+
.squeeze();
|
|
262
|
+
const norm = boneVector.norm();
|
|
263
|
+
const normalizedBoneVector = boneVector.div(norm);
|
|
264
|
+
boneVectors.push(normalizedBoneVector);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Calculate relative hand bone angles
|
|
268
|
+
const relativeHandBoneAngles = [];
|
|
269
|
+
xb.HAND_BONE_IDX_CONNECTION_MAP.forEach(([bone1, bone2]) => {
|
|
270
|
+
const angle = boneVectors[bone1].dot(boneVectors[bone2]);
|
|
271
|
+
relativeHandBoneAngles.push(angle);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Stack the angles into a tensor.
|
|
275
|
+
return tf.stack(relativeHandBoneAngles);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async detectGesture(handJoints) {
|
|
279
|
+
if (!this.model || !handJoints || handJoints.length !== 25 * 3) {
|
|
280
|
+
console.log('Invalid hand joints or model load error.');
|
|
281
|
+
return UNKNOWN_GESTURE;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const tensor = this.calculateRelativeHandBoneAngles(
|
|
286
|
+
tf.tensor1d(handJoints)
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
let tensorReshaped = tensor.reshape([
|
|
290
|
+
1,
|
|
291
|
+
xb.HAND_BONE_IDX_CONNECTION_MAP.length,
|
|
292
|
+
1,
|
|
293
|
+
]);
|
|
294
|
+
var result = -1;
|
|
295
|
+
|
|
296
|
+
result = runWithTfjsTensors(this.model, tensorReshaped);
|
|
297
|
+
|
|
298
|
+
let integerLabel = result[0].as1D().arraySync();
|
|
299
|
+
if (integerLabel.length == 7) {
|
|
300
|
+
let x = integerLabel[0];
|
|
301
|
+
let idx = 0;
|
|
302
|
+
for (let t = 0; t < 7; ++t) {
|
|
303
|
+
if (integerLabel[t] > x) {
|
|
304
|
+
idx = t;
|
|
305
|
+
x = integerLabel[t];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return idx;
|
|
309
|
+
}
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error('Error:', error);
|
|
312
|
+
}
|
|
313
|
+
return UNKNOWN_GESTURE;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async #detectHandGestures(joints) {
|
|
317
|
+
if (Object.keys(joints).length !== 25) {
|
|
318
|
+
return UNKNOWN_GESTURE;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let handJointPositions = [];
|
|
322
|
+
for (const i in joints) {
|
|
323
|
+
handJointPositions.push(joints[i].position.x);
|
|
324
|
+
handJointPositions.push(joints[i].position.y);
|
|
325
|
+
handJointPositions.push(joints[i].position.z);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (handJointPositions.length !== 25 * 3) {
|
|
329
|
+
return UNKNOWN_GESTURE;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let result = await this.detectGesture(handJointPositions);
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
#shiftIndexIfNeeded(joints, result) {
|
|
337
|
+
// no need to shift before thumb which is 2
|
|
338
|
+
result += result > 2 ? 1 : 0;
|
|
339
|
+
// check thumb direction
|
|
340
|
+
if (result === 2) {
|
|
341
|
+
let tmp = this.isThumbUpOrDown(
|
|
342
|
+
joints['thumb-phalanx-distal'].position,
|
|
343
|
+
joints['thumb-tip'].position
|
|
344
|
+
);
|
|
345
|
+
// 1 -up; -1 down; 0 - other
|
|
346
|
+
result = tmp === 0 ? 0 : tmp < 0 ? result + 1 : result;
|
|
347
|
+
}
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async update() {
|
|
352
|
+
if (this.frameId % 5 === 0) {
|
|
353
|
+
const hands = xb.user.hands;
|
|
354
|
+
if (hands != null && hands.hands && hands.hands.length == 2) {
|
|
355
|
+
// Left hand.
|
|
356
|
+
const leftJoints = hands.hands[LEFT_HAND_INDEX].joints;
|
|
357
|
+
let leftHandResult = await this.#detectHandGestures(leftJoints);
|
|
358
|
+
leftHandResult = this.#shiftIndexIfNeeded(leftJoints, leftHandResult);
|
|
359
|
+
|
|
360
|
+
// Update image and label.
|
|
361
|
+
this.leftHandImage.load(GESTURE_IMAGES[leftHandResult]);
|
|
362
|
+
this.leftHandLabel.setText(GESTURE_LABELS[leftHandResult]);
|
|
363
|
+
|
|
364
|
+
// Right hand.
|
|
365
|
+
const rightJoints = hands.hands[RIGHT_HAND_INDEX].joints;
|
|
366
|
+
let rightHandResult = await this.#detectHandGestures(rightJoints);
|
|
367
|
+
rightHandResult = this.#shiftIndexIfNeeded(
|
|
368
|
+
rightJoints,
|
|
369
|
+
rightHandResult
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
// Update image and label.
|
|
373
|
+
this.rightHandImage.load(GESTURE_IMAGES[rightHandResult]);
|
|
374
|
+
this.rightHandLabel.setText(GESTURE_LABELS[rightHandResult]);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
this.frameId++;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
isThumbUpOrDown(p1, p2) {
|
|
381
|
+
// Assuming p1 is the base of the thumb and p2 is the tip.
|
|
382
|
+
|
|
383
|
+
// Vector from base to tip.
|
|
384
|
+
const vector = {
|
|
385
|
+
x: p2.x - p1.x,
|
|
386
|
+
y: p2.y - p1.y,
|
|
387
|
+
z: p2.z - p1.z,
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Calculate the magnitude of the vector.
|
|
391
|
+
const magnitude = Math.sqrt(
|
|
392
|
+
vector.x * vector.x + vector.y * vector.y + vector.z * vector.z
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
// If the magnitude is very small, it's likely not a significant gesture
|
|
396
|
+
if (magnitude < 0.001) {
|
|
397
|
+
return 0; // Otherwise
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Normalize the vector to get its direction.
|
|
401
|
+
const normalizedVector = {
|
|
402
|
+
x: vector.x / magnitude,
|
|
403
|
+
y: vector.y / magnitude,
|
|
404
|
+
z: vector.z / magnitude,
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Define the "up" and "down" direction vectors (positive and negative
|
|
408
|
+
// Y-axis)
|
|
409
|
+
const upVector = {x: 0, y: 1, z: 0};
|
|
410
|
+
const downVector = {x: 0, y: -1, z: 0};
|
|
411
|
+
|
|
412
|
+
// Angle threshold (cosine) for "up" (within 45 degrees of vertical)
|
|
413
|
+
const cosUpThreshold = Math.cos((45 * Math.PI) / 180); // Approximately 0.707
|
|
414
|
+
|
|
415
|
+
// Angle threshold (cosine) for "down" (within 45 degrees of negative
|
|
416
|
+
// vertical) We need the dot product with the *down* vector to be >= cos(45
|
|
417
|
+
// degrees)
|
|
418
|
+
const dotDownThreshold = cosUpThreshold;
|
|
419
|
+
|
|
420
|
+
// Calculates the dot product with the "up" vector.
|
|
421
|
+
const dotUp =
|
|
422
|
+
normalizedVector.x * upVector.x +
|
|
423
|
+
normalizedVector.y * upVector.y +
|
|
424
|
+
normalizedVector.z * upVector.z;
|
|
425
|
+
|
|
426
|
+
// Calculates the dot product with the "down" vector (negate the y component
|
|
427
|
+
// of normalized vector).
|
|
428
|
+
const dotDown =
|
|
429
|
+
normalizedVector.x * downVector.x +
|
|
430
|
+
normalizedVector.y * downVector.y +
|
|
431
|
+
normalizedVector.z * downVector.z;
|
|
432
|
+
|
|
433
|
+
if (dotUp >= cosUpThreshold) {
|
|
434
|
+
return 1; // Thumb up
|
|
435
|
+
} else if (dotDown >= dotDownThreshold) {
|
|
436
|
+
return -1; // Thumb down
|
|
437
|
+
} else {
|
|
438
|
+
return 0; // Otherwise
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// --- Main entry point ---
|
|
444
|
+
const options = new xb.Options({
|
|
445
|
+
antialias: true,
|
|
446
|
+
reticles: {enabled: true},
|
|
447
|
+
visualizeRays: false,
|
|
448
|
+
hands: {enabled: true, visualization: false},
|
|
449
|
+
simulator: {defaultMode: xb.SimulatorMode.POSE},
|
|
450
|
+
});
|
|
451
|
+
options.simulator.instructions.enabled = false;
|
|
452
|
+
|
|
453
|
+
async function start() {
|
|
454
|
+
options.setAppTitle('Custom Gestures');
|
|
455
|
+
xb.add(new CustomGestureDemo());
|
|
456
|
+
await xb.init(options);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
460
|
+
setTimeout(function () {
|
|
461
|
+
start();
|
|
462
|
+
}, 200);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
</script>
|
|
466
|
+
</body>
|
|
467
|
+
</html>
|