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,522 @@
1
+ // GPU Particle System Demo — Nova64
2
+ // Visually rich: fire columns, blizzard, electric forge
3
+ // Controls: 1/2/3 = scene, SPACE = burst, WASD = orbit, QE = zoom
4
+
5
+ let scene = 0; // 0=inferno, 1=blizzard, 2=forge
6
+ let systemIds = [];
7
+ let lightIds = []; // { id, baseX, baseZ, phase, fire/aurora/forge/electric }
8
+ let propIds = [];
9
+ let orbitAngle = 0.3;
10
+ let orbitDist = 18;
11
+ let orbitY = 8;
12
+ let frameCount = 0;
13
+ let burstCooldown = 0;
14
+ let sceneTime = 0;
15
+
16
+ const SCENES = ['\uD83D\uDD25 Inferno', '\u2745 Blizzard', '\u26A1 Forge'];
17
+
18
+ export function init() {
19
+ setCameraFOV(70);
20
+ buildScene(scene);
21
+ }
22
+
23
+ function clearSystems() {
24
+ systemIds.forEach(id => removeParticleSystem(id));
25
+ systemIds = [];
26
+ lightIds = [];
27
+ propIds = [];
28
+ sceneTime = 0;
29
+ }
30
+
31
+ function buildScene(idx) {
32
+ clearSystems();
33
+ clearScene();
34
+ if (idx === 0) buildFire();
35
+ else if (idx === 1) buildBlizzard();
36
+ else if (idx === 2) buildForge();
37
+ }
38
+
39
+ // ── Scene 0: Inferno — 3 brazier fire columns with smoke and embers ───────────
40
+ function buildFire() {
41
+ setAmbientLight(0x110400, 0.25);
42
+ setFog(0x050100, 12, 50);
43
+ enableBloom(2.5, 0.7, 0.2);
44
+
45
+ const floor = createPlane(40, 40, 0x221100, [0, 0, 0], { material: 'standard', roughness: 1 });
46
+ setRotation(floor, -Math.PI / 2, 0, 0);
47
+ propIds.push(floor);
48
+
49
+ const cols = [
50
+ [-5, 0, 0],
51
+ [0, 0, -3],
52
+ [5, 0, 0],
53
+ ];
54
+ const endColors = [0xff3300, 0xff6600, 0xff9900];
55
+ for (const [px, , pz] of cols) {
56
+ propIds.push(
57
+ createCylinder(0.35, 2.5, 0x443322, [px, 1.25, pz], { material: 'standard', roughness: 0.9 })
58
+ );
59
+ propIds.push(
60
+ createTorus(0.55, 0.15, 0x553300, [px, 2.6, pz], {
61
+ material: 'standard',
62
+ roughness: 0.6,
63
+ metalness: 0.4,
64
+ })
65
+ );
66
+ }
67
+
68
+ for (let f = 0; f < 3; f++) {
69
+ const [fpx, , fpz] = cols[f];
70
+ // Core flames
71
+ systemIds.push(
72
+ createParticleSystem(280, {
73
+ shape: 'sphere',
74
+ segments: 3,
75
+ gravity: -5,
76
+ drag: 0.97,
77
+ emitterX: fpx,
78
+ emitterY: 2.7,
79
+ emitterZ: fpz,
80
+ emitRate: 75,
81
+ minLife: 0.8,
82
+ maxLife: 1.6,
83
+ minSpeed: 2,
84
+ maxSpeed: 5,
85
+ spread: 0.35,
86
+ minSize: 0.06,
87
+ maxSize: 0.28,
88
+ startColor: 0xffdd00,
89
+ endColor: endColors[f],
90
+ })
91
+ );
92
+ // Wide embers
93
+ systemIds.push(
94
+ createParticleSystem(100, {
95
+ shape: 'sphere',
96
+ segments: 3,
97
+ gravity: -1.5,
98
+ drag: 0.91,
99
+ emitterX: fpx,
100
+ emitterY: 3.0,
101
+ emitterZ: fpz,
102
+ emitRate: 20,
103
+ minLife: 0.5,
104
+ maxLife: 2.5,
105
+ minSpeed: 3,
106
+ maxSpeed: 10,
107
+ spread: 1.2,
108
+ minSize: 0.02,
109
+ maxSize: 0.07,
110
+ startColor: 0xffffff,
111
+ endColor: 0xff1100,
112
+ })
113
+ );
114
+ // Dark smoke
115
+ systemIds.push(
116
+ createParticleSystem(130, {
117
+ shape: 'sphere',
118
+ segments: 4,
119
+ gravity: -0.8,
120
+ drag: 0.99,
121
+ emitterX: fpx,
122
+ emitterY: 4.2,
123
+ emitterZ: fpz,
124
+ emitRate: 28,
125
+ minLife: 2.0,
126
+ maxLife: 3.5,
127
+ minSpeed: 0.4,
128
+ maxSpeed: 1.2,
129
+ spread: 0.6,
130
+ minSize: 0.15,
131
+ maxSize: 0.55,
132
+ startColor: 0x333333,
133
+ endColor: 0x111111,
134
+ })
135
+ );
136
+ // Flickering point light per column
137
+ lightIds.push({
138
+ id: createPointLight(0xff5500, 6, 18, fpx, 3.5, fpz),
139
+ baseX: fpx,
140
+ baseZ: fpz,
141
+ phase: f * 2.1,
142
+ });
143
+ }
144
+ }
145
+
146
+ // ── Scene 1: Blizzard — howling wind-driven snow with aurora ──────────────────
147
+ function buildBlizzard() {
148
+ setAmbientLight(0x0a1525, 0.5);
149
+ setFog(0x060c18, 14, 50);
150
+ enableBloom(1.2, 0.5, 0.3);
151
+
152
+ const floor = createPlane(60, 60, 0x8899bb, [0, 0, 0], {
153
+ material: 'standard',
154
+ roughness: 0.3,
155
+ metalness: 0.1,
156
+ });
157
+ setRotation(floor, -Math.PI / 2, 0, 0);
158
+ propIds.push(floor);
159
+
160
+ [
161
+ [-8, 0.5, -4],
162
+ [5, 0.8, 3],
163
+ [-3, 0.4, 7],
164
+ [9, 0.6, -2],
165
+ [-6, 0.3, 5],
166
+ [4, 0.5, -8],
167
+ ].forEach(([rx, ry, rz]) =>
168
+ propIds.push(
169
+ createSphere(0.8, 0x99aabb, [rx, ry, rz], {
170
+ material: 'standard',
171
+ roughness: 0.4,
172
+ metalness: 0.05,
173
+ })
174
+ )
175
+ );
176
+
177
+ // Wind-driven snow
178
+ systemIds.push(
179
+ createParticleSystem(900, {
180
+ shape: 'sphere',
181
+ segments: 3,
182
+ gravity: 3.5,
183
+ drag: 0.995,
184
+ emitterX: -10,
185
+ emitterY: 10,
186
+ emitterZ: 0,
187
+ emitRate: 200,
188
+ minLife: 2.5,
189
+ maxLife: 4.5,
190
+ minSpeed: 7,
191
+ maxSpeed: 15,
192
+ spread: 0.85,
193
+ minSize: 0.04,
194
+ maxSize: 0.18,
195
+ startColor: 0xffffff,
196
+ endColor: 0xaabbdd,
197
+ })
198
+ );
199
+ // Fine drifting mist
200
+ systemIds.push(
201
+ createParticleSystem(400, {
202
+ shape: 'sphere',
203
+ segments: 3,
204
+ gravity: 1.0,
205
+ drag: 0.998,
206
+ emitterX: 0,
207
+ emitterY: 8,
208
+ emitterZ: 0,
209
+ emitRate: 80,
210
+ minLife: 4.0,
211
+ maxLife: 7.0,
212
+ minSpeed: 0.5,
213
+ maxSpeed: 2.5,
214
+ spread: Math.PI,
215
+ minSize: 0.02,
216
+ maxSize: 0.08,
217
+ startColor: 0xddeeff,
218
+ endColor: 0x6688aa,
219
+ })
220
+ );
221
+ // Ground ice crystals blasted up by wind
222
+ systemIds.push(
223
+ createParticleSystem(200, {
224
+ shape: 'sphere',
225
+ segments: 3,
226
+ gravity: 12,
227
+ drag: 0.93,
228
+ emitterX: 0,
229
+ emitterY: 0.1,
230
+ emitterZ: 0,
231
+ emitRate: 35,
232
+ minLife: 0.5,
233
+ maxLife: 1.3,
234
+ minSpeed: 2,
235
+ maxSpeed: 8,
236
+ spread: 0.6,
237
+ minSize: 0.02,
238
+ maxSize: 0.09,
239
+ startColor: 0xeeffff,
240
+ endColor: 0x3366aa,
241
+ })
242
+ );
243
+
244
+ lightIds.push({
245
+ id: createPointLight(0x0044ff, 3, 40, 0, 14, 0),
246
+ baseX: 0,
247
+ baseZ: 0,
248
+ phase: 0,
249
+ aurora: true,
250
+ });
251
+ lightIds.push({
252
+ id: createPointLight(0x00ff88, 2, 30, 5, 11, -5),
253
+ baseX: 5,
254
+ baseZ: -5,
255
+ phase: 1.7,
256
+ aurora: true,
257
+ });
258
+ }
259
+
260
+ // ── Scene 2: Forge — electric anvil with sparks and plasma ────────────────────
261
+ function buildForge() {
262
+ setAmbientLight(0x050510, 0.2);
263
+ setFog(0x020208, 12, 40);
264
+ enableBloom(2.5, 0.7, 0.15);
265
+
266
+ const floor = createPlane(30, 30, 0x1a1a2a, [0, 0, 0], {
267
+ material: 'standard',
268
+ roughness: 0.8,
269
+ metalness: 0.6,
270
+ });
271
+ setRotation(floor, -Math.PI / 2, 0, 0);
272
+ propIds.push(floor);
273
+
274
+ propIds.push(
275
+ createCube(2.5, 0.6, 0x222222, [0, 0.3, 0], {
276
+ material: 'standard',
277
+ roughness: 0.5,
278
+ metalness: 0.9,
279
+ })
280
+ );
281
+ propIds.push(
282
+ createCube(2.0, 0.4, 0x333344, [0, 0.8, 0], {
283
+ material: 'standard',
284
+ roughness: 0.3,
285
+ metalness: 1.0,
286
+ })
287
+ );
288
+ propIds.push(
289
+ createCylinder(0.5, 0.2, 0xff6600, [0, 1.1, 0], {
290
+ material: 'standard',
291
+ roughness: 0.6,
292
+ metalness: 0.8,
293
+ emissive: 0xff3300,
294
+ emissiveIntensity: 3.0,
295
+ })
296
+ );
297
+
298
+ // Gold sparks — burst only
299
+ systemIds.push(
300
+ createParticleSystem(700, {
301
+ shape: 'sphere',
302
+ segments: 3,
303
+ gravity: 18,
304
+ drag: 0.91,
305
+ emitterX: 0,
306
+ emitterY: 1.2,
307
+ emitterZ: 0,
308
+ emitRate: 0,
309
+ minLife: 0.4,
310
+ maxLife: 1.2,
311
+ minSpeed: 6,
312
+ maxSpeed: 24,
313
+ spread: Math.PI * 0.7,
314
+ minSize: 0.015,
315
+ maxSize: 0.1,
316
+ startColor: 0xffffff,
317
+ endColor: 0xff2200,
318
+ })
319
+ );
320
+ // Blue plasma arcs — burst only
321
+ systemIds.push(
322
+ createParticleSystem(300, {
323
+ shape: 'sphere',
324
+ segments: 3,
325
+ gravity: 8,
326
+ drag: 0.88,
327
+ emitterX: 0,
328
+ emitterY: 1.2,
329
+ emitterZ: 0,
330
+ emitRate: 0,
331
+ minLife: 0.2,
332
+ maxLife: 0.7,
333
+ minSpeed: 8,
334
+ maxSpeed: 20,
335
+ spread: Math.PI,
336
+ minSize: 0.01,
337
+ maxSize: 0.06,
338
+ startColor: 0xaaffff,
339
+ endColor: 0x0000ff,
340
+ })
341
+ );
342
+ // Constant embers from hot metal
343
+ systemIds.push(
344
+ createParticleSystem(150, {
345
+ shape: 'sphere',
346
+ segments: 3,
347
+ gravity: 5,
348
+ drag: 0.94,
349
+ emitterX: 0,
350
+ emitterY: 1.1,
351
+ emitterZ: 0,
352
+ emitRate: 40,
353
+ minLife: 0.5,
354
+ maxLife: 1.8,
355
+ minSpeed: 1,
356
+ maxSpeed: 5,
357
+ spread: 0.8,
358
+ minSize: 0.02,
359
+ maxSize: 0.08,
360
+ startColor: 0xffcc00,
361
+ endColor: 0xff2200,
362
+ })
363
+ );
364
+ // Lightning streaks — burst only
365
+ systemIds.push(
366
+ createParticleSystem(120, {
367
+ shape: 'sphere',
368
+ segments: 3,
369
+ gravity: 0,
370
+ drag: 0.85,
371
+ emitterX: 0,
372
+ emitterY: 3,
373
+ emitterZ: 0,
374
+ emitRate: 0,
375
+ minLife: 0.08,
376
+ maxLife: 0.25,
377
+ minSpeed: 12,
378
+ maxSpeed: 35,
379
+ spread: 0.3,
380
+ minSize: 0.01,
381
+ maxSize: 0.04,
382
+ startColor: 0xffffff,
383
+ endColor: 0x4444ff,
384
+ })
385
+ );
386
+
387
+ lightIds.push({
388
+ id: createPointLight(0xff4400, 8, 15, 0, 2, 0),
389
+ baseX: 0,
390
+ baseZ: 0,
391
+ phase: 0,
392
+ forge: true,
393
+ });
394
+ lightIds.push({
395
+ id: createPointLight(0x4488ff, 5, 12, 0, 3, 0),
396
+ baseX: 0,
397
+ baseZ: 0,
398
+ phase: 0,
399
+ electric: true,
400
+ });
401
+ burstCooldown = 0.1; // kick off first hammer blow immediately
402
+ }
403
+
404
+ export function update(dt) {
405
+ frameCount++;
406
+ sceneTime += dt;
407
+ burstCooldown = Math.max(0, burstCooldown - dt);
408
+
409
+ // Camera orbit
410
+ if (key('KeyA') || key('ArrowLeft')) orbitAngle -= dt * 1.2;
411
+ if (key('KeyD') || key('ArrowRight')) orbitAngle += dt * 1.2;
412
+ if (key('KeyW') || key('ArrowUp')) orbitY = Math.min(20, orbitY + dt * 5);
413
+ if (key('KeyS') || key('ArrowDown')) orbitY = Math.max(2, orbitY - dt * 5);
414
+ if (key('KeyQ')) orbitDist = Math.min(40, orbitDist + dt * 8);
415
+ if (key('KeyE')) orbitDist = Math.max(6, orbitDist - dt * 8);
416
+
417
+ const cx = Math.sin(orbitAngle) * orbitDist;
418
+ const cz = Math.cos(orbitAngle) * orbitDist;
419
+ setCameraPosition(cx, orbitY, cz);
420
+ setCameraTarget(0, 2, 0);
421
+
422
+ // Scene switch
423
+ if (keyp('Digit1') || keyp('Numpad1')) {
424
+ scene = 0;
425
+ buildScene(0);
426
+ }
427
+ if (keyp('Digit2') || keyp('Numpad2')) {
428
+ scene = 1;
429
+ buildScene(1);
430
+ }
431
+ if (keyp('Digit3') || keyp('Numpad3')) {
432
+ scene = 2;
433
+ buildScene(2);
434
+ }
435
+
436
+ // Manual burst
437
+ if (keyp('Space') && burstCooldown <= 0) {
438
+ triggerBurst();
439
+ burstCooldown = 0.3;
440
+ }
441
+
442
+ // Animated lights
443
+ for (const ldata of lightIds) {
444
+ const t = sceneTime + ldata.phase;
445
+ if (ldata.aurora) {
446
+ setPointLightPosition(
447
+ ldata.id,
448
+ ldata.baseX + Math.sin(t * 0.4) * 6,
449
+ 10 + Math.sin(t * 0.7) * 2,
450
+ ldata.baseZ + Math.cos(t * 0.3) * 4
451
+ );
452
+ } else if (ldata.forge) {
453
+ setPointLightPosition(
454
+ ldata.id,
455
+ (Math.random() - 0.5) * 0.4,
456
+ 1.0 + Math.random() * 0.5,
457
+ (Math.random() - 0.5) * 0.4
458
+ );
459
+ } else if (ldata.electric) {
460
+ setPointLightPosition(
461
+ ldata.id,
462
+ (Math.random() - 0.5) * 0.7,
463
+ 2.5 + Math.random() * 0.5,
464
+ (Math.random() - 0.5) * 0.7
465
+ );
466
+ } else {
467
+ // Fire column flicker
468
+ setPointLightPosition(
469
+ ldata.id,
470
+ ldata.baseX + (Math.random() - 0.5) * 0.7,
471
+ 2.8 + Math.sin(t * 14 + ldata.phase) * 0.6,
472
+ ldata.baseZ + (Math.random() - 0.5) * 0.5
473
+ );
474
+ }
475
+ }
476
+
477
+ // Blizzard: animate wind gust and periodic ice burst
478
+ if (scene === 1) {
479
+ if (systemIds[0]) {
480
+ setParticleEmitter(systemIds[0], {
481
+ emitterX: -10 + Math.sin(sceneTime * 0.25) * 5,
482
+ emitterY: 9 + Math.sin(sceneTime * 0.5) * 2,
483
+ });
484
+ }
485
+ if (frameCount % 75 === 0 && systemIds[2]) burstParticles(systemIds[2], 50);
486
+ }
487
+
488
+ // Forge: auto-hammer every 1.4s
489
+ if (scene === 2 && burstCooldown <= 0) {
490
+ triggerBurst();
491
+ burstCooldown = 1.4;
492
+ }
493
+
494
+ updateParticles(dt);
495
+ }
496
+
497
+ function triggerBurst() {
498
+ if (scene === 0) {
499
+ // Extra ember burst from each brazier
500
+ for (let i = 1; i < systemIds.length; i += 3) burstParticles(systemIds[i], 50);
501
+ } else if (scene === 1) {
502
+ if (systemIds[2]) burstParticles(systemIds[2], 90);
503
+ } else if (scene === 2) {
504
+ if (systemIds[0]) burstParticles(systemIds[0], 180);
505
+ if (systemIds[1]) burstParticles(systemIds[1], 100);
506
+ if (systemIds[3]) burstParticles(systemIds[3], 70);
507
+ }
508
+ }
509
+
510
+ export function draw() {
511
+ const total = systemIds.reduce((s, id) => {
512
+ const st = getParticleStats(id);
513
+ return s + (st ? st.active : 0);
514
+ }, 0);
515
+
516
+ drawRoundedRect(0, 0, 320, 14, 0, rgba8(0, 0, 0, 150));
517
+ printCentered('[1] Inferno [2] Blizzard [3] Forge', 160, 2, rgba8(220, 200, 150, 255));
518
+
519
+ drawRoundedRect(0, 220, 320, 20, 0, rgba8(0, 0, 0, 130));
520
+ print('Scene: ' + SCENES[scene] + ' Particles: ' + total, 6, 222, rgba8(180, 255, 180, 255));
521
+ print('[SPACE] Burst [WASD] Orbit [QE] Zoom', 6, 231, rgba8(110, 110, 110, 220));
522
+ }
@@ -0,0 +1,140 @@
1
+ // 🔮 PBR SHOWCASE — Physically Based Rendering Material Demo
2
+ //
3
+ // A 5×5 grid of spheres demonstrates how metalness and roughness
4
+ // interact in a physically based rendering pipeline.
5
+ //
6
+ // X-axis → Metalness 0.0 (dielectric) → 1.0 (metallic)
7
+ // Y-axis ↑ Roughness 1.0 (matte) → 0.0 (mirror-smooth)
8
+ //
9
+ // No special assets needed — the engine's built-in PMREM environment
10
+ // map drives the reflections. Load your own cube-map with
11
+ // createImageSkybox([...6 urls]) to upgrade to full IBL reflections.
12
+
13
+ const COLS = 5;
14
+ const ROWS = 5;
15
+ const SPACING = 2.4;
16
+
17
+ // Warm neutral base that reads clearly for both dielectric and metallic
18
+ const BASE_COLOR = 0xd4c8b8;
19
+
20
+ let sphereIds = [];
21
+ let floorId = null;
22
+ let orbitAngle = Math.PI * 0.15; // start slightly to the side
23
+ let time = 0;
24
+
25
+ // ── Init ────────────────────────────────────────────────────────────────────
26
+ export async function init() {
27
+ setCameraPosition(0, 1.5, 16);
28
+ setCameraTarget(0, 0, 0);
29
+ setCameraFOV(58);
30
+
31
+ // Load the procedural studio cube-map — drives both the skybox and IBL
32
+ // PBR reflections automatically via scene.environment (PMREM-processed).
33
+ try {
34
+ await createImageSkybox([
35
+ '/assets/sky/studio/px.png',
36
+ '/assets/sky/studio/nx.png',
37
+ '/assets/sky/studio/py.png',
38
+ '/assets/sky/studio/ny.png',
39
+ '/assets/sky/studio/pz.png',
40
+ '/assets/sky/studio/nz.png',
41
+ ]);
42
+ } catch (e) {
43
+ // Fallback to a gradient sky if the images aren't available
44
+ createGradientSkybox(0x0c1828, 0x040810);
45
+ }
46
+ setFog(0x060e1a, 22, 60);
47
+
48
+ // Cool-white key + warm fill for good specular contrast
49
+ setAmbientLight(0x2a3a55, 1.6);
50
+ setLightDirection(-0.6, -1.0, -0.5);
51
+ setLightColor(0xffffff);
52
+
53
+ // Cinematic post-processing
54
+ enableBloom(0.5, 0.35, 0.35);
55
+ enableVignette(1.0, 0.75);
56
+ enableFXAA();
57
+
58
+ // Reflective floor — shows IBL reflections of the spheres
59
+ floorId = createPlane(50, 50, 0x080c14, [0, -3.3, 0]);
60
+ setRotation(floorId, -Math.PI / 2, 0, 0);
61
+ setPBRProperties(floorId, { metalness: 0.85, roughness: 0.08, envMapIntensity: 1.5 });
62
+
63
+ // ── 5×5 PBR grid ──────────────────────────────────────────────────────────
64
+ // Row 0 = roughness 1.0 (top = matte) Row 4 = roughness 0.0 (bottom = glossy)
65
+ // Col 0 = metalness 0.0 (dielectric) Col 4 = metalness 1.0 (metallic)
66
+ for (let row = 0; row < ROWS; row++) {
67
+ for (let col = 0; col < COLS; col++) {
68
+ const metalness = col / (COLS - 1); // 0 → 1
69
+ const roughness = row / (ROWS - 1); // 0 → 1 (row 0 = smooth top)
70
+
71
+ const x = (col - (COLS - 1) / 2) * SPACING;
72
+ const y = ((ROWS - 1) / 2 - row) * SPACING; // row 0 at top
73
+
74
+ // Slight colour shift: warm for dielectrics, cooler for metals
75
+ const color = lerpHex(0xd4c0a8, 0xc8d4e0, metalness);
76
+
77
+ const id = createSphere(0.95, color, [x, y, 0], 32, {});
78
+ setPBRProperties(id, { metalness, roughness });
79
+ sphereIds.push(id);
80
+ }
81
+ }
82
+ }
83
+
84
+ // ── Update ───────────────────────────────────────────────────────────────────
85
+ export function update(dt) {
86
+ time += dt;
87
+
88
+ // Slow idle orbit around the grid
89
+ orbitAngle += dt * 0.16;
90
+ const r = 15.5;
91
+ const camX = Math.sin(orbitAngle) * r;
92
+ const camZ = Math.cos(orbitAngle) * r;
93
+ const camY = 1.5 + Math.sin(time * 0.22) * 0.8;
94
+
95
+ setCameraPosition(camX, camY, camZ);
96
+ setCameraTarget(0, 0, 0);
97
+ }
98
+
99
+ // ── Draw (2D HUD) ─────────────────────────────────────────────────────────────
100
+ export function draw() {
101
+ // Title
102
+ printCentered('PBR SHOWCASE', 160, 7, 0xffffff, 1);
103
+
104
+ // Metalness axis label (bottom of screen)
105
+ print('METALNESS 0 ────────────────── 1', 26, 218, 0x6aadcc, 1);
106
+
107
+ // Roughness axis label (left edge, vertical)
108
+ print('ROUGH', 2, 56, 0xcc8844, 1);
109
+ print(' |', 4, 64, 0xcc8844, 1);
110
+ print(' |', 4, 72, 0xcc8844, 1);
111
+ print(' |', 4, 80, 0xcc8844, 1);
112
+ print(' |', 4, 88, 0xcc8844, 1);
113
+ print(' |', 4, 96, 0xcc8844, 1);
114
+ print(' |', 4, 104, 0xcc8844, 1);
115
+ print(' |', 4, 112, 0xcc8844, 1);
116
+ print(' |', 4, 120, 0xcc8844, 1);
117
+ print(' |', 4, 128, 0xcc8844, 1);
118
+ print(' |', 4, 136, 0xcc8844, 1);
119
+ print(' |', 4, 144, 0xcc8844, 1);
120
+ print(' |', 4, 152, 0xcc8844, 1);
121
+ print('SMOOTH', 1, 160, 0xcc8844, 1);
122
+
123
+ // Tip
124
+ print('IBL: studio cube-map env reflections active', 8, 228, 0x445566, 1);
125
+ }
126
+
127
+ // ── Helpers ──────────────────────────────────────────────────────────────────
128
+ function lerpHex(a, b, t) {
129
+ const ar = (a >> 16) & 0xff,
130
+ ag = (a >> 8) & 0xff,
131
+ ab = a & 0xff;
132
+ const br = (b >> 16) & 0xff,
133
+ bg = (b >> 8) & 0xff,
134
+ bb = b & 0xff;
135
+ return (
136
+ (Math.round(ar + (br - ar) * t) << 16) |
137
+ (Math.round(ag + (bg - ag) * t) << 8) |
138
+ Math.round(ab + (bb - ab) * t)
139
+ );
140
+ }