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,833 @@
1
+ <!doctype html>
2
+ <!-- reference: demos/xremoji -->
3
+ <html lang="en">
4
+ <head>
5
+ <title>Meet emoji</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
+ "lit": "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js",
20
+ "lit/": "https://esm.run/lit@3/",
21
+ "three": "https://cdn.jsdelivr.net/npm/three@0.182.0/build/three.module.js",
22
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.182.0/examples/jsm/",
23
+ "troika-three-text": "https://cdn.jsdelivr.net/gh/protectwise/troika@028b81cf308f0f22e5aa8e78196be56ec1997af5/packages/troika-three-text/src/index.js",
24
+ "troika-three-utils": "https://cdn.jsdelivr.net/gh/protectwise/troika@v0.52.4/packages/troika-three-utils/src/index.js",
25
+ "troika-worker-utils": "https://cdn.jsdelivr.net/gh/protectwise/troika@v0.52.4/packages/troika-worker-utils/src/index.js",
26
+ "bidi-js": "https://esm.sh/bidi-js@%5E1.0.2?target=es2022",
27
+ "webgl-sdf-generator": "https://esm.sh/webgl-sdf-generator@1.1.1/es2022/webgl-sdf-generator.mjs",
28
+ "@tensorflow/tfjs-core": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core/+esm",
29
+ "@tensorflow/tfjs": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/+esm",
30
+ "@tensorflow/tfjs-backend-webgpu": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgpu/+esm",
31
+ "@litertjs/core": "https://unpkg.com/@litertjs/core@0.2.1",
32
+ "@litertjs/tfjs-interop": "https://unpkg.com/@litertjs/tfjs-interop@1.0.1",
33
+ "@litertjs/wasm-utils": "https://unpkg.com/@litertjs/wasm-utils@0.2.1",
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
+ import * as THREE from 'three';
46
+ import * as xb from 'xrblocks';
47
+
48
+ // TensorflowJS + WebGpu backend
49
+ import * as tf from '@tensorflow/tfjs';
50
+ import {WebGPUBackend} from '@tensorflow/tfjs-backend-webgpu';
51
+
52
+ // LiteRt-eap
53
+ import {loadLiteRt, setWebGpuDevice} from '@litertjs/core';
54
+ import {runWithTfjsTensors} from '@litertjs/tfjs-interop';
55
+
56
+ // --- AnimationHandler inlined ---
57
+ let globalIndex = 0;
58
+
59
+ class AnimationItemView {
60
+ constructor(options, objectView, modelView, isDebug = false) {
61
+ this.options = {...options};
62
+ this.objectView = objectView;
63
+ this.modelView = modelView;
64
+ this.isDebug = isDebug;
65
+ this.isReady = false;
66
+ this.isPlaying = false;
67
+ this.animationStartTime = performance.now();
68
+ this.enableDebugIfNeeded();
69
+ }
70
+
71
+ enableDebugIfNeeded() {
72
+ if (this.isDebug) {
73
+ return;
74
+ }
75
+
76
+ this.uniqueId = globalIndex++;
77
+
78
+ this.name = 'GLTF_MODEL_VIEW_' + this.uniqueId;
79
+
80
+ console.time(this.name);
81
+
82
+ this.modelView.name = 'ModelViewer-' + this.uniqueId;
83
+
84
+ this.objectView.name = 'ObjectView-' + this.uniqueId;
85
+ }
86
+
87
+ printItemInfo() {
88
+ if (!this.isDebug) return;
89
+
90
+ this.printInfo(this.modelView);
91
+ this.printInfo(this.objectView);
92
+ }
93
+
94
+ printInfo(model) {
95
+ if (!model) {
96
+ console.warn('Model is null or undefined.');
97
+ return;
98
+ }
99
+
100
+ console.log(`--- Model Information: ${model.name || 'Unnamed Model'} ---`);
101
+
102
+ console.log(
103
+ 'Position:',
104
+ model.position.x.toFixed(2),
105
+ model.position.y.toFixed(2),
106
+ model.position.z.toFixed(2)
107
+ );
108
+
109
+ console.log(
110
+ 'Scale:',
111
+ model.scale.x.toFixed(5),
112
+ model.scale.y.toFixed(5),
113
+ model.scale.z.toFixed(5)
114
+ );
115
+
116
+ const boundingBox = new THREE.Box3().setFromObject(model);
117
+ const size = new THREE.Vector3();
118
+ boundingBox.getSize(size);
119
+ console.log(
120
+ 'Size (Width x Height x Depth):',
121
+ `${size.x.toFixed(2)} x ${size.y.toFixed(2)} x ${size.z.toFixed(2)}`
122
+ );
123
+
124
+ console.log('------------------------------------------');
125
+ }
126
+
127
+ onSceneReady(data, scene) {
128
+ this.isReady = true;
129
+ if (data.position) {
130
+ this.modelView.position.copy(data.position);
131
+ }
132
+
133
+ const scene_position = data.model.position || {x: 0, y: 0, z: 0};
134
+ scene.position.copy(scene_position);
135
+
136
+ if (this.isDebug) console.timeEnd(this.name);
137
+ }
138
+
139
+ updateOptions(newOptions) {
140
+ this.options = {...newOptions};
141
+ }
142
+ }
143
+
144
+ class AnimationHandler {
145
+ constructor(data, isDebug = false) {
146
+ this.data = data;
147
+ this.isDebug = isDebug;
148
+ this.animationViews = [];
149
+ }
150
+
151
+ init(core, panel, options = {}) {
152
+ this.loadGltfModels(panel, this.data, options);
153
+ for (let i = 0; i < this.animationViews.length; ++i) {
154
+ const item = this.animationViews[i];
155
+ core.scene.add(item.modelView);
156
+ }
157
+ }
158
+
159
+ loadGltfModels(panel, data, options) {
160
+ let result = [];
161
+ for (let i = 0; i < data.length; i++) {
162
+ if (data[i].model) {
163
+ let objectView = new xb.View();
164
+ const model = new xb.ModelViewer({});
165
+ model.visible = false;
166
+ model.startAnimationOnLoad = false;
167
+
168
+ const animationItem = new AnimationItemView(
169
+ options,
170
+ objectView,
171
+ model,
172
+ this.isDebug
173
+ );
174
+ this.animationViews.push(animationItem);
175
+
176
+ model.loadGLTFModel({
177
+ data: data[i].model,
178
+ setupRaycastCylinder: false,
179
+ setupRaycastBox: true,
180
+ renderer: xb.core.renderer,
181
+ setupPlatform: false,
182
+ onSceneLoaded: (scene) => {
183
+ animationItem.onSceneReady(data[i], scene);
184
+ },
185
+ });
186
+ objectView.add(model);
187
+ panel.add(objectView);
188
+ }
189
+ }
190
+ panel.updateLayouts();
191
+ }
192
+
193
+ onBeforePlay() {
194
+ // Empty
195
+ }
196
+
197
+ onBeforeUpdate() {}
198
+
199
+ play(playbackTime = 1800) {
200
+ if (this.isPlaying) {
201
+ return;
202
+ }
203
+
204
+ this.isPlaying = true;
205
+
206
+ this.animationDuration = playbackTime;
207
+
208
+ this.onBeforePlay();
209
+
210
+ for (let i = 0; i < this.animationViews.length; ++i) {
211
+ const item = this.animationViews[i];
212
+ item.modelView.playClipAnimationOnce();
213
+
214
+ item.printItemInfo();
215
+ }
216
+ this.setVisibility(true);
217
+
218
+ setTimeout(() => {
219
+ this.setVisibility(false);
220
+ setTimeout(() => {
221
+ this.isPlaying = false;
222
+ }, 150);
223
+ }, playbackTime);
224
+ }
225
+
226
+ setVisibility(visible) {
227
+ for (let i = 0; i < this.animationViews.length; ++i) {
228
+ const item = this.animationViews[i];
229
+ item.modelView.visible = visible;
230
+ }
231
+ }
232
+ }
233
+
234
+ // --- BalloonsAnimationHandler inlined ---
235
+ const defaultOptions = {
236
+ oscillationAmplitude: 0.2,
237
+ oscillationFrequencyX: 0.2,
238
+ oscillationFrequencyZ: 0.3,
239
+ };
240
+
241
+ class BalloonsAnimationHandler extends AnimationHandler {
242
+ constructor(data, isDebug) {
243
+ super(data, isDebug);
244
+ this.animationStartTime = performance.now();
245
+ this.animationDuration = 3000;
246
+ this.totalVerticalDistance = 0.15;
247
+ }
248
+
249
+ init(core, panel, options = {}) {
250
+ super.init(core, panel, {...defaultOptions, ...options});
251
+ }
252
+
253
+ onBeforePlay() {
254
+ this.animationStartTime = performance.now();
255
+
256
+ for (let i = 0; i < this.data.length; ++i) {
257
+ let original = this.data[i].position;
258
+ if (original) {
259
+ this.animationViews[i].modelView.position.copy({
260
+ x: original.x + (Math.random() - 0.5),
261
+ y: original.y + (Math.random() - 0.5),
262
+ z: original.z + (Math.random() - 0.5),
263
+ });
264
+ }
265
+
266
+ this.animationViews[i].updateOptions({
267
+ oscillationAmplitude:
268
+ defaultOptions.oscillationAmplitude + (Math.random() - 0.5) * 0.2,
269
+ oscillationFrequencyX:
270
+ defaultOptions.oscillationFrequencyX + (Math.random() - 0.5) * 0.2,
271
+ oscillationFrequencyZ:
272
+ defaultOptions.oscillationFrequencyZ + (Math.random() - 0.5) * 0.2,
273
+ });
274
+ }
275
+ }
276
+
277
+ onBeforeUpdate() {
278
+ const currentTime = performance.now();
279
+ const elapsedTime = currentTime - this.animationStartTime;
280
+ const progress = Math.min(1, elapsedTime / this.animationDuration);
281
+
282
+ this.animationViews.forEach((viewData) => {
283
+ const mv = viewData.modelView;
284
+ const startY = mv.position.y || 0;
285
+
286
+ const opt = viewData.options;
287
+
288
+ mv.position.y = startY + this.totalVerticalDistance * progress;
289
+
290
+ const time = currentTime * 0.001;
291
+ mv.position.x +=
292
+ Math.sin(time * opt.oscillationFrequencyX * Math.PI * 2) *
293
+ opt.oscillationAmplitude *
294
+ progress;
295
+ mv.position.z +=
296
+ Math.cos(time * opt.oscillationFrequencyZ * Math.PI * 2) *
297
+ opt.oscillationAmplitude *
298
+ progress;
299
+ });
300
+ }
301
+ }
302
+
303
+ // --- GestureDetectionHandler inlined ---
304
+ const HAND_JOINT_COUNT = 25;
305
+ const HAND_JOINT_IDX_CONNECTION_MAP = [
306
+ [1, 2],
307
+ [2, 3],
308
+ [3, 4],
309
+ [5, 6],
310
+ [6, 7],
311
+ [7, 8],
312
+ [8, 9],
313
+ [10, 11],
314
+ [11, 12],
315
+ [12, 13],
316
+ [13, 14],
317
+ [15, 16],
318
+ [16, 17],
319
+ [17, 18],
320
+ [18, 19],
321
+ [20, 21],
322
+ [21, 22],
323
+ [22, 23],
324
+ [23, 24],
325
+ ];
326
+
327
+ const HAND_BONE_IDX_CONNECTION_MAP = [
328
+ [0, 1],
329
+ [1, 2],
330
+ [3, 4],
331
+ [4, 5],
332
+ [5, 6],
333
+ [7, 8],
334
+ [8, 9],
335
+ [9, 10],
336
+ [11, 12],
337
+ [12, 13],
338
+ [13, 14],
339
+ [15, 16],
340
+ [16, 17],
341
+ [17, 18],
342
+ ];
343
+
344
+ const UNKNOWN_GESTURE = 7;
345
+
346
+ class LatestTaskQueue {
347
+ constructor() {
348
+ this.latestTask = null;
349
+ this.isProcessing = false;
350
+ }
351
+
352
+ enqueue(task) {
353
+ if (typeof task !== 'function') {
354
+ console.error('Task must be a function.');
355
+ return;
356
+ }
357
+ this.latestTask = task;
358
+ if (!this.isProcessing) {
359
+ this.processLatestTask();
360
+ }
361
+ }
362
+
363
+ processLatestTask() {
364
+ if (this.latestTask) {
365
+ this.isProcessing = true;
366
+ const taskToProcess = this.latestTask;
367
+ this.latestTask = null;
368
+
369
+ setTimeout(async () => {
370
+ try {
371
+ await taskToProcess();
372
+ } catch (error) {
373
+ console.error('Error processing latest task:', error);
374
+ } finally {
375
+ this.isProcessing = false;
376
+ if (this.latestTask) {
377
+ this.processLatestTask();
378
+ }
379
+ }
380
+ }, 0);
381
+ }
382
+ }
383
+
384
+ getSize() {
385
+ return this.latestTask === null ? 0 : 1;
386
+ }
387
+
388
+ isEmpty() {
389
+ return this.latestTask === null;
390
+ }
391
+ }
392
+
393
+ class GestureDetectionHandler {
394
+ constructor() {
395
+ this.modelPath = './custom_gestures_model.tflite';
396
+ this.modelState = 'None';
397
+
398
+ this.queue = [];
399
+ this.queue.push(new LatestTaskQueue());
400
+ this.queue.push(new LatestTaskQueue());
401
+
402
+ setTimeout(() => {
403
+ this.setBackendAndLoadModel();
404
+ }, 1);
405
+ }
406
+
407
+ async setBackendAndLoadModel() {
408
+ this.modelState = 'Loading';
409
+ try {
410
+ await tf.setBackend('webgpu');
411
+ await tf.ready();
412
+
413
+ const wasmPath = 'https://unpkg.com/@litertjs/core@0.2.1/wasm/';
414
+ const liteRt = await loadLiteRt(wasmPath);
415
+
416
+ const backend = tf.backend();
417
+ setWebGpuDevice(backend.device);
418
+
419
+ await this.loadModel(liteRt);
420
+
421
+ if (this.model) {
422
+ console.log('MODEL DETAILS: ', this.model.getInputDetails());
423
+ }
424
+ this.modelState = 'Ready';
425
+ } catch (error) {
426
+ console.error('Failed to load model or backend:', error);
427
+ }
428
+ }
429
+
430
+ async loadModel(liteRt) {
431
+ try {
432
+ this.model = await liteRt.loadAndCompile(this.modelPath, {
433
+ accelerator: 'webgpu',
434
+ });
435
+ } catch (error) {
436
+ this.model = null;
437
+ console.error('Error loading model:', error);
438
+ }
439
+ }
440
+
441
+ calculateRelativeHandBoneAngles(jointPositions) {
442
+ let jointPositionsReshaped = [];
443
+
444
+ jointPositionsReshaped = jointPositions.reshape([HAND_JOINT_COUNT, 3]);
445
+
446
+ const boneVectors = [];
447
+ HAND_JOINT_IDX_CONNECTION_MAP.forEach(([joint1, joint2]) => {
448
+ const boneVector = jointPositionsReshaped
449
+ .slice([joint2, 0], [1, 3])
450
+ .sub(jointPositionsReshaped.slice([joint1, 0], [1, 3]))
451
+ .squeeze();
452
+ const norm = boneVector.norm();
453
+ const normalizedBoneVector = boneVector.div(norm);
454
+ boneVectors.push(normalizedBoneVector);
455
+ });
456
+
457
+ const relativeHandBoneAngles = [];
458
+ HAND_BONE_IDX_CONNECTION_MAP.forEach(([bone1, bone2]) => {
459
+ const angle = boneVectors[bone1].dot(boneVectors[bone2]);
460
+ relativeHandBoneAngles.push(angle);
461
+ });
462
+
463
+ return tf.stack(relativeHandBoneAngles);
464
+ }
465
+
466
+ async detectGesture(handJoints) {
467
+ if (!this.model || !handJoints || handJoints.length !== 25 * 3) {
468
+ console.log('Invalid hand joints or model load error.');
469
+ return UNKNOWN_GESTURE;
470
+ }
471
+
472
+ try {
473
+ const tensor = this.calculateRelativeHandBoneAngles(
474
+ tf.tensor1d(handJoints)
475
+ );
476
+
477
+ let tensorReshaped = tensor.reshape([
478
+ 1,
479
+ HAND_BONE_IDX_CONNECTION_MAP.length,
480
+ 1,
481
+ ]);
482
+ var result = -1;
483
+
484
+ result = runWithTfjsTensors(this.model, tensorReshaped);
485
+
486
+ let integerLabel = result[0].as1D().arraySync();
487
+ if (integerLabel.length == 7) {
488
+ let x = integerLabel[0];
489
+ let idx = 0;
490
+ for (let t = 0; t < 7; ++t) {
491
+ if (integerLabel[t] > x) {
492
+ idx = t;
493
+ x = integerLabel[t];
494
+ }
495
+ }
496
+ return idx;
497
+ }
498
+ } catch (error) {
499
+ console.error('Error:', error);
500
+ }
501
+ return UNKNOWN_GESTURE;
502
+ }
503
+
504
+ postTask(joints, handIndex) {
505
+ if (Object.keys(joints).length !== 25) {
506
+ return UNKNOWN_GESTURE;
507
+ }
508
+
509
+ let handJointPositions = [];
510
+ for (const i in joints) {
511
+ handJointPositions.push(joints[i].position.x);
512
+ handJointPositions.push(joints[i].position.y);
513
+ handJointPositions.push(joints[i].position.z);
514
+ }
515
+
516
+ if (handJointPositions.length !== 25 * 3) {
517
+ return UNKNOWN_GESTURE;
518
+ }
519
+
520
+ if (handIndex >= 0 && handIndex < this.queue.length) {
521
+ this.queue[handIndex].enqueue(async () => {
522
+ let result = await this.detectGesture(handJointPositions);
523
+ if (result === 2 && !this.isThumbUp(handJointPositions, 2, 3, 4)) {
524
+ result = 0;
525
+ }
526
+ if (this.observer && this.observer.onGestureDetected) {
527
+ this.observer.onGestureDetected(handIndex, result);
528
+ }
529
+ });
530
+ }
531
+ }
532
+
533
+ isThumbUp(d1, p1, p2, p3) {
534
+ return this.isThumbUpSimple(d1, p1, p3);
535
+ }
536
+
537
+ isThumbUpAdvanced(data, p1, p2, p3) {
538
+ const v1 = {
539
+ x: data[p2 * 3] - data[p1 * 3],
540
+ y: data[p2 * 3 + 1] - data[p1 * 3 + 1],
541
+ z: data[p2 * 3 + 2] - data[p1 * 3 + 2],
542
+ };
543
+
544
+ const v2 = {
545
+ x: data[p3 * 3] - data[p2 * 3],
546
+ y: data[p3 * 3 + 1] - data[p2 * 3 + 1],
547
+ z: data[p3 * 3 + 2] - data[p2 * 3 + 2],
548
+ };
549
+
550
+ const dotProduct = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
551
+
552
+ const magnitudeV1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z);
553
+ const magnitudeV2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y + v2.z * v2.z);
554
+
555
+ if (magnitudeV1 === 0 || magnitudeV2 === 0) {
556
+ return false;
557
+ }
558
+
559
+ const cosAngle = dotProduct / (magnitudeV1 * magnitudeV2);
560
+
561
+ const angleRadians = Math.acos(Math.max(-1, Math.min(1, cosAngle)));
562
+
563
+ const angleDegrees = angleRadians * (180 / Math.PI);
564
+
565
+ const thumbUpThreshold = 90;
566
+
567
+ return angleDegrees > thumbUpThreshold;
568
+ }
569
+
570
+ isThumbUpSimple(data, p1, p2) {
571
+ const vector = {
572
+ x: data[p2 * 3] - data[p1 * 3],
573
+ y: data[p2 * 3 + 1] - data[p1 * 3 + 1],
574
+ z: data[p2 * 3 + 2] - data[p1 * 3 + 2],
575
+ };
576
+
577
+ const magnitude = Math.sqrt(
578
+ vector.x * vector.x + vector.y * vector.y + vector.z * vector.z
579
+ );
580
+
581
+ if (magnitude < 0.001) {
582
+ return false;
583
+ }
584
+
585
+ const normalizedVector = {
586
+ x: vector.x / magnitude,
587
+ y: vector.y / magnitude,
588
+ z: vector.z / magnitude,
589
+ };
590
+
591
+ const upVector = {x: 0, y: 1, z: 0};
592
+
593
+ const dotProduct =
594
+ normalizedVector.x * upVector.x +
595
+ normalizedVector.y * upVector.y +
596
+ normalizedVector.z * upVector.z;
597
+
598
+ const cos45Degrees = Math.cos((45 * Math.PI) / 180);
599
+
600
+ return dotProduct >= cos45Degrees;
601
+ }
602
+
603
+ registerObserver(observer) {
604
+ this.observer = observer;
605
+ }
606
+ }
607
+
608
+ // --- MeetEmoji inlined ---
609
+ const LEFT_HAND_INDEX = 0;
610
+ const RIGHT_HAND_INDEX = 1;
611
+
612
+ const PROPRIETARY_ASSETS_PATH =
613
+ 'https://cdn.jsdelivr.net/gh/xrblocks/proprietary-assets@main/';
614
+
615
+ const BALLOONS_MODELS = [
616
+ {
617
+ model: {
618
+ scale: {x: 1, y: 1, z: 1},
619
+ rotation: {x: 0, y: 0, z: 0},
620
+ path: PROPRIETARY_ASSETS_PATH + 'balloons/',
621
+ model: 'scene.gltf',
622
+ verticallyAlignObject: true,
623
+ },
624
+ position: {x: 4, y: -1.2, z: -5},
625
+ },
626
+ {
627
+ model: {
628
+ scale: {x: 1, y: 1, z: 1},
629
+ rotation: {x: 0, y: 0, z: 0},
630
+ path: PROPRIETARY_ASSETS_PATH + 'balloons/',
631
+ model: 'scene.gltf',
632
+ verticallyAlignObject: true,
633
+ },
634
+ position: {x: 0, y: -1, z: -5},
635
+ },
636
+ {
637
+ model: {
638
+ scale: {x: 1, y: 1, z: 1},
639
+ rotation: {x: 0, y: 0, z: 0},
640
+ path: PROPRIETARY_ASSETS_PATH + 'balloons/',
641
+ model: 'scene.gltf',
642
+ verticallyAlignObject: true,
643
+ },
644
+ position: {x: -4, y: -1.2, z: -5},
645
+ },
646
+ ];
647
+
648
+ const VICTORY_MODELS = [
649
+ {
650
+ model: {
651
+ scale: {x: 0.05, y: 0.05, z: 0.05},
652
+ rotation: {x: 0, y: 0, z: 0},
653
+ scene_position: {x: 0, y: 0, z: 0},
654
+ path: PROPRIETARY_ASSETS_PATH + 'Confetti/',
655
+ model: 'scene.gltf',
656
+ verticallyAlignObject: true,
657
+ },
658
+ position: {x: 34, y: -15, z: 15},
659
+ },
660
+ {
661
+ model: {
662
+ scale: {x: 0.05, y: 0.05, z: 0.05},
663
+ rotation: {x: 0, y: 0, z: 0},
664
+ scene_position: {x: 0, y: 0, z: 0},
665
+ path: PROPRIETARY_ASSETS_PATH + 'Confetti/',
666
+ model: 'scene.gltf',
667
+ verticallyAlignObject: true,
668
+ },
669
+ position: {x: -34, y: -15, z: 15},
670
+ },
671
+ ];
672
+
673
+ class MeetEmoji extends xb.Script {
674
+ constructor() {
675
+ super();
676
+ this.handGesture = [0, 0];
677
+
678
+ this.playBalloonsOnUpdate = false;
679
+ this.playConfettiOnUpdate = false;
680
+
681
+ {
682
+ const panel = new xb.SpatialPanel({
683
+ backgroundColor: '#00000000',
684
+ useDefaultPosition: false,
685
+ showEdge: false,
686
+ });
687
+ panel.scale.set(panel.width, panel.height, 1);
688
+ panel.isRoot = true;
689
+ this.add(panel);
690
+
691
+ const grid = panel.addGrid();
692
+ grid.addRow({weight: 0.4});
693
+
694
+ grid.addRow({weight: 0.1});
695
+ const controlRow = grid.addRow({weight: 0.5});
696
+ const ctrlPanel = controlRow.addPanel({backgroundColor: '#000000bb'});
697
+
698
+ const ctrlGrid = ctrlPanel.addGrid();
699
+ {
700
+ const midColumn = ctrlGrid.addCol({weight: 0.9});
701
+
702
+ midColumn.addRow({weight: 0.3});
703
+
704
+ const gesturesRow = midColumn.addRow({weight: 0.4});
705
+
706
+ gesturesRow.addCol({weight: 0.05});
707
+
708
+ const textCol = gesturesRow.addCol({weight: 1.0});
709
+ textCol.addRow({weight: 1.0}).addText({
710
+ text: 'Give the victory or thumbs-up gestures a try!',
711
+ fontColor: '#ffffff',
712
+ fontSize: 0.05,
713
+ });
714
+
715
+ gesturesRow.addCol({weight: 0.01});
716
+
717
+ midColumn.addRow({weight: 0.1});
718
+ }
719
+
720
+ const orbiter = ctrlGrid.addOrbiter();
721
+ orbiter.addExitButton();
722
+
723
+ panel.updateLayouts();
724
+
725
+ this.panel = panel;
726
+
727
+ this.victoryHandler = new AnimationHandler(VICTORY_MODELS);
728
+ this.balloonsHandler = new BalloonsAnimationHandler(BALLOONS_MODELS);
729
+
730
+ this.gestureDetectionHandler = new GestureDetectionHandler();
731
+ this.gestureDetectionHandler.registerObserver(this);
732
+ }
733
+
734
+ this.frameId = 0;
735
+ }
736
+
737
+ onGestureDetected(handIndex, result) {
738
+ if (this.handGesture[handIndex] !== result) {
739
+ if (result === 4) {
740
+ if (!this.victoryHandler.isPlaying) this.playConfettiOnUpdate = true;
741
+ } else if (result === 2) {
742
+ if (!this.balloonsHandler.isPlaying) this.playBalloonsOnUpdate = true;
743
+ }
744
+
745
+ if (
746
+ this.handGesture[handIndex] === 4 ||
747
+ this.handGesture[handIndex] === 2
748
+ ) {
749
+ this.onGestureStopped(handIndex, this.handGesture[handIndex]);
750
+ }
751
+ this.handGesture[handIndex] = result;
752
+ }
753
+ }
754
+
755
+ onGestureStopped(handIndex, gestureIndex) {
756
+ // TODO: we could hide animation on gesture stop
757
+ }
758
+
759
+ init() {
760
+ xb.core.renderer.localClippingEnabled = true;
761
+
762
+ this.add(new THREE.HemisphereLight(0x888877, 0x777788, 3));
763
+ const light = new THREE.DirectionalLight(0xffffff, 5.0);
764
+ light.position.set(-0.5, 4, 1.0);
765
+ this.add(light);
766
+
767
+ this.panel.position.set(0, 1.2, -1.0);
768
+
769
+ this.victoryHandler.init(xb.core, this.panel);
770
+ this.balloonsHandler.init(xb.core, this.panel);
771
+ }
772
+
773
+ onSelectStart(event) {}
774
+
775
+ onSelecting(id) {}
776
+
777
+ async update() {
778
+ if (this.playConfettiOnUpdate) {
779
+ this.victoryHandler.play(3000);
780
+ this.playConfettiOnUpdate = false;
781
+ }
782
+
783
+ if (this.playBalloonsOnUpdate) {
784
+ this.balloonsHandler.play(3000);
785
+ this.playBalloonsOnUpdate = false;
786
+ }
787
+
788
+ if (this.balloonsHandler) {
789
+ this.balloonsHandler.onBeforeUpdate();
790
+ }
791
+
792
+ if (this.frameId % 5 === 0 || false) {
793
+ const hands = xb.core.user.hands;
794
+ if (hands != null && hands.hands && hands.hands.length == 2) {
795
+ this.gestureDetectionHandler.postTask(
796
+ hands.hands[LEFT_HAND_INDEX].joints,
797
+ LEFT_HAND_INDEX
798
+ );
799
+ this.gestureDetectionHandler.postTask(
800
+ hands.hands[RIGHT_HAND_INDEX].joints,
801
+ RIGHT_HAND_INDEX
802
+ );
803
+ }
804
+ }
805
+
806
+ this.frameId++;
807
+ }
808
+ }
809
+
810
+ const options = new xb.Options({
811
+ antialias: true,
812
+ reticles: {enabled: true},
813
+ visualizeRays: false,
814
+ hands: {enabled: true, visualization: true},
815
+ simulator: {defaultMode: xb.SimulatorMode.POSE},
816
+ });
817
+ options.simulator.instructions.enabled = false;
818
+
819
+ async function start() {
820
+ xb.add(new MeetEmoji());
821
+ options.setAppTitle('XR Emoji');
822
+ await xb.init(options);
823
+ }
824
+
825
+ document.addEventListener('DOMContentLoaded', function () {
826
+ setTimeout(function () {
827
+ start();
828
+ }, 200);
829
+ });
830
+
831
+ </script>
832
+ </body>
833
+ </html>