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.
Files changed (149) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/CLAUDE.md +35 -0
  3. package/bin/install.js +25 -0
  4. package/package.json +28 -0
  5. package/skills/mudra-master/SKILL.md +202 -0
  6. package/skills/mudra-master/mudra-preview/SKILL.md +151 -0
  7. package/skills/mudra-master/mudra-preview/assets/ar-menu.html +382 -0
  8. package/skills/mudra-master/mudra-preview/assets/document-scroller.html +468 -0
  9. package/skills/mudra-master/mudra-preview/assets/drum-machine.html +788 -0
  10. package/skills/mudra-master/mudra-preview/assets/emg-visualizer.html +390 -0
  11. package/skills/mudra-master/mudra-preview/assets/generative-art.html +408 -0
  12. package/skills/mudra-master/mudra-preview/assets/gesture-assistant.html +342 -0
  13. package/skills/mudra-master/mudra-preview/assets/gesture-speech.html +444 -0
  14. package/skills/mudra-master/mudra-preview/assets/hands-free-desktop.html +376 -0
  15. package/skills/mudra-master/mudra-preview/assets/model-rotator.html +365 -0
  16. package/skills/mudra-master/mudra-preview/assets/mudra-duel.html +3200 -0
  17. package/skills/mudra-master/mudra-preview/assets/mudra-monitor.html +1451 -0
  18. package/skills/mudra-master/mudra-preview/assets/mudra-ultimate-template.html +891 -0
  19. package/skills/mudra-master/mudra-preview/assets/music-sequencer.html +455 -0
  20. package/skills/mudra-master/mudra-preview/assets/neural-pong.html +380 -0
  21. package/skills/mudra-master/mudra-preview/assets/neural-snake.html +364 -0
  22. package/skills/mudra-master/mudra-preview/assets/presentation-controller.html +345 -0
  23. package/skills/mudra-master/mudra-preview/assets/pressure-painter.html +285 -0
  24. package/skills/mudra-master/mudra-preview/assets/runner.html +1812 -0
  25. package/skills/mudra-master/mudra-preview/assets/smart-home.html +461 -0
  26. package/skills/mudra-master/mudra-preview/assets/space-invaders.html +1154 -0
  27. package/skills/mudra-master/mudra-preview/assets/waterful-ring-toss.html +1624 -0
  28. package/skills/mudra-master/mudra-preview/references/agent_protocol.json +646 -0
  29. package/skills/mudra-master/mudra-preview/references/promt.md +16829 -0
  30. package/skills/mudra-master/mudra-xr/SKILL.md +245 -0
  31. package/skills/mudra-master/mudra-xr/assets/demos/3dgs-walkthrough.html +242 -0
  32. package/skills/mudra-master/mudra-xr/assets/demos/aisimulator.html +792 -0
  33. package/skills/mudra-master/mudra-xr/assets/demos/balloonpop.html +422 -0
  34. package/skills/mudra-master/mudra-xr/assets/demos/ballpit.html +284 -0
  35. package/skills/mudra-master/mudra-xr/assets/demos/drone.html +41 -0
  36. package/skills/mudra-master/mudra-xr/assets/demos/gemini-icebreakers.html +338 -0
  37. package/skills/mudra-master/mudra-xr/assets/demos/gemini-xrobject.html +302 -0
  38. package/skills/mudra-master/mudra-xr/assets/demos/math3d.html +188 -0
  39. package/skills/mudra-master/mudra-xr/assets/demos/measure.html +247 -0
  40. package/skills/mudra-master/mudra-xr/assets/demos/occlusion.html +251 -0
  41. package/skills/mudra-master/mudra-xr/assets/demos/rain.html +498 -0
  42. package/skills/mudra-master/mudra-xr/assets/demos/screenwiper.html +432 -0
  43. package/skills/mudra-master/mudra-xr/assets/demos/splash.html +519 -0
  44. package/skills/mudra-master/mudra-xr/assets/demos/webcam_gestures.html +253 -0
  45. package/skills/mudra-master/mudra-xr/assets/demos/xremoji.html +833 -0
  46. package/skills/mudra-master/mudra-xr/assets/demos/xrpoet.html +151 -0
  47. package/skills/mudra-master/mudra-xr/assets/samples/depthmap.html +266 -0
  48. package/skills/mudra-master/mudra-xr/assets/samples/depthmesh.html +100 -0
  49. package/skills/mudra-master/mudra-xr/assets/samples/game_rps.html +1099 -0
  50. package/skills/mudra-master/mudra-xr/assets/samples/gestures_custom.html +467 -0
  51. package/skills/mudra-master/mudra-xr/assets/samples/gestures_heuristic.html +256 -0
  52. package/skills/mudra-master/mudra-xr/assets/samples/lighting.html +163 -0
  53. package/skills/mudra-master/mudra-xr/assets/samples/mesh_detection.html +46 -0
  54. package/skills/mudra-master/mudra-xr/assets/samples/modelviewer.html +183 -0
  55. package/skills/mudra-master/mudra-xr/assets/samples/paint.html +133 -0
  56. package/skills/mudra-master/mudra-xr/assets/samples/planar-vst.html +45 -0
  57. package/skills/mudra-master/mudra-xr/assets/samples/reticle.html +95 -0
  58. package/skills/mudra-master/mudra-xr/assets/samples/skybox_agent.html +384 -0
  59. package/skills/mudra-master/mudra-xr/assets/samples/sound.html +390 -0
  60. package/skills/mudra-master/mudra-xr/assets/samples/ui.html +496 -0
  61. package/skills/mudra-master/mudra-xr/assets/samples/virtual-screens.html +808 -0
  62. package/skills/mudra-master/mudra-xr/assets/templates/0_basic.html +64 -0
  63. package/skills/mudra-master/mudra-xr/assets/templates/1_ui.html +107 -0
  64. package/skills/mudra-master/mudra-xr/assets/templates/2_hands.html +183 -0
  65. package/skills/mudra-master/mudra-xr/assets/templates/3_depth.html +89 -0
  66. package/skills/mudra-master/mudra-xr/assets/templates/4_stereo.html +84 -0
  67. package/skills/mudra-master/mudra-xr/assets/templates/5_camera.html +117 -0
  68. package/skills/mudra-master/mudra-xr/assets/templates/6_ai.html +154 -0
  69. package/skills/mudra-master/mudra-xr/assets/templates/7_ai_live.html +253 -0
  70. package/skills/mudra-master/mudra-xr/assets/templates/8_objects.html +95 -0
  71. package/skills/mudra-master/mudra-xr/assets/templates/9_xr-toggle.html +90 -0
  72. package/skills/mudra-master/mudra-xr/assets/templates/heuristic_hand_gestures.html +102 -0
  73. package/skills/mudra-master/mudra-xr/assets/templates/meshes.html +47 -0
  74. package/skills/mudra-master/mudra-xr/assets/templates/planes.html +47 -0
  75. package/skills/mudra-master/mudra-xr/assets/templates/uikit.html +187 -0
  76. package/skills/mudra-master/mudra-xr/references/agent_protocol.json +878 -0
  77. package/skills/mudra-master/mudra-xr/references/promt.md +1516 -0
  78. package/skills/mudra-preview/SKILL.md +151 -0
  79. package/skills/mudra-preview/assets/ar-menu.html +382 -0
  80. package/skills/mudra-preview/assets/document-scroller.html +468 -0
  81. package/skills/mudra-preview/assets/drum-machine.html +788 -0
  82. package/skills/mudra-preview/assets/emg-visualizer.html +390 -0
  83. package/skills/mudra-preview/assets/generative-art.html +408 -0
  84. package/skills/mudra-preview/assets/gesture-assistant.html +342 -0
  85. package/skills/mudra-preview/assets/gesture-speech.html +444 -0
  86. package/skills/mudra-preview/assets/hands-free-desktop.html +376 -0
  87. package/skills/mudra-preview/assets/model-rotator.html +365 -0
  88. package/skills/mudra-preview/assets/mudra-duel.html +3200 -0
  89. package/skills/mudra-preview/assets/mudra-monitor.html +1451 -0
  90. package/skills/mudra-preview/assets/mudra-ultimate-template.html +891 -0
  91. package/skills/mudra-preview/assets/music-sequencer.html +455 -0
  92. package/skills/mudra-preview/assets/neural-pong.html +380 -0
  93. package/skills/mudra-preview/assets/neural-snake.html +364 -0
  94. package/skills/mudra-preview/assets/presentation-controller.html +345 -0
  95. package/skills/mudra-preview/assets/pressure-painter.html +285 -0
  96. package/skills/mudra-preview/assets/runner.html +1812 -0
  97. package/skills/mudra-preview/assets/smart-home.html +461 -0
  98. package/skills/mudra-preview/assets/space-invaders.html +1154 -0
  99. package/skills/mudra-preview/assets/waterful-ring-toss.html +1624 -0
  100. package/skills/mudra-preview/references/agent_protocol.json +646 -0
  101. package/skills/mudra-preview/references/promt.md +16829 -0
  102. package/skills/mudra-xr/SKILL.md +245 -0
  103. package/skills/mudra-xr/assets/demos/3dgs-walkthrough.html +242 -0
  104. package/skills/mudra-xr/assets/demos/aisimulator.html +792 -0
  105. package/skills/mudra-xr/assets/demos/balloonpop.html +422 -0
  106. package/skills/mudra-xr/assets/demos/ballpit.html +284 -0
  107. package/skills/mudra-xr/assets/demos/drone.html +41 -0
  108. package/skills/mudra-xr/assets/demos/gemini-icebreakers.html +338 -0
  109. package/skills/mudra-xr/assets/demos/gemini-xrobject.html +302 -0
  110. package/skills/mudra-xr/assets/demos/math3d.html +188 -0
  111. package/skills/mudra-xr/assets/demos/measure.html +247 -0
  112. package/skills/mudra-xr/assets/demos/occlusion.html +251 -0
  113. package/skills/mudra-xr/assets/demos/rain.html +498 -0
  114. package/skills/mudra-xr/assets/demos/screenwiper.html +432 -0
  115. package/skills/mudra-xr/assets/demos/splash.html +519 -0
  116. package/skills/mudra-xr/assets/demos/webcam_gestures.html +253 -0
  117. package/skills/mudra-xr/assets/demos/xremoji.html +833 -0
  118. package/skills/mudra-xr/assets/demos/xrpoet.html +151 -0
  119. package/skills/mudra-xr/assets/samples/depthmap.html +266 -0
  120. package/skills/mudra-xr/assets/samples/depthmesh.html +100 -0
  121. package/skills/mudra-xr/assets/samples/game_rps.html +1099 -0
  122. package/skills/mudra-xr/assets/samples/gestures_custom.html +467 -0
  123. package/skills/mudra-xr/assets/samples/gestures_heuristic.html +256 -0
  124. package/skills/mudra-xr/assets/samples/lighting.html +163 -0
  125. package/skills/mudra-xr/assets/samples/mesh_detection.html +46 -0
  126. package/skills/mudra-xr/assets/samples/modelviewer.html +183 -0
  127. package/skills/mudra-xr/assets/samples/paint.html +133 -0
  128. package/skills/mudra-xr/assets/samples/planar-vst.html +45 -0
  129. package/skills/mudra-xr/assets/samples/reticle.html +95 -0
  130. package/skills/mudra-xr/assets/samples/skybox_agent.html +384 -0
  131. package/skills/mudra-xr/assets/samples/sound.html +390 -0
  132. package/skills/mudra-xr/assets/samples/ui.html +496 -0
  133. package/skills/mudra-xr/assets/samples/virtual-screens.html +808 -0
  134. package/skills/mudra-xr/assets/templates/0_basic.html +64 -0
  135. package/skills/mudra-xr/assets/templates/1_ui.html +107 -0
  136. package/skills/mudra-xr/assets/templates/2_hands.html +183 -0
  137. package/skills/mudra-xr/assets/templates/3_depth.html +89 -0
  138. package/skills/mudra-xr/assets/templates/4_stereo.html +84 -0
  139. package/skills/mudra-xr/assets/templates/5_camera.html +117 -0
  140. package/skills/mudra-xr/assets/templates/6_ai.html +154 -0
  141. package/skills/mudra-xr/assets/templates/7_ai_live.html +253 -0
  142. package/skills/mudra-xr/assets/templates/8_objects.html +95 -0
  143. package/skills/mudra-xr/assets/templates/9_xr-toggle.html +90 -0
  144. package/skills/mudra-xr/assets/templates/heuristic_hand_gestures.html +102 -0
  145. package/skills/mudra-xr/assets/templates/meshes.html +47 -0
  146. package/skills/mudra-xr/assets/templates/planes.html +47 -0
  147. package/skills/mudra-xr/assets/templates/uikit.html +187 -0
  148. package/skills/mudra-xr/references/agent_protocol.json +878 -0
  149. 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>