nova64 0.2.5 → 0.2.7
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/README.md +25 -8
- package/bin/nova64.js +165 -0
- package/dist/assets/console-CY_kygm3.js +14 -0
- package/dist/assets/console-CY_kygm3.js.map +1 -0
- package/dist/assets/main-l0sNRNKZ.js.map +1 -0
- package/dist/assets/sky/studio/nx.png +0 -0
- package/dist/assets/sky/studio/ny.png +0 -0
- package/dist/assets/sky/studio/nz.png +0 -0
- package/dist/assets/sky/studio/px.png +0 -0
- package/dist/assets/sky/studio/py.png +0 -0
- package/dist/assets/sky/studio/pz.png +0 -0
- package/dist/assets/vanilla-Dcuy32gi.js +2 -0
- package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
- package/dist/console.html +899 -0
- package/dist/docs/BENCHMARK.md +77 -0
- package/dist/docs/CHEATSHEET.md +255 -0
- package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
- package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
- package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
- package/dist/docs/FREE_GLB_ASSETS.md +330 -0
- package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
- package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
- package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
- package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
- package/dist/docs/MIGRATION_GUIDE.md +553 -0
- package/dist/docs/NOVA64_3D_API.md +356 -0
- package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
- package/dist/docs/NOVA64_UI_API.md +503 -0
- package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
- package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
- package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
- package/dist/docs/api-3d.html +750 -0
- package/dist/docs/api-effects.html +385 -0
- package/dist/docs/api-improvements.md +121 -0
- package/dist/docs/api-skybox.html +407 -0
- package/dist/docs/api-sprites.html +321 -0
- package/dist/docs/api-voxel.html +337 -0
- package/dist/docs/api.html +543 -0
- package/dist/docs/assets.html +306 -0
- package/dist/docs/audio.html +340 -0
- package/dist/docs/blogs.html +286 -0
- package/dist/docs/collision.html +316 -0
- package/dist/docs/console.html +247 -0
- package/dist/docs/editor.html +297 -0
- package/dist/docs/font.html +247 -0
- package/dist/docs/framebuffer.html +247 -0
- package/dist/docs/fullscreen-button.html +297 -0
- package/dist/docs/gpu-systems.html +247 -0
- package/dist/docs/index.html +580 -0
- package/dist/docs/input.html +491 -0
- package/dist/docs/physics.html +311 -0
- package/dist/docs/screens.html +311 -0
- package/dist/docs/storage.html +311 -0
- package/dist/docs/textinput.html +332 -0
- package/dist/docs/ui.html +488 -0
- package/dist/examples/3d-advanced/code.js +695 -0
- package/dist/examples/adventure-comic-3d/code.js +342 -0
- package/dist/examples/audio-lab/code.js +150 -0
- package/dist/examples/boids-flocking/code.js +270 -0
- package/dist/examples/crystal-cathedral-3d/code.js +706 -0
- package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
- package/dist/examples/demoscene/README.md +192 -0
- package/dist/examples/demoscene/code.js +1081 -0
- package/dist/examples/demoscene/meta.json +21 -0
- package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
- package/dist/examples/f-zero-nova-3d/code.js +865 -0
- package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
- package/dist/examples/fps-demo-3d/code.js +744 -0
- package/dist/examples/game-of-life-3d/code.js +338 -0
- package/dist/examples/generative-art/code.js +632 -0
- package/dist/examples/hello-3d/code.js +325 -0
- package/dist/examples/hello-skybox/code.js +183 -0
- package/dist/examples/hello-world/code.js +19 -0
- package/dist/examples/input-showcase/code.js +109 -0
- package/dist/examples/instancing-demo/code.js +315 -0
- package/dist/examples/minecraft-demo/code.js +387 -0
- package/dist/examples/model-viewer-3d/code.js +114 -0
- package/dist/examples/mystical-realm-3d/code.js +1203 -0
- package/dist/examples/nature-explorer-3d/code.js +1318 -0
- package/dist/examples/particles-demo/code.js +522 -0
- package/dist/examples/pbr-showcase/code.js +140 -0
- package/dist/examples/physics-demo-3d/code.js +948 -0
- package/dist/examples/screen-demo/code.js +267 -0
- package/dist/examples/shooter-demo-3d/code.js +1286 -0
- package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
- package/dist/examples/space-combat-3d/README.md +135 -0
- package/dist/examples/space-combat-3d/code.js +1332 -0
- package/dist/examples/space-harrier-3d/code.js +923 -0
- package/dist/examples/star-fox-nova-3d/code.js +1116 -0
- package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
- package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
- package/dist/examples/storage-quest/code.js +209 -0
- package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
- package/dist/examples/strider-demo-3d/cache-test.html +132 -0
- package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
- package/dist/examples/strider-demo-3d/code-old.js +1537 -0
- package/dist/examples/strider-demo-3d/code.js +1462 -0
- package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
- package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
- package/dist/examples/super-plumber-64/README.md +128 -0
- package/dist/examples/super-plumber-64/code.js +1185 -0
- package/dist/examples/super-plumber-64/index.html +88 -0
- package/dist/examples/test-2d-overlay/code.js +32 -0
- package/dist/examples/test-font/code.js +51 -0
- package/dist/examples/test-minimal/code.js +21 -0
- package/dist/examples/ui-demo/code.js +306 -0
- package/dist/examples/wing-commander-space/README.md +180 -0
- package/dist/examples/wing-commander-space/code.js +1285 -0
- package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
- package/dist/examples/wizardry-3d/code.js +3928 -0
- package/dist/index.html +666 -0
- package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/dist/os9-shell/index.html +23 -0
- package/dist/os9-shell/nova-icon.svg +12 -0
- package/dist/runtime/api-2d.js +1158 -0
- package/dist/runtime/api-3d/camera.js +73 -0
- package/dist/runtime/api-3d/instancing.js +180 -0
- package/dist/runtime/api-3d/lights.js +51 -0
- package/dist/runtime/api-3d/materials.js +47 -0
- package/dist/runtime/api-3d/models.js +84 -0
- package/dist/runtime/api-3d/particles.js +296 -0
- package/dist/runtime/api-3d/pbr.js +113 -0
- package/dist/runtime/api-3d/primitives.js +304 -0
- package/dist/runtime/api-3d/scene.js +169 -0
- package/dist/runtime/api-3d/transforms.js +161 -0
- package/dist/runtime/api-3d.js +166 -0
- package/dist/runtime/api-effects.js +840 -0
- package/dist/runtime/api-gameutils.js +476 -0
- package/dist/runtime/api-generative.js +610 -0
- package/dist/runtime/api-presets.js +85 -0
- package/dist/runtime/api-skybox.js +232 -0
- package/dist/runtime/api-sprites.js +100 -0
- package/dist/runtime/api-voxel.js +712 -0
- package/dist/runtime/api.js +201 -0
- package/dist/runtime/assets.js +27 -0
- package/dist/runtime/audio.js +114 -0
- package/dist/runtime/collision.js +47 -0
- package/dist/runtime/console.js +101 -0
- package/dist/runtime/editor.js +233 -0
- package/dist/runtime/font.js +233 -0
- package/dist/runtime/framebuffer.js +28 -0
- package/dist/runtime/fullscreen-button.js +185 -0
- package/dist/runtime/gpu-canvas2d.js +47 -0
- package/dist/runtime/gpu-threejs.js +643 -0
- package/dist/runtime/gpu-webgl2.js +310 -0
- package/dist/runtime/index.d.ts +682 -0
- package/dist/runtime/index.js +22 -0
- package/dist/runtime/input.js +225 -0
- package/dist/runtime/logger.js +60 -0
- package/dist/runtime/physics.js +101 -0
- package/dist/runtime/screens.js +213 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/store.js +151 -0
- package/dist/runtime/textinput.js +68 -0
- package/dist/runtime/ui/buttons.js +124 -0
- package/dist/runtime/ui/panels.js +105 -0
- package/dist/runtime/ui/text.js +86 -0
- package/dist/runtime/ui/widgets.js +141 -0
- package/dist/runtime/ui.js +111 -0
- package/index.html +6 -1
- package/package.json +9 -2
- package/public/assets/sky/studio/nx.png +0 -0
- package/public/assets/sky/studio/ny.png +0 -0
- package/public/assets/sky/studio/nz.png +0 -0
- package/public/assets/sky/studio/px.png +0 -0
- package/public/assets/sky/studio/py.png +0 -0
- package/public/assets/sky/studio/pz.png +0 -0
- package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/public/os9-shell/index.html +10 -1
- package/runtime/api-2d.js +301 -21
- package/runtime/api-3d/pbr.js +45 -1
- package/runtime/api-3d.js +1 -0
- package/runtime/api-effects.js +90 -3
- package/runtime/api-gameutils.js +476 -0
- package/runtime/api-generative.js +610 -0
- package/runtime/api-skybox.js +54 -0
- package/runtime/api-voxel.js +139 -28
- package/runtime/gpu-threejs.js +13 -9
- package/runtime/ui.js +2 -2
- package/src/main.js +20 -0
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
// PHYSICS LAB 3D - Advanced 3D Physics Simulation
|
|
2
|
+
// Nintendo 64 / PlayStation style 3D physics with full GPU acceleration
|
|
3
|
+
|
|
4
|
+
// Game state
|
|
5
|
+
let gameTime = 0;
|
|
6
|
+
let selectedDemo = 0;
|
|
7
|
+
let showDebugInfo = true;
|
|
8
|
+
|
|
9
|
+
// Scoring & sandbox
|
|
10
|
+
let destructionScore = 0;
|
|
11
|
+
let objectsCreated = 0;
|
|
12
|
+
let biggestImpact = 0;
|
|
13
|
+
let collisionCount = 0;
|
|
14
|
+
let highScore = 0;
|
|
15
|
+
|
|
16
|
+
// Screen management
|
|
17
|
+
let gameState = 'start'; // 'start', 'simulating'
|
|
18
|
+
let startScreenTime = 0;
|
|
19
|
+
let uiButtons = [];
|
|
20
|
+
|
|
21
|
+
// 3D Physics objects
|
|
22
|
+
let physicsObjects = [];
|
|
23
|
+
let particles = [];
|
|
24
|
+
let forceFields = [];
|
|
25
|
+
let constraints = [];
|
|
26
|
+
|
|
27
|
+
// Demo configurations
|
|
28
|
+
const demos = [
|
|
29
|
+
{ name: 'BOUNCING SPHERES', setup: setupBouncingSpheres },
|
|
30
|
+
{ name: 'PENDULUM CHAIN', setup: setupPendulumChain },
|
|
31
|
+
{ name: 'PARTICLE FOUNTAIN', setup: setupParticleFountain },
|
|
32
|
+
{ name: 'GRAVITY WELL', setup: setupGravityWell },
|
|
33
|
+
{ name: 'COLLISION CASCADE', setup: setupCollisionCascade },
|
|
34
|
+
{ name: 'SANDBOX', setup: setupSandbox },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Physics constants
|
|
38
|
+
const GRAVITY = -15;
|
|
39
|
+
const BOUNCE_DAMPING = 0.8;
|
|
40
|
+
const AIR_RESISTANCE = 0.995;
|
|
41
|
+
|
|
42
|
+
export async function init() {
|
|
43
|
+
cls();
|
|
44
|
+
|
|
45
|
+
// Setup 3D scene with N64-style aesthetics
|
|
46
|
+
setCameraPosition(0, 15, 25);
|
|
47
|
+
setCameraTarget(0, 5, 0);
|
|
48
|
+
setCameraFOV(60);
|
|
49
|
+
|
|
50
|
+
// Setup dramatic lighting
|
|
51
|
+
setLightDirection(-0.5, -1, -0.2);
|
|
52
|
+
setFog(0x1a1a2e, 40, 120);
|
|
53
|
+
|
|
54
|
+
// Enable retro effects
|
|
55
|
+
enablePixelation(1);
|
|
56
|
+
enableDithering(true);
|
|
57
|
+
enableBloom(1.0, 0.4, 0.3);
|
|
58
|
+
enableFXAA();
|
|
59
|
+
enableVignette(1.2, 0.9);
|
|
60
|
+
|
|
61
|
+
// Create world environment
|
|
62
|
+
await createWorld();
|
|
63
|
+
|
|
64
|
+
// Start with first demo
|
|
65
|
+
demos[selectedDemo].setup();
|
|
66
|
+
|
|
67
|
+
// Initialize start screen
|
|
68
|
+
initStartScreen();
|
|
69
|
+
|
|
70
|
+
console.log('Physics Lab 3D - 3D Physics Simulation initialized');
|
|
71
|
+
console.log('Use LEFT/RIGHT to change demos, UP to toggle debug, X to interact');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function initStartScreen() {
|
|
75
|
+
uiButtons = [];
|
|
76
|
+
|
|
77
|
+
uiButtons.push(
|
|
78
|
+
createButton(
|
|
79
|
+
centerX(240),
|
|
80
|
+
150,
|
|
81
|
+
240,
|
|
82
|
+
60,
|
|
83
|
+
'⚛ START SIMULATION',
|
|
84
|
+
() => {
|
|
85
|
+
console.log('🎯 START SIMULATION CLICKED! Changing gameState to simulating...');
|
|
86
|
+
gameState = 'simulating';
|
|
87
|
+
console.log('✅ gameState is now:', gameState);
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
normalColor: rgba8(50, 200, 100, 255),
|
|
91
|
+
hoverColor: rgba8(80, 230, 130, 255),
|
|
92
|
+
pressedColor: rgba8(30, 170, 80, 255),
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
uiButtons.push(
|
|
98
|
+
createButton(
|
|
99
|
+
centerX(200),
|
|
100
|
+
355,
|
|
101
|
+
200,
|
|
102
|
+
45,
|
|
103
|
+
'📊 DEMOS',
|
|
104
|
+
() => {
|
|
105
|
+
console.log('Physics demos: Bouncing, Pendulum, Fountain, Gravity, Cascade');
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
normalColor: rgba8(100, 150, 255, 255),
|
|
109
|
+
hoverColor: rgba8(130, 180, 255, 255),
|
|
110
|
+
pressedColor: rgba8(70, 120, 220, 255),
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function update(dt) {
|
|
117
|
+
gameTime += dt;
|
|
118
|
+
|
|
119
|
+
if (gameState === 'start') {
|
|
120
|
+
startScreenTime += dt;
|
|
121
|
+
updateAllButtons();
|
|
122
|
+
|
|
123
|
+
// Animate physics in background
|
|
124
|
+
updatePhysics(dt);
|
|
125
|
+
updateParticles(dt);
|
|
126
|
+
updateForceFields(dt);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
handleInput(dt);
|
|
131
|
+
updatePhysics(dt);
|
|
132
|
+
updateParticles(dt);
|
|
133
|
+
updateForceFields(dt);
|
|
134
|
+
updateCamera(dt);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function draw() {
|
|
138
|
+
if (gameState === 'start') {
|
|
139
|
+
drawStartScreen();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 3D scene is automatically rendered by GPU backend
|
|
144
|
+
// Draw UI overlay using 2D API
|
|
145
|
+
drawUI();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function drawStartScreen() {
|
|
149
|
+
// Scientific gradient background
|
|
150
|
+
drawGradientRect(0, 0, 640, 360, rgba8(20, 40, 30, 230), rgba8(10, 20, 15, 245), true);
|
|
151
|
+
|
|
152
|
+
// Animated title
|
|
153
|
+
setFont('huge');
|
|
154
|
+
setTextAlign('center');
|
|
155
|
+
const pulse = Math.sin(startScreenTime * 3) * 0.3 + 0.7;
|
|
156
|
+
const sciColor = rgba8(
|
|
157
|
+
Math.floor(pulse * 100),
|
|
158
|
+
Math.floor(pulse * 255),
|
|
159
|
+
Math.floor(pulse * 150),
|
|
160
|
+
255
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const bounce = Math.abs(Math.sin(startScreenTime * 4)) * 15;
|
|
164
|
+
drawTextShadow('PHYSICS', 320, 50 + bounce, sciColor, rgba8(0, 0, 0, 255), 6, 1);
|
|
165
|
+
drawTextShadow('LAB 3D', 320, 105, rgba8(255, 255, 255, 255), rgba8(0, 0, 0, 255), 6, 1);
|
|
166
|
+
|
|
167
|
+
// Subtitle
|
|
168
|
+
setFont('large');
|
|
169
|
+
const glow = Math.sin(startScreenTime * 4) * 0.2 + 0.8;
|
|
170
|
+
drawTextOutline(
|
|
171
|
+
'Advanced 3D Physics Simulation',
|
|
172
|
+
320,
|
|
173
|
+
165,
|
|
174
|
+
rgba8(100, 255, 150, Math.floor(glow * 255)),
|
|
175
|
+
rgba8(0, 0, 0, 255),
|
|
176
|
+
1
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Info panel
|
|
180
|
+
const panel = createPanel(centerX(480), 210, 480, 190, {
|
|
181
|
+
bgColor: rgba8(20, 35, 25, 210),
|
|
182
|
+
borderColor: rgba8(50, 200, 100, 255),
|
|
183
|
+
borderWidth: 3,
|
|
184
|
+
shadow: true,
|
|
185
|
+
gradient: true,
|
|
186
|
+
gradientColor: rgba8(30, 50, 35, 210),
|
|
187
|
+
});
|
|
188
|
+
drawPanel(panel);
|
|
189
|
+
|
|
190
|
+
setFont('normal');
|
|
191
|
+
setTextAlign('center');
|
|
192
|
+
drawText('5 PHYSICS SIMULATIONS', 320, 230, rgba8(100, 255, 150, 255), 1);
|
|
193
|
+
|
|
194
|
+
setFont('small');
|
|
195
|
+
drawText('⚛ Bouncing Spheres - Realistic collision physics', 320, 255, uiColors.light, 1);
|
|
196
|
+
drawText('⚛ Pendulum Chain - Connected body dynamics', 320, 270, uiColors.light, 1);
|
|
197
|
+
drawText('⚛ Particle Fountain - Mass particle system', 320, 285, uiColors.light, 1);
|
|
198
|
+
drawText('⚛ Gravity Well - Force field simulation', 320, 300, uiColors.light, 1);
|
|
199
|
+
|
|
200
|
+
setFont('tiny');
|
|
201
|
+
drawText('Use LEFT/RIGHT arrows to cycle demos', 320, 320, uiColors.secondary, 1);
|
|
202
|
+
|
|
203
|
+
// Draw buttons
|
|
204
|
+
drawAllButtons();
|
|
205
|
+
|
|
206
|
+
// Pulsing prompt
|
|
207
|
+
const alpha = Math.floor((Math.sin(startScreenTime * 6) * 0.5 + 0.5) * 255);
|
|
208
|
+
setFont('normal');
|
|
209
|
+
drawText('⚛ EXPERIENCE REAL-TIME 3D PHYSICS ⚛', 320, 430, rgba8(100, 255, 150, alpha), 1);
|
|
210
|
+
|
|
211
|
+
// Tech info
|
|
212
|
+
setFont('tiny');
|
|
213
|
+
drawText('Nintendo 64 / PlayStation Style Rendering', 320, 345, rgba8(150, 200, 150, 150), 1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function createWorld() {
|
|
217
|
+
// Create ground plane
|
|
218
|
+
const ground = createPlane(50, 50, 0x444466, [0, 0, 0]);
|
|
219
|
+
setRotation(ground, -Math.PI / 2, 0, 0);
|
|
220
|
+
|
|
221
|
+
// Create invisible walls for physics boundaries
|
|
222
|
+
const wallHeight = 20;
|
|
223
|
+
const walls = [
|
|
224
|
+
createCube(1, wallHeight, 50, [-25, wallHeight / 2, 0]), // Left wall
|
|
225
|
+
createCube(1, wallHeight, 50, [25, wallHeight / 2, 0]), // Right wall
|
|
226
|
+
createCube(50, wallHeight, 1, [0, wallHeight / 2, -25]), // Back wall
|
|
227
|
+
createCube(50, wallHeight, 1, [0, wallHeight / 2, 25]), // Front wall
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
// Make walls transparent
|
|
231
|
+
walls.forEach(wall => {
|
|
232
|
+
// Note: In a real implementation, you'd set material transparency
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Add some decorative elements
|
|
236
|
+
for (let i = 0; i < 8; i++) {
|
|
237
|
+
const pillar = createCube(2, 12, 0x666688, [
|
|
238
|
+
(Math.random() - 0.5) * 40,
|
|
239
|
+
6,
|
|
240
|
+
(Math.random() - 0.5) * 40,
|
|
241
|
+
]);
|
|
242
|
+
setScale(pillar, 0.8, 1, 0.8);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function handleInput(dt) {
|
|
247
|
+
// Switch demos
|
|
248
|
+
if (btnp(0)) {
|
|
249
|
+
// Left
|
|
250
|
+
selectedDemo = (selectedDemo - 1 + demos.length) % demos.length;
|
|
251
|
+
resetDemo();
|
|
252
|
+
}
|
|
253
|
+
if (btnp(1)) {
|
|
254
|
+
// Right
|
|
255
|
+
selectedDemo = (selectedDemo + 1) % demos.length;
|
|
256
|
+
resetDemo();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Toggle debug info
|
|
260
|
+
if (btnp(2)) {
|
|
261
|
+
// Up
|
|
262
|
+
showDebugInfo = !showDebugInfo;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Interact with current demo
|
|
266
|
+
if (btnp(5) || keyp('Space')) {
|
|
267
|
+
// X or SPACE
|
|
268
|
+
interactWithDemo();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Chaos mode (G key) — only in sandbox
|
|
272
|
+
if (keyp('KeyG') && selectedDemo === 5) {
|
|
273
|
+
spawnChaosBurst();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Reset demo
|
|
277
|
+
if (btnp(4)) {
|
|
278
|
+
// Z
|
|
279
|
+
resetDemo();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function resetDemo() {
|
|
284
|
+
// Clear existing objects
|
|
285
|
+
physicsObjects.forEach(obj => destroyMesh(obj.mesh));
|
|
286
|
+
particles.forEach(p => destroyMesh(p.mesh));
|
|
287
|
+
physicsObjects = [];
|
|
288
|
+
particles = [];
|
|
289
|
+
forceFields = [];
|
|
290
|
+
constraints = [];
|
|
291
|
+
|
|
292
|
+
// Save high score before reset
|
|
293
|
+
if (destructionScore > highScore) highScore = destructionScore;
|
|
294
|
+
destructionScore = 0;
|
|
295
|
+
objectsCreated = 0;
|
|
296
|
+
biggestImpact = 0;
|
|
297
|
+
collisionCount = 0;
|
|
298
|
+
|
|
299
|
+
// Setup new demo
|
|
300
|
+
demos[selectedDemo].setup();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function setupBouncingSpheres() {
|
|
304
|
+
// Create bouncing spheres with different materials
|
|
305
|
+
const materials = [
|
|
306
|
+
{ color: 0xff4444, bounce: 0.9, size: 0.8 },
|
|
307
|
+
{ color: 0x44ff44, bounce: 0.7, size: 1.0 },
|
|
308
|
+
{ color: 0x4444ff, bounce: 0.5, size: 1.2 },
|
|
309
|
+
{ color: 0xffff44, bounce: 1.1, size: 0.6 },
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
for (let i = 0; i < 12; i++) {
|
|
313
|
+
const material = materials[i % materials.length];
|
|
314
|
+
const sphere = createPhysicsObject({
|
|
315
|
+
mesh: createSphere(material.size, material.color, [
|
|
316
|
+
(Math.random() - 0.5) * 20,
|
|
317
|
+
5 + Math.random() * 10,
|
|
318
|
+
(Math.random() - 0.5) * 20,
|
|
319
|
+
]),
|
|
320
|
+
x: 0,
|
|
321
|
+
y: 0,
|
|
322
|
+
z: 0,
|
|
323
|
+
vx: (Math.random() - 0.5) * 10,
|
|
324
|
+
vy: Math.random() * 5,
|
|
325
|
+
vz: (Math.random() - 0.5) * 10,
|
|
326
|
+
radius: material.size,
|
|
327
|
+
bounce: material.bounce,
|
|
328
|
+
mass: material.size,
|
|
329
|
+
type: 'sphere',
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Update position from mesh
|
|
333
|
+
const pos = getPosition(sphere.mesh);
|
|
334
|
+
sphere.x = pos[0];
|
|
335
|
+
sphere.y = pos[1];
|
|
336
|
+
sphere.z = pos[2];
|
|
337
|
+
|
|
338
|
+
physicsObjects.push(sphere);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function setupPendulumChain() {
|
|
343
|
+
// Create a chain of connected pendulums
|
|
344
|
+
const chainLength = 6;
|
|
345
|
+
const segmentLength = 2;
|
|
346
|
+
|
|
347
|
+
for (let i = 0; i < chainLength; i++) {
|
|
348
|
+
const sphere = createPhysicsObject({
|
|
349
|
+
mesh: createSphere(0.5, 0xff6600 + i * 0x001100, [
|
|
350
|
+
i * segmentLength,
|
|
351
|
+
10 - i * segmentLength,
|
|
352
|
+
0,
|
|
353
|
+
]),
|
|
354
|
+
x: i * segmentLength,
|
|
355
|
+
y: 10 - i * segmentLength,
|
|
356
|
+
z: 0,
|
|
357
|
+
vx: 0,
|
|
358
|
+
vy: 0,
|
|
359
|
+
vz: 0,
|
|
360
|
+
radius: 0.5,
|
|
361
|
+
bounce: 0.1,
|
|
362
|
+
mass: 1,
|
|
363
|
+
type: 'pendulum',
|
|
364
|
+
chainIndex: i,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
physicsObjects.push(sphere);
|
|
368
|
+
|
|
369
|
+
// Create constraint to previous sphere (except first)
|
|
370
|
+
if (i > 0) {
|
|
371
|
+
constraints.push({
|
|
372
|
+
objA: physicsObjects[physicsObjects.length - 2],
|
|
373
|
+
objB: sphere,
|
|
374
|
+
restLength: segmentLength,
|
|
375
|
+
strength: 0.8,
|
|
376
|
+
});
|
|
377
|
+
} else {
|
|
378
|
+
// First sphere is anchored
|
|
379
|
+
sphere.anchored = true;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function setupParticleFountain() {
|
|
385
|
+
// Create a particle fountain effect
|
|
386
|
+
const fountain = {
|
|
387
|
+
x: 0,
|
|
388
|
+
y: 5,
|
|
389
|
+
z: 0,
|
|
390
|
+
spawnRate: 0.1,
|
|
391
|
+
lastSpawn: 0,
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
forceFields.push(fountain);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function setupGravityWell() {
|
|
398
|
+
// Create objects that orbit around a central gravity well
|
|
399
|
+
for (let i = 0; i < 8; i++) {
|
|
400
|
+
const angle = (i / 8) * Math.PI * 2;
|
|
401
|
+
const radius = 8 + Math.random() * 4;
|
|
402
|
+
|
|
403
|
+
const sphere = createPhysicsObject({
|
|
404
|
+
mesh: createSphere(0.6, 0x44ffff, [Math.cos(angle) * radius, 8, Math.sin(angle) * radius]),
|
|
405
|
+
x: Math.cos(angle) * radius,
|
|
406
|
+
y: 8,
|
|
407
|
+
z: Math.sin(angle) * radius,
|
|
408
|
+
vx: -Math.sin(angle) * 5,
|
|
409
|
+
vy: 0,
|
|
410
|
+
vz: Math.cos(angle) * 5,
|
|
411
|
+
radius: 0.6,
|
|
412
|
+
bounce: 0.8,
|
|
413
|
+
mass: 1,
|
|
414
|
+
type: 'orbiter',
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
physicsObjects.push(sphere);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Create central gravity well
|
|
421
|
+
forceFields.push({
|
|
422
|
+
type: 'gravity',
|
|
423
|
+
x: 0,
|
|
424
|
+
y: 8,
|
|
425
|
+
z: 0,
|
|
426
|
+
strength: 50,
|
|
427
|
+
radius: 15,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function setupCollisionCascade() {
|
|
432
|
+
// Create a domino-like cascade effect
|
|
433
|
+
for (let i = 0; i < 10; i++) {
|
|
434
|
+
const cube = createPhysicsObject({
|
|
435
|
+
mesh: createCube(1, 0x8844ff, [i * 2.5 - 12, 2, Math.sin(i * 0.5) * 3]),
|
|
436
|
+
x: i * 2.5 - 12,
|
|
437
|
+
y: 2,
|
|
438
|
+
z: Math.sin(i * 0.5) * 3,
|
|
439
|
+
vx: 0,
|
|
440
|
+
vy: 0,
|
|
441
|
+
vz: 0,
|
|
442
|
+
radius: 1,
|
|
443
|
+
bounce: 0.3,
|
|
444
|
+
mass: 2,
|
|
445
|
+
type: 'domino',
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
setScale(cube.mesh, 0.8, 3, 0.4);
|
|
449
|
+
physicsObjects.push(cube);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Add initial impulse object
|
|
453
|
+
const impulse = createPhysicsObject({
|
|
454
|
+
mesh: createSphere(1, 0xff4444, [-20, 5, 0]),
|
|
455
|
+
x: -20,
|
|
456
|
+
y: 5,
|
|
457
|
+
z: 0,
|
|
458
|
+
vx: 15,
|
|
459
|
+
vy: 0,
|
|
460
|
+
vz: 0,
|
|
461
|
+
radius: 1,
|
|
462
|
+
bounce: 0.9,
|
|
463
|
+
mass: 3,
|
|
464
|
+
type: 'impulse',
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
physicsObjects.push(impulse);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function setupSandbox() {
|
|
471
|
+
// Sandbox mode with objectives!
|
|
472
|
+
// Start with a tower of blocks to knock down
|
|
473
|
+
for (let row = 0; row < 5; row++) {
|
|
474
|
+
for (let col = 0; col < 3; col++) {
|
|
475
|
+
const cube = createPhysicsObject({
|
|
476
|
+
mesh: createCube(1.2, [0xff4444, 0x44ff44, 0x4488ff, 0xffff44, 0xff88ff][row], [
|
|
477
|
+
(col - 1) * 1.5,
|
|
478
|
+
row * 1.5 + 1,
|
|
479
|
+
0,
|
|
480
|
+
]),
|
|
481
|
+
x: (col - 1) * 1.5,
|
|
482
|
+
y: row * 1.5 + 1,
|
|
483
|
+
z: 0,
|
|
484
|
+
vx: 0,
|
|
485
|
+
vy: 0,
|
|
486
|
+
vz: 0,
|
|
487
|
+
radius: 0.8,
|
|
488
|
+
bounce: 0.3,
|
|
489
|
+
mass: 1,
|
|
490
|
+
type: 'target',
|
|
491
|
+
});
|
|
492
|
+
physicsObjects.push(cube);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const SANDBOX_SHAPES = ['sphere', 'cube', 'cone', 'cylinder'];
|
|
498
|
+
const SANDBOX_COLORS = [
|
|
499
|
+
0xff4444, 0x44ff44, 0x4488ff, 0xffff44, 0xff88ff, 0x44ffff, 0xff8800, 0x88ff00,
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
function sandboxSpawnObject() {
|
|
503
|
+
if (physicsObjects.length >= 60) return; // raised cap for more chaos
|
|
504
|
+
objectsCreated++;
|
|
505
|
+
const shape = SANDBOX_SHAPES[Math.floor(Math.random() * SANDBOX_SHAPES.length)];
|
|
506
|
+
const color = SANDBOX_COLORS[Math.floor(Math.random() * SANDBOX_COLORS.length)];
|
|
507
|
+
const size = 0.5 + Math.random() * 1.5;
|
|
508
|
+
const sx = (Math.random() - 0.5) * 16;
|
|
509
|
+
const sy = 12 + Math.random() * 8;
|
|
510
|
+
const sz = (Math.random() - 0.5) * 16;
|
|
511
|
+
let mesh;
|
|
512
|
+
if (shape === 'sphere') mesh = createSphere(size, color, [sx, sy, sz]);
|
|
513
|
+
else if (shape === 'cube') mesh = createCube(size, color, [sx, sy, sz]);
|
|
514
|
+
else if (shape === 'cone') mesh = createCone(size, size * 2, color, [sx, sy, sz]);
|
|
515
|
+
else mesh = createCylinder(size * 0.5, size * 1.5, color, [sx, sy, sz]);
|
|
516
|
+
|
|
517
|
+
physicsObjects.push(
|
|
518
|
+
createPhysicsObject({
|
|
519
|
+
mesh,
|
|
520
|
+
x: sx,
|
|
521
|
+
y: sy,
|
|
522
|
+
z: sz,
|
|
523
|
+
vx: (Math.random() - 0.5) * 8,
|
|
524
|
+
vy: (Math.random() - 0.5) * 4,
|
|
525
|
+
vz: (Math.random() - 0.5) * 8,
|
|
526
|
+
radius: size * 0.8,
|
|
527
|
+
bounce: 0.5 + Math.random() * 0.5,
|
|
528
|
+
mass: size,
|
|
529
|
+
type: 'sandbox',
|
|
530
|
+
})
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function spawnChaosBurst() {
|
|
535
|
+
for (let i = 0; i < 6; i++) sandboxSpawnObject();
|
|
536
|
+
// Also kick existing objects
|
|
537
|
+
physicsObjects.forEach(obj => {
|
|
538
|
+
obj.vx += (Math.random() - 0.5) * 20;
|
|
539
|
+
obj.vy += Math.random() * 15;
|
|
540
|
+
obj.vz += (Math.random() - 0.5) * 20;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function createPhysicsObject(props) {
|
|
545
|
+
return {
|
|
546
|
+
mesh: props.mesh,
|
|
547
|
+
x: props.x || 0,
|
|
548
|
+
y: props.y || 0,
|
|
549
|
+
z: props.z || 0,
|
|
550
|
+
vx: props.vx || 0,
|
|
551
|
+
vy: props.vy || 0,
|
|
552
|
+
vz: props.vz || 0,
|
|
553
|
+
radius: props.radius || 1,
|
|
554
|
+
bounce: props.bounce || 0.8,
|
|
555
|
+
mass: props.mass || 1,
|
|
556
|
+
type: props.type || 'generic',
|
|
557
|
+
anchored: props.anchored || false,
|
|
558
|
+
chainIndex: props.chainIndex || 0,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function updatePhysics(dt) {
|
|
563
|
+
physicsObjects.forEach(obj => {
|
|
564
|
+
if (obj.anchored) return;
|
|
565
|
+
|
|
566
|
+
// Apply gravity
|
|
567
|
+
obj.vy += GRAVITY * dt;
|
|
568
|
+
|
|
569
|
+
// Apply force fields
|
|
570
|
+
forceFields.forEach(field => {
|
|
571
|
+
if (field.type === 'gravity') {
|
|
572
|
+
const dx = field.x - obj.x;
|
|
573
|
+
const dy = field.y - obj.y;
|
|
574
|
+
const dz = field.z - obj.z;
|
|
575
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
576
|
+
|
|
577
|
+
if (distance < field.radius && distance > 0.1) {
|
|
578
|
+
const force = field.strength / (distance * distance);
|
|
579
|
+
obj.vx += (dx / distance) * force * dt;
|
|
580
|
+
obj.vy += (dy / distance) * force * dt;
|
|
581
|
+
obj.vz += (dz / distance) * force * dt;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Update position
|
|
587
|
+
obj.x += obj.vx * dt;
|
|
588
|
+
obj.y += obj.vy * dt;
|
|
589
|
+
obj.z += obj.vz * dt;
|
|
590
|
+
|
|
591
|
+
// Ground collision
|
|
592
|
+
if (obj.y - obj.radius < 0) {
|
|
593
|
+
obj.y = obj.radius;
|
|
594
|
+
obj.vy *= -obj.bounce;
|
|
595
|
+
createBounceParticles(obj.x, obj.y, obj.z);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Wall collisions
|
|
599
|
+
const wallBounds = 24;
|
|
600
|
+
if (obj.x - obj.radius < -wallBounds) {
|
|
601
|
+
obj.x = -wallBounds + obj.radius;
|
|
602
|
+
obj.vx *= -obj.bounce;
|
|
603
|
+
}
|
|
604
|
+
if (obj.x + obj.radius > wallBounds) {
|
|
605
|
+
obj.x = wallBounds - obj.radius;
|
|
606
|
+
obj.vx *= -obj.bounce;
|
|
607
|
+
}
|
|
608
|
+
if (obj.z - obj.radius < -wallBounds) {
|
|
609
|
+
obj.z = -wallBounds + obj.radius;
|
|
610
|
+
obj.vz *= -obj.bounce;
|
|
611
|
+
}
|
|
612
|
+
if (obj.z + obj.radius > wallBounds) {
|
|
613
|
+
obj.z = wallBounds - obj.radius;
|
|
614
|
+
obj.vz *= -obj.bounce;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Air resistance
|
|
618
|
+
obj.vx *= AIR_RESISTANCE;
|
|
619
|
+
obj.vy *= AIR_RESISTANCE;
|
|
620
|
+
obj.vz *= AIR_RESISTANCE;
|
|
621
|
+
|
|
622
|
+
// Update mesh position
|
|
623
|
+
setPosition(obj.mesh, obj.x, obj.y, obj.z);
|
|
624
|
+
|
|
625
|
+
// Add rotation for visual interest
|
|
626
|
+
if (obj.type !== 'pendulum') {
|
|
627
|
+
rotateMesh(obj.mesh, obj.vx * dt * 0.1, obj.vy * dt * 0.1, obj.vz * dt * 0.1);
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// Handle object-object collisions
|
|
632
|
+
for (let i = 0; i < physicsObjects.length; i++) {
|
|
633
|
+
for (let j = i + 1; j < physicsObjects.length; j++) {
|
|
634
|
+
checkCollision(physicsObjects[i], physicsObjects[j]);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Update constraints (for pendulum chains)
|
|
639
|
+
constraints.forEach(constraint => {
|
|
640
|
+
const dx = constraint.objB.x - constraint.objA.x;
|
|
641
|
+
const dy = constraint.objB.y - constraint.objA.y;
|
|
642
|
+
const dz = constraint.objB.z - constraint.objA.z;
|
|
643
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
644
|
+
|
|
645
|
+
if (distance > 0) {
|
|
646
|
+
const difference = (constraint.restLength - distance) / distance;
|
|
647
|
+
const force = difference * constraint.strength * 0.5;
|
|
648
|
+
|
|
649
|
+
const offsetX = dx * force;
|
|
650
|
+
const offsetY = dy * force;
|
|
651
|
+
const offsetZ = dz * force;
|
|
652
|
+
|
|
653
|
+
if (!constraint.objA.anchored) {
|
|
654
|
+
constraint.objA.x -= offsetX;
|
|
655
|
+
constraint.objA.y -= offsetY;
|
|
656
|
+
constraint.objA.z -= offsetZ;
|
|
657
|
+
}
|
|
658
|
+
if (!constraint.objB.anchored) {
|
|
659
|
+
constraint.objB.x += offsetX;
|
|
660
|
+
constraint.objB.y += offsetY;
|
|
661
|
+
constraint.objB.z += offsetZ;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function checkCollision(objA, objB) {
|
|
668
|
+
const dx = objB.x - objA.x;
|
|
669
|
+
const dy = objB.y - objA.y;
|
|
670
|
+
const dz = objB.z - objA.z;
|
|
671
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
672
|
+
const minDistance = objA.radius + objB.radius;
|
|
673
|
+
|
|
674
|
+
if (distance < minDistance && distance > 0) {
|
|
675
|
+
// Separate objects
|
|
676
|
+
const overlap = minDistance - distance;
|
|
677
|
+
const separationX = (dx / distance) * overlap * 0.5;
|
|
678
|
+
const separationY = (dy / distance) * overlap * 0.5;
|
|
679
|
+
const separationZ = (dz / distance) * overlap * 0.5;
|
|
680
|
+
|
|
681
|
+
if (!objA.anchored) {
|
|
682
|
+
objA.x -= separationX;
|
|
683
|
+
objA.y -= separationY;
|
|
684
|
+
objA.z -= separationZ;
|
|
685
|
+
}
|
|
686
|
+
if (!objB.anchored) {
|
|
687
|
+
objB.x += separationX;
|
|
688
|
+
objB.y += separationY;
|
|
689
|
+
objB.z += separationZ;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Calculate collision response
|
|
693
|
+
const normalX = dx / distance;
|
|
694
|
+
const normalY = dy / distance;
|
|
695
|
+
const normalZ = dz / distance;
|
|
696
|
+
|
|
697
|
+
const relativeVx = objB.vx - objA.vx;
|
|
698
|
+
const relativeVy = objB.vy - objA.vy;
|
|
699
|
+
const relativeVz = objB.vz - objA.vz;
|
|
700
|
+
|
|
701
|
+
const velocityAlongNormal = relativeVx * normalX + relativeVy * normalY + relativeVz * normalZ;
|
|
702
|
+
|
|
703
|
+
if (velocityAlongNormal > 0) return; // Objects separating
|
|
704
|
+
|
|
705
|
+
const restitution = Math.min(objA.bounce, objB.bounce);
|
|
706
|
+
const impulse = (-(1 + restitution) * velocityAlongNormal) / (1 / objA.mass + 1 / objB.mass);
|
|
707
|
+
|
|
708
|
+
// Track destruction score from impact force
|
|
709
|
+
const impactForce = Math.abs(impulse);
|
|
710
|
+
destructionScore += Math.floor(impactForce * 10);
|
|
711
|
+
collisionCount++;
|
|
712
|
+
if (impactForce > biggestImpact) biggestImpact = impactForce;
|
|
713
|
+
|
|
714
|
+
if (!objA.anchored) {
|
|
715
|
+
objA.vx -= (impulse * normalX) / objA.mass;
|
|
716
|
+
objA.vy -= (impulse * normalY) / objA.mass;
|
|
717
|
+
objA.vz -= (impulse * normalZ) / objA.mass;
|
|
718
|
+
}
|
|
719
|
+
if (!objB.anchored) {
|
|
720
|
+
objB.vx += (impulse * normalX) / objB.mass;
|
|
721
|
+
objB.vy += (impulse * normalY) / objB.mass;
|
|
722
|
+
objB.vz += (impulse * normalZ) / objB.mass;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Create collision particles
|
|
726
|
+
createCollisionParticles(objA.x + separationX, objA.y + separationY, objA.z + separationZ);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function updateParticles(dt) {
|
|
731
|
+
// Handle particle fountain
|
|
732
|
+
forceFields.forEach(field => {
|
|
733
|
+
if (field.spawnRate) {
|
|
734
|
+
field.lastSpawn -= dt;
|
|
735
|
+
if (field.lastSpawn <= 0) {
|
|
736
|
+
spawnParticle(field.x, field.y, field.z);
|
|
737
|
+
field.lastSpawn = field.spawnRate;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// Update existing particles
|
|
743
|
+
for (let i = particles.length - 1; i >= 0; i--) {
|
|
744
|
+
const particle = particles[i];
|
|
745
|
+
particle.life -= dt;
|
|
746
|
+
|
|
747
|
+
// Physics
|
|
748
|
+
particle.vy += GRAVITY * dt * 0.5; // Less gravity for particles
|
|
749
|
+
particle.x += particle.vx * dt;
|
|
750
|
+
particle.y += particle.vy * dt;
|
|
751
|
+
particle.z += particle.vz * dt;
|
|
752
|
+
|
|
753
|
+
// Ground bounce
|
|
754
|
+
if (particle.y < 0.1) {
|
|
755
|
+
particle.y = 0.1;
|
|
756
|
+
particle.vy *= -0.3;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
setPosition(particle.mesh, particle.x, particle.y, particle.z);
|
|
760
|
+
|
|
761
|
+
// Fade out
|
|
762
|
+
const scale = particle.life / particle.maxLife;
|
|
763
|
+
setScale(particle.mesh, scale);
|
|
764
|
+
|
|
765
|
+
if (particle.life <= 0) {
|
|
766
|
+
destroyMesh(particle.mesh);
|
|
767
|
+
particles.splice(i, 1);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function updateForceFields(dt) {
|
|
773
|
+
// Visual effects for force fields could go here
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function updateCamera(dt) {
|
|
777
|
+
// Smooth camera rotation around the scene
|
|
778
|
+
const radius = 25;
|
|
779
|
+
const height = 15;
|
|
780
|
+
const angle = gameTime * 0.2;
|
|
781
|
+
|
|
782
|
+
const x = Math.cos(angle) * radius;
|
|
783
|
+
const z = Math.sin(angle) * radius;
|
|
784
|
+
|
|
785
|
+
setCameraPosition(x, height, z);
|
|
786
|
+
setCameraTarget(0, 5, 0);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function interactWithDemo() {
|
|
790
|
+
switch (selectedDemo) {
|
|
791
|
+
case 0: {
|
|
792
|
+
// Bouncing spheres - add a new sphere
|
|
793
|
+
const sphere = createPhysicsObject({
|
|
794
|
+
mesh: createSphere(1, 0xffffff, [0, 15, 0]),
|
|
795
|
+
x: 0,
|
|
796
|
+
y: 15,
|
|
797
|
+
z: 0,
|
|
798
|
+
vx: (Math.random() - 0.5) * 10,
|
|
799
|
+
vy: 0,
|
|
800
|
+
vz: (Math.random() - 0.5) * 10,
|
|
801
|
+
radius: 1,
|
|
802
|
+
bounce: 0.9,
|
|
803
|
+
mass: 1,
|
|
804
|
+
type: 'sphere',
|
|
805
|
+
});
|
|
806
|
+
physicsObjects.push(sphere);
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
case 3: // Gravity well - add impulse
|
|
811
|
+
physicsObjects.forEach(obj => {
|
|
812
|
+
if (obj.type === 'orbiter') {
|
|
813
|
+
obj.vx += (Math.random() - 0.5) * 5;
|
|
814
|
+
obj.vz += (Math.random() - 0.5) * 5;
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
break;
|
|
818
|
+
|
|
819
|
+
case 4: {
|
|
820
|
+
// Collision cascade - reset impulse
|
|
821
|
+
const impulseObj = physicsObjects.find(obj => obj.type === 'impulse');
|
|
822
|
+
if (impulseObj) {
|
|
823
|
+
impulseObj.x = -20;
|
|
824
|
+
impulseObj.y = 5;
|
|
825
|
+
impulseObj.z = 0;
|
|
826
|
+
impulseObj.vx = 15;
|
|
827
|
+
impulseObj.vy = 0;
|
|
828
|
+
impulseObj.vz = 0;
|
|
829
|
+
setPosition(impulseObj.mesh, impulseObj.x, impulseObj.y, impulseObj.z);
|
|
830
|
+
}
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
case 5: {
|
|
835
|
+
// Sandbox — spawn a random object
|
|
836
|
+
sandboxSpawnObject();
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function spawnParticle(x, y, z) {
|
|
843
|
+
const particle = {
|
|
844
|
+
mesh: createSphere(0.2, 0xff6600, [x, y, z]),
|
|
845
|
+
x: x,
|
|
846
|
+
y: y,
|
|
847
|
+
z: z,
|
|
848
|
+
vx: (Math.random() - 0.5) * 8,
|
|
849
|
+
vy: Math.random() * 12 + 5,
|
|
850
|
+
vz: (Math.random() - 0.5) * 8,
|
|
851
|
+
life: 3,
|
|
852
|
+
maxLife: 3,
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
particles.push(particle);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function createBounceParticles(x, y, z) {
|
|
859
|
+
for (let i = 0; i < 3; i++) {
|
|
860
|
+
const particle = {
|
|
861
|
+
mesh: createSphere(0.1, 0xffaa44, [x, y, z]),
|
|
862
|
+
x: x,
|
|
863
|
+
y: y,
|
|
864
|
+
z: z,
|
|
865
|
+
vx: (Math.random() - 0.5) * 4,
|
|
866
|
+
vy: Math.random() * 3,
|
|
867
|
+
vz: (Math.random() - 0.5) * 4,
|
|
868
|
+
life: 1,
|
|
869
|
+
maxLife: 1,
|
|
870
|
+
};
|
|
871
|
+
particles.push(particle);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function createCollisionParticles(x, y, z) {
|
|
876
|
+
for (let i = 0; i < 5; i++) {
|
|
877
|
+
const particle = {
|
|
878
|
+
mesh: createSphere(0.08, 0xff4444, [x, y, z]),
|
|
879
|
+
x: x,
|
|
880
|
+
y: y,
|
|
881
|
+
z: z,
|
|
882
|
+
vx: (Math.random() - 0.5) * 6,
|
|
883
|
+
vy: Math.random() * 4,
|
|
884
|
+
vz: (Math.random() - 0.5) * 6,
|
|
885
|
+
life: 0.8,
|
|
886
|
+
maxLife: 0.8,
|
|
887
|
+
};
|
|
888
|
+
particles.push(particle);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function drawUI() {
|
|
893
|
+
// HUD Background
|
|
894
|
+
rect(16, 16, 450, 105, rgba8(0, 0, 0, 150), true);
|
|
895
|
+
rect(16, 16, 450, 105, rgba8(100, 100, 200, 100), false);
|
|
896
|
+
|
|
897
|
+
// Title and Demo Info
|
|
898
|
+
print('PHYSICS LAB 3D', 24, 24, rgba8(100, 200, 255, 255));
|
|
899
|
+
print(`DEMO: ${demos[selectedDemo].name}`, 24, 40, rgba8(255, 255, 255, 255));
|
|
900
|
+
|
|
901
|
+
// Stats
|
|
902
|
+
print(`OBJECTS: ${physicsObjects.length}`, 24, 56, rgba8(255, 215, 0, 255));
|
|
903
|
+
print(`PARTICLES: ${particles.length}`, 24, 72, rgba8(255, 100, 100, 255));
|
|
904
|
+
|
|
905
|
+
// Destruction score — the fun metric!
|
|
906
|
+
const scoreColor =
|
|
907
|
+
destructionScore > 500
|
|
908
|
+
? rgba8(255, 50, 50)
|
|
909
|
+
: destructionScore > 100
|
|
910
|
+
? rgba8(255, 200, 50)
|
|
911
|
+
: rgba8(100, 255, 100);
|
|
912
|
+
print(`DESTRUCTION: ${destructionScore}`, 24, 88, scoreColor);
|
|
913
|
+
print(`COLLISIONS: ${collisionCount}`, 24, 104, rgba8(200, 150, 255));
|
|
914
|
+
|
|
915
|
+
// Right side stats
|
|
916
|
+
print(`BIGGEST HIT: ${biggestImpact.toFixed(1)}`, 250, 56, rgba8(255, 150, 50));
|
|
917
|
+
if (highScore > 0) {
|
|
918
|
+
print(`HIGH SCORE: ${highScore}`, 250, 72, rgba8(255, 215, 0));
|
|
919
|
+
}
|
|
920
|
+
print(`SPAWNED: ${objectsCreated}`, 250, 88, rgba8(150, 200, 255));
|
|
921
|
+
|
|
922
|
+
// 3D Stats
|
|
923
|
+
const stats = get3DStats();
|
|
924
|
+
if (stats) {
|
|
925
|
+
print(`3D MESHES: ${stats.meshes || 0}`, 250, 104, rgba8(150, 150, 255, 255));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Debug info
|
|
929
|
+
if (showDebugInfo) {
|
|
930
|
+
print('DEBUG: ON', 250, 40, rgba8(100, 255, 100, 255));
|
|
931
|
+
} else {
|
|
932
|
+
print('DEBUG: OFF', 250, 40, rgba8(255, 100, 100, 255));
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Sandbox objectives hint
|
|
936
|
+
if (selectedDemo === 5) {
|
|
937
|
+
const hint = 'KNOCK DOWN THE TOWER! SPAM SPACE FOR CHAOS!';
|
|
938
|
+
printCentered(hint, 320, 135, rgba8(255, 200, 100, 200));
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Controls
|
|
942
|
+
print(
|
|
943
|
+
'←→ CHANGE DEMO ↑ DEBUG SPC/X INTERACT Z RESET G CHAOS(sandbox)',
|
|
944
|
+
24,
|
|
945
|
+
340,
|
|
946
|
+
rgba8(150, 150, 150, 200)
|
|
947
|
+
);
|
|
948
|
+
}
|