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.
Files changed (185) hide show
  1. package/README.md +25 -8
  2. package/bin/nova64.js +165 -0
  3. package/dist/assets/console-CY_kygm3.js +14 -0
  4. package/dist/assets/console-CY_kygm3.js.map +1 -0
  5. package/dist/assets/main-l0sNRNKZ.js.map +1 -0
  6. package/dist/assets/sky/studio/nx.png +0 -0
  7. package/dist/assets/sky/studio/ny.png +0 -0
  8. package/dist/assets/sky/studio/nz.png +0 -0
  9. package/dist/assets/sky/studio/px.png +0 -0
  10. package/dist/assets/sky/studio/py.png +0 -0
  11. package/dist/assets/sky/studio/pz.png +0 -0
  12. package/dist/assets/vanilla-Dcuy32gi.js +2 -0
  13. package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
  14. package/dist/console.html +899 -0
  15. package/dist/docs/BENCHMARK.md +77 -0
  16. package/dist/docs/CHEATSHEET.md +255 -0
  17. package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
  18. package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
  19. package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
  20. package/dist/docs/FREE_GLB_ASSETS.md +330 -0
  21. package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
  22. package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
  23. package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
  24. package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
  25. package/dist/docs/MIGRATION_GUIDE.md +553 -0
  26. package/dist/docs/NOVA64_3D_API.md +356 -0
  27. package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
  28. package/dist/docs/NOVA64_UI_API.md +503 -0
  29. package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
  30. package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
  31. package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
  32. package/dist/docs/api-3d.html +750 -0
  33. package/dist/docs/api-effects.html +385 -0
  34. package/dist/docs/api-improvements.md +121 -0
  35. package/dist/docs/api-skybox.html +407 -0
  36. package/dist/docs/api-sprites.html +321 -0
  37. package/dist/docs/api-voxel.html +337 -0
  38. package/dist/docs/api.html +543 -0
  39. package/dist/docs/assets.html +306 -0
  40. package/dist/docs/audio.html +340 -0
  41. package/dist/docs/blogs.html +286 -0
  42. package/dist/docs/collision.html +316 -0
  43. package/dist/docs/console.html +247 -0
  44. package/dist/docs/editor.html +297 -0
  45. package/dist/docs/font.html +247 -0
  46. package/dist/docs/framebuffer.html +247 -0
  47. package/dist/docs/fullscreen-button.html +297 -0
  48. package/dist/docs/gpu-systems.html +247 -0
  49. package/dist/docs/index.html +580 -0
  50. package/dist/docs/input.html +491 -0
  51. package/dist/docs/physics.html +311 -0
  52. package/dist/docs/screens.html +311 -0
  53. package/dist/docs/storage.html +311 -0
  54. package/dist/docs/textinput.html +332 -0
  55. package/dist/docs/ui.html +488 -0
  56. package/dist/examples/3d-advanced/code.js +695 -0
  57. package/dist/examples/adventure-comic-3d/code.js +342 -0
  58. package/dist/examples/audio-lab/code.js +150 -0
  59. package/dist/examples/boids-flocking/code.js +270 -0
  60. package/dist/examples/crystal-cathedral-3d/code.js +706 -0
  61. package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
  62. package/dist/examples/demoscene/README.md +192 -0
  63. package/dist/examples/demoscene/code.js +1081 -0
  64. package/dist/examples/demoscene/meta.json +21 -0
  65. package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
  66. package/dist/examples/f-zero-nova-3d/code.js +865 -0
  67. package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
  68. package/dist/examples/fps-demo-3d/code.js +744 -0
  69. package/dist/examples/game-of-life-3d/code.js +338 -0
  70. package/dist/examples/generative-art/code.js +632 -0
  71. package/dist/examples/hello-3d/code.js +325 -0
  72. package/dist/examples/hello-skybox/code.js +183 -0
  73. package/dist/examples/hello-world/code.js +19 -0
  74. package/dist/examples/input-showcase/code.js +109 -0
  75. package/dist/examples/instancing-demo/code.js +315 -0
  76. package/dist/examples/minecraft-demo/code.js +387 -0
  77. package/dist/examples/model-viewer-3d/code.js +114 -0
  78. package/dist/examples/mystical-realm-3d/code.js +1203 -0
  79. package/dist/examples/nature-explorer-3d/code.js +1318 -0
  80. package/dist/examples/particles-demo/code.js +522 -0
  81. package/dist/examples/pbr-showcase/code.js +140 -0
  82. package/dist/examples/physics-demo-3d/code.js +948 -0
  83. package/dist/examples/screen-demo/code.js +267 -0
  84. package/dist/examples/shooter-demo-3d/code.js +1286 -0
  85. package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
  86. package/dist/examples/space-combat-3d/README.md +135 -0
  87. package/dist/examples/space-combat-3d/code.js +1332 -0
  88. package/dist/examples/space-harrier-3d/code.js +923 -0
  89. package/dist/examples/star-fox-nova-3d/code.js +1116 -0
  90. package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
  91. package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
  92. package/dist/examples/storage-quest/code.js +209 -0
  93. package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
  94. package/dist/examples/strider-demo-3d/cache-test.html +132 -0
  95. package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
  96. package/dist/examples/strider-demo-3d/code-old.js +1537 -0
  97. package/dist/examples/strider-demo-3d/code.js +1462 -0
  98. package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
  99. package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
  100. package/dist/examples/super-plumber-64/README.md +128 -0
  101. package/dist/examples/super-plumber-64/code.js +1185 -0
  102. package/dist/examples/super-plumber-64/index.html +88 -0
  103. package/dist/examples/test-2d-overlay/code.js +32 -0
  104. package/dist/examples/test-font/code.js +51 -0
  105. package/dist/examples/test-minimal/code.js +21 -0
  106. package/dist/examples/ui-demo/code.js +306 -0
  107. package/dist/examples/wing-commander-space/README.md +180 -0
  108. package/dist/examples/wing-commander-space/code.js +1285 -0
  109. package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
  110. package/dist/examples/wizardry-3d/code.js +3928 -0
  111. package/dist/index.html +666 -0
  112. package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
  113. package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
  114. package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
  115. package/dist/os9-shell/index.html +23 -0
  116. package/dist/os9-shell/nova-icon.svg +12 -0
  117. package/dist/runtime/api-2d.js +1158 -0
  118. package/dist/runtime/api-3d/camera.js +73 -0
  119. package/dist/runtime/api-3d/instancing.js +180 -0
  120. package/dist/runtime/api-3d/lights.js +51 -0
  121. package/dist/runtime/api-3d/materials.js +47 -0
  122. package/dist/runtime/api-3d/models.js +84 -0
  123. package/dist/runtime/api-3d/particles.js +296 -0
  124. package/dist/runtime/api-3d/pbr.js +113 -0
  125. package/dist/runtime/api-3d/primitives.js +304 -0
  126. package/dist/runtime/api-3d/scene.js +169 -0
  127. package/dist/runtime/api-3d/transforms.js +161 -0
  128. package/dist/runtime/api-3d.js +166 -0
  129. package/dist/runtime/api-effects.js +840 -0
  130. package/dist/runtime/api-gameutils.js +476 -0
  131. package/dist/runtime/api-generative.js +610 -0
  132. package/dist/runtime/api-presets.js +85 -0
  133. package/dist/runtime/api-skybox.js +232 -0
  134. package/dist/runtime/api-sprites.js +100 -0
  135. package/dist/runtime/api-voxel.js +712 -0
  136. package/dist/runtime/api.js +201 -0
  137. package/dist/runtime/assets.js +27 -0
  138. package/dist/runtime/audio.js +114 -0
  139. package/dist/runtime/collision.js +47 -0
  140. package/dist/runtime/console.js +101 -0
  141. package/dist/runtime/editor.js +233 -0
  142. package/dist/runtime/font.js +233 -0
  143. package/dist/runtime/framebuffer.js +28 -0
  144. package/dist/runtime/fullscreen-button.js +185 -0
  145. package/dist/runtime/gpu-canvas2d.js +47 -0
  146. package/dist/runtime/gpu-threejs.js +643 -0
  147. package/dist/runtime/gpu-webgl2.js +310 -0
  148. package/dist/runtime/index.d.ts +682 -0
  149. package/dist/runtime/index.js +22 -0
  150. package/dist/runtime/input.js +225 -0
  151. package/dist/runtime/logger.js +60 -0
  152. package/dist/runtime/physics.js +101 -0
  153. package/dist/runtime/screens.js +213 -0
  154. package/dist/runtime/storage.js +38 -0
  155. package/dist/runtime/store.js +151 -0
  156. package/dist/runtime/textinput.js +68 -0
  157. package/dist/runtime/ui/buttons.js +124 -0
  158. package/dist/runtime/ui/panels.js +105 -0
  159. package/dist/runtime/ui/text.js +86 -0
  160. package/dist/runtime/ui/widgets.js +141 -0
  161. package/dist/runtime/ui.js +111 -0
  162. package/index.html +6 -1
  163. package/package.json +9 -2
  164. package/public/assets/sky/studio/nx.png +0 -0
  165. package/public/assets/sky/studio/ny.png +0 -0
  166. package/public/assets/sky/studio/nz.png +0 -0
  167. package/public/assets/sky/studio/px.png +0 -0
  168. package/public/assets/sky/studio/py.png +0 -0
  169. package/public/assets/sky/studio/pz.png +0 -0
  170. package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
  171. package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
  172. package/public/os9-shell/index.html +10 -1
  173. package/runtime/api-2d.js +301 -21
  174. package/runtime/api-3d/pbr.js +45 -1
  175. package/runtime/api-3d.js +1 -0
  176. package/runtime/api-effects.js +90 -3
  177. package/runtime/api-gameutils.js +476 -0
  178. package/runtime/api-generative.js +610 -0
  179. package/runtime/api-skybox.js +54 -0
  180. package/runtime/api-voxel.js +139 -28
  181. package/runtime/gpu-threejs.js +13 -9
  182. package/runtime/ui.js +2 -2
  183. package/src/main.js +20 -0
  184. package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
  185. 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
+ }