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,422 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!-- reference: demos/balloonpop -->
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<title>XR Blocks: Balloon Pop (v5.1 - Stable Fallback)</title>
|
|
6
|
+
|
|
7
|
+
<meta
|
|
8
|
+
name="viewport"
|
|
9
|
+
content="width=device-width, initial-scale=1.0, user-scalable=no"
|
|
10
|
+
/>
|
|
11
|
+
<link type="text/css" rel="stylesheet" href="https://xrblocks.github.io/css/xr.css" />
|
|
12
|
+
<script>
|
|
13
|
+
window.litDisableBundleWarning = true;
|
|
14
|
+
</script>
|
|
15
|
+
<script type="importmap">
|
|
16
|
+
{
|
|
17
|
+
"imports": {
|
|
18
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js",
|
|
19
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.180.0/examples/jsm/",
|
|
20
|
+
"troika-three-text": "https://cdn.jsdelivr.net/gh/protectwise/troika@028b81cf308f0f22e5aa8e78196be56ec1997af5/packages/troika-three-text/src/index.js",
|
|
21
|
+
"troika-three-utils": "https://cdn.jsdelivr.net/gh/protectwise/troika@v0.52.4/packages/troika-three-utils/src/index.js",
|
|
22
|
+
"troika-worker-utils": "https://cdn.jsdelivr.net/gh/protectwise/troika@v0.52.4/packages/troika-worker-utils/src/index.js",
|
|
23
|
+
"bidi-js": "https://esm.sh/bidi-js@%5E1.0.2?target=es2022",
|
|
24
|
+
"webgl-sdf-generator": "https://esm.sh/webgl-sdf-generator@1.1.1/es2022/webgl-sdf-generator.mjs",
|
|
25
|
+
"lit": "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js",
|
|
26
|
+
"lit/": "https://esm.run/lit@3/",
|
|
27
|
+
"@dimforge/rapier3d-simd-compat": "https://cdn.skypack.dev/@dimforge/rapier3d-simd-compat@0.17.0",
|
|
28
|
+
"xrblocks": "https://cdn.jsdelivr.net/gh/google/xrblocks@build/xrblocks.js",
|
|
29
|
+
"xrblocks/addons/": "https://cdn.jsdelivr.net/gh/google/xrblocks@build/addons/"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
</head>
|
|
34
|
+
|
|
35
|
+
<body>
|
|
36
|
+
<script type="module">
|
|
37
|
+
import * as xb from 'xrblocks';
|
|
38
|
+
import RAPIER from '@dimforge/rapier3d-simd-compat';
|
|
39
|
+
import 'xrblocks/addons/simulator/SimulatorAddons.js';
|
|
40
|
+
import * as THREE from 'three';
|
|
41
|
+
|
|
42
|
+
// --- audio.js inlined ---
|
|
43
|
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
44
|
+
|
|
45
|
+
function playPopSound() {
|
|
46
|
+
if (audioContext.state === 'suspended') audioContext.resume();
|
|
47
|
+
const noiseSource = audioContext.createBufferSource();
|
|
48
|
+
const bandpass = audioContext.createBiquadFilter();
|
|
49
|
+
const gainNode = audioContext.createGain();
|
|
50
|
+
const now = audioContext.currentTime;
|
|
51
|
+
const sampleRate = audioContext.sampleRate;
|
|
52
|
+
const bufferSize = sampleRate * 0.15;
|
|
53
|
+
const noiseBuffer = audioContext.createBuffer(1, bufferSize, sampleRate);
|
|
54
|
+
const output = noiseBuffer.getChannelData(0);
|
|
55
|
+
for (let i = 0; i < bufferSize; i++) output[i] = Math.random() * 2 - 1;
|
|
56
|
+
noiseSource.buffer = noiseBuffer;
|
|
57
|
+
bandpass.type = 'bandpass';
|
|
58
|
+
bandpass.frequency.setValueAtTime(3000, now);
|
|
59
|
+
bandpass.Q.setValueAtTime(1.2, now);
|
|
60
|
+
gainNode.gain.setValueAtTime(0, now);
|
|
61
|
+
gainNode.gain.linearRampToValueAtTime(0.8, now + 0.002);
|
|
62
|
+
gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.12);
|
|
63
|
+
noiseSource.connect(bandpass);
|
|
64
|
+
bandpass.connect(gainNode);
|
|
65
|
+
gainNode.connect(audioContext.destination);
|
|
66
|
+
noiseSource.start(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function playWhooshSound() {
|
|
70
|
+
if (audioContext.state === 'suspended') audioContext.resume();
|
|
71
|
+
const oscillator = audioContext.createOscillator();
|
|
72
|
+
const gainNode = audioContext.createGain();
|
|
73
|
+
const now = audioContext.currentTime;
|
|
74
|
+
oscillator.type = 'sawtooth';
|
|
75
|
+
oscillator.frequency.setValueAtTime(800, now);
|
|
76
|
+
oscillator.frequency.exponentialRampToValueAtTime(100, now + 0.1);
|
|
77
|
+
gainNode.gain.setValueAtTime(0.3, now);
|
|
78
|
+
gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.1);
|
|
79
|
+
oscillator.connect(gainNode);
|
|
80
|
+
gainNode.connect(audioContext.destination);
|
|
81
|
+
oscillator.start(0);
|
|
82
|
+
oscillator.stop(now + 0.1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- BalloonPop.js inlined ---
|
|
86
|
+
const DART_SPEED = 15.0;
|
|
87
|
+
const DART_GRAVITY_SCALE = 0.1;
|
|
88
|
+
const MENU_WIDTH = 0.85;
|
|
89
|
+
const PARTICLE_COUNT = 30;
|
|
90
|
+
const PARTICLE_LIFE = 1.0;
|
|
91
|
+
const BOUNDARY_RADIUS = 7.62;
|
|
92
|
+
const BOUNDARY_IMPULSE = 0.02;
|
|
93
|
+
|
|
94
|
+
const GROUP_WORLD = (0x0001 << 16) | (0x0002 | 0x0004);
|
|
95
|
+
const GROUP_BALLOON = (0x0002 << 16) | (0x0001 | 0x0002 | 0x0004);
|
|
96
|
+
const GROUP_DART = (0x0004 << 16) | (0x0001 | 0x0002);
|
|
97
|
+
|
|
98
|
+
function createStepperControl(game, grid, labelText, valueRef, min, max, step, isCount) {
|
|
99
|
+
const H_BUTTON = 0.15;
|
|
100
|
+
const H_VALUE_LABEL = 0.12;
|
|
101
|
+
const menuHeight = game.menuPanel.height;
|
|
102
|
+
const getW = (h) => h / menuHeight;
|
|
103
|
+
|
|
104
|
+
grid.addRow({weight: getW(H_BUTTON)}).addTextButton({
|
|
105
|
+
text: '+', fontColor: '#ffffff', backgroundColor: '#4285f4', fontSize: 0.7, width: 0.3, weight: 1.0,
|
|
106
|
+
}).onTriggered = () => {
|
|
107
|
+
const maxVal = isCount ? max : 0.1;
|
|
108
|
+
game[valueRef] = Math.min(maxVal, game[valueRef] + step);
|
|
109
|
+
game.renderMenu();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const displayValue = isCount ? game[valueRef] : Math.round(game[valueRef] * 100);
|
|
113
|
+
const valueText = grid.addRow({weight: getW(H_VALUE_LABEL)}).addText({
|
|
114
|
+
text: `${labelText}: ${displayValue}`, fontColor: '#ffffff', fontSize: 0.12, textAlign: 'center',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (isCount) game.countValueText = valueText;
|
|
118
|
+
else game.speedValueText = valueText;
|
|
119
|
+
|
|
120
|
+
grid.addRow({weight: getW(H_BUTTON)}).addTextButton({
|
|
121
|
+
text: '-', fontColor: '#ffffff', backgroundColor: '#4285f4', fontSize: 0.7, width: 0.3, weight: 1.0,
|
|
122
|
+
}).onTriggered = () => {
|
|
123
|
+
game[valueRef] = Math.max(min, game[valueRef] - step);
|
|
124
|
+
game.renderMenu();
|
|
125
|
+
};
|
|
126
|
+
grid.addRow({weight: getW(0.01)});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
class BalloonGame extends xb.Script {
|
|
130
|
+
constructor() {
|
|
131
|
+
super();
|
|
132
|
+
this.balloons = new Map();
|
|
133
|
+
this.darts = new Map();
|
|
134
|
+
this.particles = [];
|
|
135
|
+
this.balloonCount = 10;
|
|
136
|
+
this.balloonSpeed = 0.03;
|
|
137
|
+
this.balloonsPopped = 0;
|
|
138
|
+
this.activeDart = null;
|
|
139
|
+
this.menuPanel = null;
|
|
140
|
+
this.isMenuExpanded = true;
|
|
141
|
+
this.physics = null;
|
|
142
|
+
this.physicsWorld = null;
|
|
143
|
+
this.RAPIER = null;
|
|
144
|
+
this.balloonModel = null;
|
|
145
|
+
this.dartModel = null;
|
|
146
|
+
this.particleGeometry = null;
|
|
147
|
+
this.particleMaterial = null;
|
|
148
|
+
this.raycaster = new THREE.Raycaster();
|
|
149
|
+
this.menuPos = new THREE.Vector3(0.6, 1.3, -1.0);
|
|
150
|
+
this.menuRot = new THREE.Euler(0, -0.4, 0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async init() {
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
const options = xb.core.registry.get(xb.Options);
|
|
156
|
+
if (options && options.depth) {
|
|
157
|
+
options.depth.enabled = true;
|
|
158
|
+
if (options.depth.depthMesh) {
|
|
159
|
+
options.depth.depthMesh.enabled = true;
|
|
160
|
+
options.depth.depthMesh.physicsEnabled = true;
|
|
161
|
+
options.depth.depthMesh.collisionGroups = GROUP_WORLD;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}, 1000);
|
|
165
|
+
|
|
166
|
+
this.add(new THREE.HemisphereLight(0xffffff, 0xbbbbff, 3));
|
|
167
|
+
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
|
|
168
|
+
dirLight.position.set(1, 2, 1);
|
|
169
|
+
this.add(dirLight);
|
|
170
|
+
|
|
171
|
+
this.createPrefabs();
|
|
172
|
+
this.renderMenu();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
createPrefabs() {
|
|
176
|
+
this.dartModel = new THREE.Group();
|
|
177
|
+
const needleMat = new THREE.MeshStandardMaterial({color: 0xaaaaaa, metalness: 1.0, roughness: 0.1});
|
|
178
|
+
const silverMat = new THREE.MeshStandardMaterial({color: 0xcccccc, metalness: 0.8, roughness: 0.3});
|
|
179
|
+
const redMat = new THREE.MeshStandardMaterial({color: 0xaa0000, roughness: 0.5});
|
|
180
|
+
const finMat = new THREE.MeshStandardMaterial({color: 0xcc0000, roughness: 0.6});
|
|
181
|
+
const needle = new THREE.Mesh(new THREE.ConeGeometry(0.002, 0.04, 6), needleMat);
|
|
182
|
+
needle.position.y = 0.17;
|
|
183
|
+
const tipHolder = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.012, 0.03, 8), silverMat);
|
|
184
|
+
tipHolder.position.y = 0.135;
|
|
185
|
+
const body = new THREE.Mesh(new THREE.CylinderGeometry(0.01, 0.01, 0.18, 8), redMat);
|
|
186
|
+
body.position.y = 0.03;
|
|
187
|
+
const createFin = (rotationY) => {
|
|
188
|
+
const fin = new THREE.Mesh(new THREE.BoxGeometry(0.07, 0.05, 0.002, 1, 1, 1), finMat);
|
|
189
|
+
fin.position.set(0, -0.05, 0);
|
|
190
|
+
fin.rotation.set(Math.PI, rotationY, 0);
|
|
191
|
+
return fin;
|
|
192
|
+
};
|
|
193
|
+
this.dartModel.add(needle, tipHolder, body);
|
|
194
|
+
this.dartModel.add(createFin(0));
|
|
195
|
+
this.dartModel.add(createFin(Math.PI / 2));
|
|
196
|
+
this.dartModel.add(createFin(Math.PI));
|
|
197
|
+
this.dartModel.add(createFin(Math.PI * 1.5));
|
|
198
|
+
|
|
199
|
+
this.balloonModel = new THREE.Group();
|
|
200
|
+
const balloonGeo = new THREE.SphereGeometry(0.5, 32, 32);
|
|
201
|
+
const pos = balloonGeo.attributes.position;
|
|
202
|
+
const v = new THREE.Vector3();
|
|
203
|
+
for (let i = 0; i < pos.count; i++) {
|
|
204
|
+
v.fromBufferAttribute(pos, i);
|
|
205
|
+
if (v.y < 0) { const t = 1.0 - Math.abs(v.y) * 0.35; v.x *= t; v.z *= t; }
|
|
206
|
+
pos.setXYZ(i, v.x, v.y, v.z);
|
|
207
|
+
}
|
|
208
|
+
balloonGeo.computeVertexNormals();
|
|
209
|
+
const balloonMat = new THREE.MeshStandardMaterial({color: 0xffffff, roughness: 0.2, metalness: 0.1, transparent: true, opacity: 0.85, side: THREE.FrontSide});
|
|
210
|
+
this.balloonModel.add(new THREE.Mesh(balloonGeo, balloonMat));
|
|
211
|
+
const knotGeo = new THREE.CylinderGeometry(0.03, 0.01, 0.12, 16);
|
|
212
|
+
knotGeo.translate(0, -0.54, 0);
|
|
213
|
+
this.balloonModel.add(new THREE.Mesh(knotGeo, balloonMat));
|
|
214
|
+
const ringGeo = new THREE.TorusGeometry(0.035, 0.015, 8, 24);
|
|
215
|
+
ringGeo.rotateX(Math.PI / 2);
|
|
216
|
+
ringGeo.translate(0, -0.6, 0);
|
|
217
|
+
this.balloonModel.add(new THREE.Mesh(ringGeo, balloonMat));
|
|
218
|
+
|
|
219
|
+
this.particleGeometry = new THREE.PlaneGeometry(0.08, 0.08);
|
|
220
|
+
this.particleMaterial = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 1.0, side: THREE.DoubleSide, blending: THREE.AdditiveBlending});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
initPhysics(physics) {
|
|
224
|
+
this.physics = physics;
|
|
225
|
+
this.physicsWorld = physics.blendedWorld;
|
|
226
|
+
this.RAPIER = physics.RAPIER;
|
|
227
|
+
this.spawnBalloons();
|
|
228
|
+
this.renderMenu();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
renderMenu() {
|
|
232
|
+
if (this.menuPanel) {
|
|
233
|
+
this.menuPos.copy(this.menuPanel.position);
|
|
234
|
+
this.menuRot.copy(this.menuPanel.rotation);
|
|
235
|
+
this.remove(this.menuPanel);
|
|
236
|
+
}
|
|
237
|
+
const H_TOGGLE = 0.1, H_SCORE = 0.15, H_RESET = 0.15, H_SPACE = 0.03, H_BUTTON = 0.15, H_VALUE_LABEL = 0.12;
|
|
238
|
+
const headerHeight = H_TOGGLE + H_SCORE + H_RESET + H_SPACE;
|
|
239
|
+
const expandedControlsHeight = (H_BUTTON + H_VALUE_LABEL + H_BUTTON + H_SPACE) * 2;
|
|
240
|
+
const menuHeight = this.isMenuExpanded ? headerHeight + expandedControlsHeight : headerHeight;
|
|
241
|
+
const getW = (h) => h / menuHeight;
|
|
242
|
+
|
|
243
|
+
this.menuPanel = new xb.SpatialPanel({width: MENU_WIDTH, height: menuHeight, backgroundColor: '#2b2b2baa', showEdge: true, edgeColor: 'white', edgeWidth: 0.001});
|
|
244
|
+
this.menuPanel.position.copy(this.menuPos);
|
|
245
|
+
this.menuPanel.rotation.copy(this.menuRot);
|
|
246
|
+
this.add(this.menuPanel);
|
|
247
|
+
const grid = this.menuPanel.addGrid();
|
|
248
|
+
grid.addRow({weight: getW(H_TOGGLE)}).addTextButton({text: this.isMenuExpanded ? '\u25B2' : '\u25BC', fontColor: '#ffffff', backgroundColor: '#444444', fontSize: 0.7, weight: 1.0}).onTriggered = () => this.toggleMenu();
|
|
249
|
+
this.scoreText = grid.addRow({weight: getW(H_SCORE)}).addText({text: `${this.balloonsPopped} / ${this.balloonCount}`, fontColor: '#ffffff', fontSize: 0.15, textAlign: 'center'});
|
|
250
|
+
grid.addRow({weight: getW(H_RESET)}).addTextButton({text: '\u21BB', fontColor: '#ffffff', backgroundColor: '#d93025', fontSize: 0.7, weight: 1.0}).onTriggered = () => this.resetGame();
|
|
251
|
+
grid.addRow({weight: getW(H_SPACE)});
|
|
252
|
+
if (this.isMenuExpanded) {
|
|
253
|
+
createStepperControl(this, grid, 'Balloons', 'balloonCount', 5, 30, 1, true);
|
|
254
|
+
createStepperControl(this, grid, 'Speed', 'balloonSpeed', 0.0, 0.1, 0.01, false);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
toggleMenu() { this.isMenuExpanded = !this.isMenuExpanded; this.renderMenu(); }
|
|
259
|
+
updateScoreDisplay() { if (this.scoreText) this.scoreText.text = `${this.balloonsPopped} / ${this.balloonCount}`; }
|
|
260
|
+
resetGame() { this.spawnBalloons(); this.renderMenu(); }
|
|
261
|
+
|
|
262
|
+
spawnBalloons() {
|
|
263
|
+
if (!this.physicsWorld || !this.RAPIER || !this.balloonModel) return;
|
|
264
|
+
this.clearBalloons();
|
|
265
|
+
this.balloonsPopped = 0;
|
|
266
|
+
this.updateScoreDisplay();
|
|
267
|
+
const color = new THREE.Color();
|
|
268
|
+
for (let i = 0; i < this.balloonCount; i++) {
|
|
269
|
+
const grp = this.balloonModel.clone();
|
|
270
|
+
const x = (Math.random() - 0.5) * 4, y = 1.5 + Math.random() * 1, z = -2 - Math.random() * 2;
|
|
271
|
+
grp.position.set(x, y, z);
|
|
272
|
+
const s = 0.7 + Math.random() * 0.6;
|
|
273
|
+
grp.scale.set(s, s, s);
|
|
274
|
+
color.setHSL(Math.random(), 0.95, 0.6);
|
|
275
|
+
grp.traverse((c) => { if (c.isMesh) { c.material = c.material.clone(); c.material.color.copy(color); } });
|
|
276
|
+
const rb = this.physicsWorld.createRigidBody(this.RAPIER.RigidBodyDesc.dynamic().setTranslation(x, y, z).setGravityScale(-0.05 * s).setLinearDamping(0.5).setAngularDamping(0.5));
|
|
277
|
+
const col = this.physicsWorld.createCollider(this.RAPIER.ColliderDesc.ball(0.5 * s).setActiveEvents(this.RAPIER.ActiveEvents.COLLISION_EVENTS).setRestitution(0.85).setDensity(0.1).setCollisionGroups(GROUP_BALLOON), rb);
|
|
278
|
+
this.balloons.set(col.handle, {mesh: grp, rigidBody: rb, collider: col, color: color.clone()});
|
|
279
|
+
this.add(grp);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
clearBalloons() {
|
|
284
|
+
if (!this.physicsWorld) return;
|
|
285
|
+
for (const [h, b] of this.balloons.entries()) { this.remove(b.mesh); this.physicsWorld.removeCollider(b.collider, false); this.physicsWorld.removeRigidBody(b.rigidBody); }
|
|
286
|
+
this.balloons.clear();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
spawnExplosion(position, color) {
|
|
290
|
+
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
291
|
+
const mat = this.particleMaterial.clone();
|
|
292
|
+
mat.color.copy(color);
|
|
293
|
+
const mesh = new THREE.Mesh(this.particleGeometry, mat);
|
|
294
|
+
mesh.position.copy(position);
|
|
295
|
+
this.add(mesh);
|
|
296
|
+
this.particles.push({mesh, velocity: new THREE.Vector3((Math.random() - 0.5) * 4.0, (Math.random() - 0.5) * 4.0, (Math.random() - 0.5) * 4.0), life: PARTICLE_LIFE});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
onSelectStart(event) {
|
|
301
|
+
if (this.menuPanel && this.menuPanel.parent) {
|
|
302
|
+
const ctrl = event.target, pos = new THREE.Vector3(), quat = new THREE.Quaternion();
|
|
303
|
+
ctrl.getWorldPosition(pos);
|
|
304
|
+
ctrl.getWorldQuaternion(quat);
|
|
305
|
+
this.raycaster.set(pos, new THREE.Vector3(0, 0, -1).applyQuaternion(quat));
|
|
306
|
+
if (this.raycaster.intersectObject(this.menuPanel, true).length > 0) return;
|
|
307
|
+
}
|
|
308
|
+
if (this.activeDart) return;
|
|
309
|
+
this.activeDart = this.dartModel.clone();
|
|
310
|
+
this.activeDart.position.set(0, -0.05, -0.15);
|
|
311
|
+
this.activeDart.rotation.set(-Math.PI / 2, 0, 0);
|
|
312
|
+
event.target.add(this.activeDart);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
onSelectEnd(event) {
|
|
316
|
+
const ctrl = event.target;
|
|
317
|
+
if (!this.activeDart || !this.physicsWorld) return;
|
|
318
|
+
const dart = this.activeDart;
|
|
319
|
+
this.activeDart = null;
|
|
320
|
+
const wPos = new THREE.Vector3(), wQuat = new THREE.Quaternion();
|
|
321
|
+
dart.getWorldPosition(wPos);
|
|
322
|
+
dart.getWorldQuaternion(wQuat);
|
|
323
|
+
ctrl.remove(dart);
|
|
324
|
+
dart.position.copy(wPos);
|
|
325
|
+
dart.quaternion.copy(wQuat);
|
|
326
|
+
this.add(dart);
|
|
327
|
+
playWhooshSound();
|
|
328
|
+
const rb = this.physicsWorld.createRigidBody(this.RAPIER.RigidBodyDesc.dynamic().setTranslation(wPos.x, wPos.y, wPos.z).setRotation(wQuat).setGravityScale(DART_GRAVITY_SCALE).setCcdEnabled(true));
|
|
329
|
+
const col = this.physicsWorld.createCollider(this.RAPIER.ColliderDesc.capsule(0.1, 0.015).setActiveEvents(this.RAPIER.ActiveEvents.COLLISION_EVENTS).setSensor(true).setCollisionGroups(GROUP_DART), rb);
|
|
330
|
+
rb.setLinvel(new THREE.Vector3(0, 1, 0).applyQuaternion(wQuat).multiplyScalar(DART_SPEED), true);
|
|
331
|
+
this.darts.set(col.handle, {mesh: dart, rigidBody: rb, collider: col});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
update(time, delta) {
|
|
335
|
+
if (this.physicsWorld) {
|
|
336
|
+
for (const [h, b] of this.balloons.entries()) { b.mesh.position.copy(b.rigidBody.translation()); b.mesh.quaternion.copy(b.rigidBody.rotation()); }
|
|
337
|
+
for (const [h, d] of this.darts.entries()) {
|
|
338
|
+
d.mesh.position.copy(d.rigidBody.translation());
|
|
339
|
+
d.mesh.quaternion.copy(d.rigidBody.rotation());
|
|
340
|
+
if (d.mesh.position.y < -5 || Math.abs(d.mesh.position.z) > 30) this.removeDart(h);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const dt = delta || 1 / 60;
|
|
344
|
+
for (let i = this.particles.length - 1; i >= 0; i--) {
|
|
345
|
+
const p = this.particles[i];
|
|
346
|
+
p.life -= dt;
|
|
347
|
+
if (p.life <= 0) { this.remove(p.mesh); this.particles.splice(i, 1); }
|
|
348
|
+
else { p.mesh.position.addScaledVector(p.velocity, dt); p.mesh.material.opacity = p.life / PARTICLE_LIFE; if (xb.core.camera) p.mesh.lookAt(xb.core.camera.position); }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
physicsStep() {
|
|
353
|
+
if (!this.physics || !this.physicsWorld || !xb.core.camera) return;
|
|
354
|
+
const camPos = xb.core.camera.position;
|
|
355
|
+
const speedFactor = this.balloonSpeed / 10;
|
|
356
|
+
for (const [h, b] of this.balloons.entries()) {
|
|
357
|
+
const s = speedFactor;
|
|
358
|
+
b.rigidBody.addForce({x: (Math.random() - 0.5) * s, y: (Math.random() - 0.5) * (s * 0.5), z: (Math.random() - 0.5) * s}, true);
|
|
359
|
+
const p = b.rigidBody.translation();
|
|
360
|
+
const dx = p.x - camPos.x, dz = p.z - camPos.z;
|
|
361
|
+
const dist = Math.sqrt(dx * dx + dz * dz);
|
|
362
|
+
if (dist > BOUNDARY_RADIUS) b.rigidBody.applyImpulse({x: (-dx / dist) * BOUNDARY_IMPULSE, y: 0, z: (-dz / dist) * BOUNDARY_IMPULSE}, true);
|
|
363
|
+
if (p.y > 5.0) b.rigidBody.applyImpulse({x: 0, y: -BOUNDARY_IMPULSE, z: 0}, true);
|
|
364
|
+
if (p.y < 0.5) b.rigidBody.applyImpulse({x: 0, y: BOUNDARY_IMPULSE, z: 0}, true);
|
|
365
|
+
}
|
|
366
|
+
this.physics.eventQueue.drainCollisionEvents((h1, h2, s) => {
|
|
367
|
+
if (!s) return;
|
|
368
|
+
if (this.darts.has(h1) && this.balloons.has(h2)) { this.popBalloon(h2); this.removeDart(h1); }
|
|
369
|
+
else if (this.darts.has(h2) && this.balloons.has(h1)) { this.popBalloon(h1); this.removeDart(h2); }
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
popBalloon(h) {
|
|
374
|
+
const b = this.balloons.get(h);
|
|
375
|
+
if (b) { this.spawnExplosion(b.mesh.position.clone(), b.color); playPopSound(); }
|
|
376
|
+
this.removeBalloon(h);
|
|
377
|
+
this.balloonsPopped++;
|
|
378
|
+
this.updateScoreDisplay();
|
|
379
|
+
}
|
|
380
|
+
removeBalloon(h) {
|
|
381
|
+
const b = this.balloons.get(h);
|
|
382
|
+
if (!b) return;
|
|
383
|
+
this.remove(b.mesh);
|
|
384
|
+
this.physicsWorld.removeCollider(b.collider, false);
|
|
385
|
+
this.physicsWorld.removeRigidBody(b.rigidBody);
|
|
386
|
+
this.balloons.delete(h);
|
|
387
|
+
}
|
|
388
|
+
removeDart(h) {
|
|
389
|
+
const d = this.darts.get(h);
|
|
390
|
+
if (!d) return;
|
|
391
|
+
this.remove(d.mesh);
|
|
392
|
+
this.physicsWorld.removeCollider(d.collider, false);
|
|
393
|
+
this.physicsWorld.removeRigidBody(d.rigidBody);
|
|
394
|
+
this.darts.delete(h);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
399
|
+
const o = new xb.Options();
|
|
400
|
+
o.simulator.instructions.enabled = false;
|
|
401
|
+
o.enableUI();
|
|
402
|
+
o.physics.RAPIER = RAPIER;
|
|
403
|
+
o.physics.useEventQueue = true;
|
|
404
|
+
o.physics.worldStep = true;
|
|
405
|
+
o.hands.enabled = true;
|
|
406
|
+
o.simulator.defaultMode = xb.SimulatorMode.POSE;
|
|
407
|
+
|
|
408
|
+
o.depth.enabled = false;
|
|
409
|
+
if (o.depth.depthMesh) {
|
|
410
|
+
o.depth.depthMesh.enabled = true;
|
|
411
|
+
o.depth.depthMesh.physicsEnabled = true;
|
|
412
|
+
o.depth.depthMesh.collisionGroups = GROUP_WORLD;
|
|
413
|
+
o.depth.depthMesh.colliderUpdateFps = 5;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
xb.add(new BalloonGame());
|
|
417
|
+
xb.init(o);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
</script>
|
|
421
|
+
</body>
|
|
422
|
+
</html>
|