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,610 @@
1
+ // runtime/api-generative.js
2
+ // Nova64 Generative Art API — Processing.js-style creative coding primitives
3
+ //
4
+ // Provides: Perlin noise, ellipse, arc, bezier curves, quadratic curves,
5
+ // matrix stack (push/pop/translate/rotate/scale), color modes (RGB/HSB),
6
+ // easing functions, flow fields, and particle emitters.
7
+ //
8
+ // Exposed globals: noise, noiseSeed, noiseDetail, ellipse, arc, bezier,
9
+ // quadCurve, pushMatrix, popMatrix, translate, rotate, scale2d,
10
+ // colorMode, hsb, lerpColor, noiseMap, flowField, TWO_PI, HALF_PI,
11
+ // QUARTER_PI, ease, smoothstep, frameCount
12
+
13
+ import { rgba8 } from './api.js';
14
+
15
+ export function generativeApi(gpu) {
16
+ const fb = gpu.getFramebuffer();
17
+
18
+ // ─── Constants ──────────────────────────────────────────────────────────────
19
+ const TWO_PI = Math.PI * 2;
20
+ const HALF_PI = Math.PI / 2;
21
+ const QUARTER_PI = Math.PI / 4;
22
+ let _frameCount = 0;
23
+
24
+ function _advanceFrame() {
25
+ _frameCount++;
26
+ }
27
+
28
+ // ─── Internal pixel writer (respects transform stack) ───────────────────────
29
+ function _pset(x, y, c) {
30
+ // Apply current transformation
31
+ const [tx, ty] = _applyTransform(x, y);
32
+ const ix = Math.round(tx);
33
+ const iy = Math.round(ty);
34
+ if (ix < 0 || iy < 0 || ix >= fb.width || iy >= fb.height) return;
35
+ const { r, g, b, a } = _unpackColor(c);
36
+ fb.pset(ix, iy, r, g, b, a);
37
+ }
38
+
39
+ function _unpackColor(c) {
40
+ if (typeof c === 'bigint') {
41
+ return {
42
+ r: Number((c >> 48n) & 0xffffn),
43
+ g: Number((c >> 32n) & 0xffffn),
44
+ b: Number((c >> 16n) & 0xffffn),
45
+ a: Number(c & 0xffffn),
46
+ };
47
+ }
48
+ const bc = BigInt(Math.floor(c));
49
+ return {
50
+ r: Number((bc >> 48n) & 0xffffn),
51
+ g: Number((bc >> 32n) & 0xffffn),
52
+ b: Number((bc >> 16n) & 0xffffn),
53
+ a: Number(bc & 0xffffn),
54
+ };
55
+ }
56
+
57
+ // ─── Perlin Noise (classic improved, 2D/3D) ────────────────────────────────
58
+
59
+ // Permutation table (seeded)
60
+ let _perm = new Uint8Array(512);
61
+ // eslint-disable-next-line no-unused-vars
62
+ let _noiseSeed = 0;
63
+ let _noiseOctaves = 4;
64
+ let _noiseFalloff = 0.5;
65
+
66
+ function _initPerm(seed) {
67
+ const p = new Uint8Array(256);
68
+ for (let i = 0; i < 256; i++) p[i] = i;
69
+ // Fisher-Yates shuffle with seed
70
+ let s = seed | 0;
71
+ for (let i = 255; i > 0; i--) {
72
+ s = (s * 1664525 + 1013904223) & 0x7fffffff;
73
+ const j = s % (i + 1);
74
+ [p[i], p[j]] = [p[j], p[i]];
75
+ }
76
+ for (let i = 0; i < 512; i++) _perm[i] = p[i & 255];
77
+ }
78
+
79
+ _initPerm(0);
80
+
81
+ const _grad3 = [
82
+ [1, 1, 0],
83
+ [-1, 1, 0],
84
+ [1, -1, 0],
85
+ [-1, -1, 0],
86
+ [1, 0, 1],
87
+ [-1, 0, 1],
88
+ [1, 0, -1],
89
+ [-1, 0, -1],
90
+ [0, 1, 1],
91
+ [0, -1, 1],
92
+ [0, 1, -1],
93
+ [0, -1, -1],
94
+ ];
95
+
96
+ function _fade(t) {
97
+ return t * t * t * (t * (t * 6 - 15) + 10);
98
+ }
99
+
100
+ function _perlin3(x, y, z) {
101
+ const X = Math.floor(x) & 255;
102
+ const Y = Math.floor(y) & 255;
103
+ const Z = Math.floor(z) & 255;
104
+ const xf = x - Math.floor(x);
105
+ const yf = y - Math.floor(y);
106
+ const zf = z - Math.floor(z);
107
+ const u = _fade(xf);
108
+ const v = _fade(yf);
109
+ const w = _fade(zf);
110
+
111
+ const A = _perm[X] + Y;
112
+ const AA = _perm[A] + Z;
113
+ const AB = _perm[A + 1] + Z;
114
+ const B = _perm[X + 1] + Y;
115
+ const BA = _perm[B] + Z;
116
+ const BB = _perm[B + 1] + Z;
117
+
118
+ const g = (hash, dx, dy, dz) => {
119
+ const gr = _grad3[hash % 12];
120
+ return gr[0] * dx + gr[1] * dy + gr[2] * dz;
121
+ };
122
+
123
+ return _lerp(
124
+ _lerp(
125
+ _lerp(g(_perm[AA], xf, yf, zf), g(_perm[BA], xf - 1, yf, zf), u),
126
+ _lerp(g(_perm[AB], xf, yf - 1, zf), g(_perm[BB], xf - 1, yf - 1, zf), u),
127
+ v
128
+ ),
129
+ _lerp(
130
+ _lerp(g(_perm[AA + 1], xf, yf, zf - 1), g(_perm[BA + 1], xf - 1, yf, zf - 1), u),
131
+ _lerp(g(_perm[AB + 1], xf, yf - 1, zf - 1), g(_perm[BB + 1], xf - 1, yf - 1, zf - 1), u),
132
+ v
133
+ ),
134
+ w
135
+ );
136
+ }
137
+
138
+ function _lerp(a, b, t) {
139
+ return a + (b - a) * t;
140
+ }
141
+
142
+ /**
143
+ * Perlin noise — returns value in [0, 1] range (centered ~0.5).
144
+ * noise(x) — 1D, noise(x, y) — 2D, noise(x, y, z) — 3D.
145
+ * Uses octaves and falloff set by noiseDetail().
146
+ */
147
+ function noise(x, y = 0, z = 0) {
148
+ let total = 0;
149
+ let amp = 1;
150
+ let freq = 1;
151
+ let maxAmp = 0;
152
+ for (let i = 0; i < _noiseOctaves; i++) {
153
+ total += _perlin3(x * freq, y * freq, z * freq) * amp;
154
+ maxAmp += amp;
155
+ amp *= _noiseFalloff;
156
+ freq *= 2;
157
+ }
158
+ // Normalize to 0..1
159
+ return (total / maxAmp + 1) * 0.5;
160
+ }
161
+
162
+ /** Set noise seed (integer). */
163
+ function noiseSeed(seed) {
164
+ _noiseSeed = seed;
165
+ _initPerm(seed);
166
+ }
167
+
168
+ /** Set noise octaves and falloff. */
169
+ function noiseDetail(octaves = 4, falloff = 0.5) {
170
+ _noiseOctaves = Math.max(1, Math.min(8, octaves));
171
+ _noiseFalloff = Math.max(0, Math.min(1, falloff));
172
+ }
173
+
174
+ /**
175
+ * Generate a 2D noise map as Float32Array.
176
+ * noiseMap(w, h, scale, offsetX, offsetY)
177
+ */
178
+ function noiseMap(w, h, scale = 0.02, offsetX = 0, offsetY = 0) {
179
+ const data = new Float32Array(w * h);
180
+ for (let y = 0; y < h; y++) {
181
+ for (let x = 0; x < w; x++) {
182
+ data[y * w + x] = noise((x + offsetX) * scale, (y + offsetY) * scale);
183
+ }
184
+ }
185
+ return data;
186
+ }
187
+
188
+ // ─── Transformation Matrix Stack ───────────────────────────────────────────
189
+
190
+ // 2D affine: [a, b, tx, c, d, ty] — column-major-ish
191
+ // [a c tx] [x] [ax + cy + tx]
192
+ // [b d ty] × [y] = [bx + dy + ty]
193
+ // [0 0 1] [1] [1 ]
194
+ let _matStack = [];
195
+ let _mat = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
196
+
197
+ function _applyTransform(x, y) {
198
+ return [_mat.a * x + _mat.c * y + _mat.tx, _mat.b * x + _mat.d * y + _mat.ty];
199
+ }
200
+
201
+ /** Save current transform. */
202
+ function pushMatrix() {
203
+ _matStack.push({ ..._mat });
204
+ }
205
+
206
+ /** Restore previous transform. */
207
+ function popMatrix() {
208
+ if (_matStack.length > 0) {
209
+ _mat = _matStack.pop();
210
+ }
211
+ }
212
+
213
+ /** Translate the coordinate origin. */
214
+ function translate(x, y) {
215
+ _mat.tx += _mat.a * x + _mat.c * y;
216
+ _mat.ty += _mat.b * x + _mat.d * y;
217
+ }
218
+
219
+ /** Rotate coordinates by angle (radians). */
220
+ function rotate(angle) {
221
+ const cos = Math.cos(angle);
222
+ const sin = Math.sin(angle);
223
+ const { a, b, c, d } = _mat;
224
+ _mat.a = a * cos + c * sin;
225
+ _mat.b = b * cos + d * sin;
226
+ _mat.c = a * -sin + c * cos;
227
+ _mat.d = b * -sin + d * cos;
228
+ }
229
+
230
+ /** Scale coordinates. scale2d(s) for uniform, scale2d(sx, sy) for non-uniform. */
231
+ function scale2d(sx, sy) {
232
+ if (sy === undefined) sy = sx;
233
+ _mat.a *= sx;
234
+ _mat.b *= sx;
235
+ _mat.c *= sy;
236
+ _mat.d *= sy;
237
+ }
238
+
239
+ /** Reset transform to identity. */
240
+ function resetMatrix() {
241
+ _mat = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
242
+ _matStack = [];
243
+ }
244
+
245
+ // ─── Shapes ────────────────────────────────────────────────────────────────
246
+
247
+ /**
248
+ * Draw an ellipse. ellipse(cx, cy, rx, ry, color, fill).
249
+ * If ry is omitted, draws a circle.
250
+ */
251
+ function ellipse(cx, cy, rx, ry, color, fill = true) {
252
+ if (ry === undefined) ry = rx;
253
+ if (fill) {
254
+ // Scanline fill
255
+ for (let dy = -ry; dy <= ry; dy++) {
256
+ const halfW = Math.sqrt(Math.max(0, 1 - (dy * dy) / (ry * ry))) * rx;
257
+ for (let dx = -Math.ceil(halfW); dx <= Math.ceil(halfW); dx++) {
258
+ _pset(cx + dx, cy + dy, color);
259
+ }
260
+ }
261
+ } else {
262
+ // Outline using parametric
263
+ const steps = Math.max(32, Math.ceil(Math.max(rx, ry) * 4));
264
+ for (let i = 0; i < steps; i++) {
265
+ const a = (i / steps) * TWO_PI;
266
+ _pset(Math.round(cx + Math.cos(a) * rx), Math.round(cy + Math.sin(a) * ry), color);
267
+ }
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Draw an arc. arc(cx, cy, rx, ry, startAngle, endAngle, color, fill).
273
+ * Angles in radians. Draws clockwise from start to end.
274
+ */
275
+ function arc(cx, cy, rx, ry, startAngle, endAngle, color, fill = false) {
276
+ const steps = Math.max(32, Math.ceil(Math.max(rx, ry) * 4));
277
+ const range = endAngle - startAngle;
278
+ if (fill) {
279
+ // Pie-slice fill: draw filled triangles from center
280
+ for (let i = 0; i < steps; i++) {
281
+ const a1 = startAngle + (i / steps) * range;
282
+ const a2 = startAngle + ((i + 1) / steps) * range;
283
+ const x1 = cx + Math.cos(a1) * rx;
284
+ const y1 = cy + Math.sin(a1) * ry;
285
+ const x2 = cx + Math.cos(a2) * rx;
286
+ const y2 = cy + Math.sin(a2) * ry;
287
+ // Fill triangle cx,cy → x1,y1 → x2,y2
288
+ _fillTriangle(cx, cy, x1, y1, x2, y2, color);
289
+ }
290
+ } else {
291
+ for (let i = 0; i <= steps; i++) {
292
+ const a = startAngle + (i / steps) * range;
293
+ _pset(Math.round(cx + Math.cos(a) * rx), Math.round(cy + Math.sin(a) * ry), color);
294
+ }
295
+ }
296
+ }
297
+
298
+ function _fillTriangle(x0, y0, x1, y1, x2, y2, color) {
299
+ // Simple scanline triangle fill
300
+ let pts = [
301
+ { x: x0, y: y0 },
302
+ { x: x1, y: y1 },
303
+ { x: x2, y: y2 },
304
+ ];
305
+ pts.sort((a, b) => a.y - b.y);
306
+
307
+ const [p0, p1, p2] = pts;
308
+ const totalHeight = p2.y - p0.y;
309
+ if (totalHeight < 1) return;
310
+
311
+ for (let y = Math.ceil(p0.y); y <= Math.floor(p2.y); y++) {
312
+ const secondHalf = y > p1.y || p1.y === p0.y;
313
+ const segH = secondHalf ? p2.y - p1.y : p1.y - p0.y;
314
+ if (segH < 0.5) continue;
315
+
316
+ const alpha = (y - p0.y) / totalHeight;
317
+ const beta = secondHalf ? (y - p1.y) / segH : (y - p0.y) / segH;
318
+
319
+ let xa = p0.x + (p2.x - p0.x) * alpha;
320
+ let xb = secondHalf ? p1.x + (p2.x - p1.x) * beta : p0.x + (p1.x - p0.x) * beta;
321
+ if (xa > xb) [xa, xb] = [xb, xa];
322
+
323
+ for (let x = Math.ceil(xa); x <= Math.floor(xb); x++) {
324
+ _pset(x, y, color);
325
+ }
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Draw a cubic bezier curve.
331
+ * bezier(x0, y0, cx0, cy0, cx1, cy1, x1, y1, color, detail)
332
+ */
333
+ function bezier(x0, y0, cx0, cy0, cx1, cy1, x1, y1, color, detail = 60) {
334
+ let prevX = x0,
335
+ prevY = y0;
336
+ for (let i = 1; i <= detail; i++) {
337
+ const t = i / detail;
338
+ const t2 = t * t;
339
+ const t3 = t2 * t;
340
+ const mt = 1 - t;
341
+ const mt2 = mt * mt;
342
+ const mt3 = mt2 * mt;
343
+ const x = mt3 * x0 + 3 * mt2 * t * cx0 + 3 * mt * t2 * cx1 + t3 * x1;
344
+ const y = mt3 * y0 + 3 * mt2 * t * cy0 + 3 * mt * t2 * cy1 + t3 * y1;
345
+ _drawLine(prevX, prevY, x, y, color);
346
+ prevX = x;
347
+ prevY = y;
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Draw a quadratic curve.
353
+ * quadCurve(x0, y0, cx, cy, x1, y1, color, detail)
354
+ */
355
+ function quadCurve(x0, y0, cx, cy, x1, y1, color, detail = 40) {
356
+ let prevX = x0,
357
+ prevY = y0;
358
+ for (let i = 1; i <= detail; i++) {
359
+ const t = i / detail;
360
+ const mt = 1 - t;
361
+ const x = mt * mt * x0 + 2 * mt * t * cx + t * t * x1;
362
+ const y = mt * mt * y0 + 2 * mt * t * cy + t * t * y1;
363
+ _drawLine(prevX, prevY, x, y, color);
364
+ prevX = x;
365
+ prevY = y;
366
+ }
367
+ }
368
+
369
+ /** Internal Bresenham line for curve segments */
370
+ function _drawLine(x0, y0, x1, y1, color) {
371
+ x0 = Math.round(x0);
372
+ y0 = Math.round(y0);
373
+ x1 = Math.round(x1);
374
+ y1 = Math.round(y1);
375
+ const dx = Math.abs(x1 - x0);
376
+ const dy = Math.abs(y1 - y0);
377
+ const sx = x0 < x1 ? 1 : -1;
378
+ const sy = y0 < y1 ? 1 : -1;
379
+ let err = dx - dy;
380
+ // eslint-disable-next-line no-constant-condition
381
+ while (true) {
382
+ _pset(x0, y0, color);
383
+ if (x0 === x1 && y0 === y1) break;
384
+ const e2 = 2 * err;
385
+ if (e2 > -dy) {
386
+ err -= dy;
387
+ x0 += sx;
388
+ }
389
+ if (e2 < dx) {
390
+ err += dx;
391
+ y0 += sy;
392
+ }
393
+ }
394
+ }
395
+
396
+ // ─── Color Modes ───────────────────────────────────────────────────────────
397
+
398
+ let _colorModeType = 'rgb'; // 'rgb' or 'hsb'
399
+ let _colorMax = [255, 255, 255, 255]; // max values for each channel
400
+
401
+ /**
402
+ * Set color mode. colorMode('rgb') or colorMode('hsb').
403
+ * Optional max values: colorMode('hsb', 360, 100, 100, 255)
404
+ */
405
+ function colorMode(mode, max1 = 255, max2, max3, maxA) {
406
+ _colorModeType = mode.toLowerCase();
407
+ if (max2 === undefined) {
408
+ _colorMax = [max1, max1, max1, max1];
409
+ } else {
410
+ _colorMax = [max1, max2 || max1, max3 || max1, maxA || 255];
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Create a color using current color mode.
416
+ * color(v1, v2, v3, alpha)
417
+ */
418
+ function color(v1, v2, v3, a) {
419
+ if (a === undefined) a = _colorMax[3];
420
+
421
+ // Normalize to 0-1
422
+ const n1 = v1 / _colorMax[0];
423
+ const n2 = v2 / _colorMax[1];
424
+ const n3 = v3 / _colorMax[2];
425
+ const na = a / _colorMax[3];
426
+
427
+ if (_colorModeType === 'hsb' || _colorModeType === 'hsv') {
428
+ // HSB to RGB
429
+ const h = (n1 * 360 + 360) % 360;
430
+ const s = Math.max(0, Math.min(1, n2));
431
+ const b = Math.max(0, Math.min(1, n3));
432
+ const c = b * s;
433
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
434
+ const m = b - c;
435
+ let r, g, bl;
436
+ if (h < 60) [r, g, bl] = [c, x, 0];
437
+ else if (h < 120) [r, g, bl] = [x, c, 0];
438
+ else if (h < 180) [r, g, bl] = [0, c, x];
439
+ else if (h < 240) [r, g, bl] = [0, x, c];
440
+ else if (h < 300) [r, g, bl] = [x, 0, c];
441
+ else [r, g, bl] = [c, 0, x];
442
+ return rgba8((r + m) * 255, (g + m) * 255, (bl + m) * 255, na * 255);
443
+ }
444
+
445
+ // RGB mode
446
+ return rgba8(n1 * 255, n2 * 255, n3 * 255, na * 255);
447
+ }
448
+
449
+ /**
450
+ * Create HSB color directly (convenience).
451
+ * hsb(hue, saturation, brightness, alpha)
452
+ * hue: 0-360, saturation: 0-1, brightness: 0-1, alpha: 0-255
453
+ */
454
+ function hsb(h, s = 1, b = 1, a = 255) {
455
+ h = ((h % 360) + 360) % 360;
456
+ const c = b * s;
457
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
458
+ const m = b - c;
459
+ let r, g, bl;
460
+ if (h < 60) [r, g, bl] = [c, x, 0];
461
+ else if (h < 120) [r, g, bl] = [x, c, 0];
462
+ else if (h < 180) [r, g, bl] = [0, c, x];
463
+ else if (h < 240) [r, g, bl] = [0, x, c];
464
+ else if (h < 300) [r, g, bl] = [x, 0, c];
465
+ else [r, g, bl] = [c, 0, x];
466
+ return rgba8((r + m) * 255, (g + m) * 255, (bl + m) * 255, a);
467
+ }
468
+
469
+ /**
470
+ * Interpolate between two colors. lerpColor(c1, c2, t).
471
+ * Works correctly regardless of color mode.
472
+ */
473
+ function lerpColor(c1, c2, t) {
474
+ const a = _unpackColor(c1);
475
+ const b = _unpackColor(c2);
476
+ return _packColor(
477
+ a.r + (b.r - a.r) * t,
478
+ a.g + (b.g - a.g) * t,
479
+ a.b + (b.b - a.b) * t,
480
+ a.a + (b.a - a.a) * t
481
+ );
482
+ }
483
+
484
+ function _packColor(r, g, b, a) {
485
+ return (
486
+ (BigInt(Math.round(r)) << 48n) |
487
+ (BigInt(Math.round(g)) << 32n) |
488
+ (BigInt(Math.round(b)) << 16n) |
489
+ BigInt(Math.round(a))
490
+ );
491
+ }
492
+
493
+ // ─── Easing Functions ──────────────────────────────────────────────────────
494
+
495
+ const _easings = {
496
+ linear: t => t,
497
+ easeInQuad: t => t * t,
498
+ easeOutQuad: t => t * (2 - t),
499
+ easeInOutQuad: t => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
500
+ easeInCubic: t => t * t * t,
501
+ easeOutCubic: t => --t * t * t + 1,
502
+ easeInOutCubic: t => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
503
+ easeInElastic: t =>
504
+ t === 0
505
+ ? 0
506
+ : t === 1
507
+ ? 1
508
+ : -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * ((2 * Math.PI) / 3)),
509
+ easeOutElastic: t =>
510
+ t === 0
511
+ ? 0
512
+ : t === 1
513
+ ? 1
514
+ : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * ((2 * Math.PI) / 3)) + 1,
515
+ easeOutBounce: t => {
516
+ if (t < 1 / 2.75) return 7.5625 * t * t;
517
+ if (t < 2 / 2.75) return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
518
+ if (t < 2.5 / 2.75) return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
519
+ return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
520
+ },
521
+ };
522
+
523
+ /**
524
+ * Apply an easing function. ease(t, type).
525
+ * Types: 'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad',
526
+ * 'easeInCubic', 'easeOutCubic', 'easeInOutCubic',
527
+ * 'easeInElastic', 'easeOutElastic', 'easeOutBounce'
528
+ */
529
+ function ease(t, type = 'easeInOutQuad') {
530
+ const fn = _easings[type] || _easings.linear;
531
+ return fn(Math.max(0, Math.min(1, t)));
532
+ }
533
+
534
+ /** Hermite smoothstep interpolation. */
535
+ function smoothstep(edge0, edge1, x) {
536
+ const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
537
+ return t * t * (3 - 2 * t);
538
+ }
539
+
540
+ // ─── Flow Field ────────────────────────────────────────────────────────────
541
+
542
+ /**
543
+ * Generate a 2D flow field (array of angle values driven by noise).
544
+ * flowField(cols, rows, scale, time)
545
+ * Returns Float32Array of angles in radians.
546
+ */
547
+ function flowField(cols, rows, scale = 0.06, time = 0) {
548
+ const field = new Float32Array(cols * rows);
549
+ for (let y = 0; y < rows; y++) {
550
+ for (let x = 0; x < cols; x++) {
551
+ const n = noise(x * scale, y * scale, time);
552
+ field[y * cols + x] = n * TWO_PI * 2;
553
+ }
554
+ }
555
+ return field;
556
+ }
557
+
558
+ // ─── expose ────────────────────────────────────────────────────────────────
559
+
560
+ return {
561
+ _advanceFrame,
562
+ exposeTo(target) {
563
+ Object.assign(target, {
564
+ // Constants
565
+ TWO_PI,
566
+ HALF_PI,
567
+ QUARTER_PI,
568
+
569
+ // Noise
570
+ noise,
571
+ noiseSeed,
572
+ noiseDetail,
573
+ noiseMap,
574
+
575
+ // Shapes
576
+ ellipse,
577
+ arc,
578
+ bezier,
579
+ quadCurve,
580
+
581
+ // Transform stack
582
+ pushMatrix,
583
+ popMatrix,
584
+ translate,
585
+ rotate,
586
+ scale2d,
587
+ resetMatrix,
588
+
589
+ // Color
590
+ colorMode,
591
+ color,
592
+ hsb,
593
+ lerpColor,
594
+
595
+ // Math
596
+ ease,
597
+ smoothstep,
598
+
599
+ // Generators
600
+ flowField,
601
+ });
602
+
603
+ // frameCount as getter
604
+ Object.defineProperty(target, 'frameCount', {
605
+ get: () => _frameCount,
606
+ configurable: true,
607
+ });
608
+ },
609
+ };
610
+ }
@@ -0,0 +1,85 @@
1
+ // runtime/api-presets.js
2
+ // One-call visual style presets for Nova64 — no new engine code required,
3
+ // each function is a curated combination of existing effects API calls.
4
+
5
+ export function presetsApi(gpu) {
6
+ // Internal: apply flat shading to every mesh currently in the scene
7
+ function _flatShadeMeshes() {
8
+ if (!gpu || !gpu.scene) return;
9
+ gpu.scene.traverse(obj => {
10
+ if (obj.isMesh && obj.material) {
11
+ obj.material.flatShading = true;
12
+ obj.material.needsUpdate = true;
13
+ }
14
+ });
15
+ }
16
+
17
+ function _smoothShadeMeshes() {
18
+ if (!gpu || !gpu.scene) return;
19
+ gpu.scene.traverse(obj => {
20
+ if (obj.isMesh && obj.material) {
21
+ obj.material.flatShading = false;
22
+ obj.material.needsUpdate = true;
23
+ }
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Nintendo 64 aesthetic: no bloom, crisp FXAA, flat vignette edge,
29
+ * and flat shading on all scene meshes.
30
+ */
31
+ function enableN64Mode() {
32
+ const g = globalThis;
33
+ if (typeof g.disableBloom === 'function') g.disableBloom();
34
+ if (typeof g.enableFXAA === 'function') g.enableFXAA();
35
+ if (typeof g.disableVignette === 'function') g.disableVignette();
36
+ if (typeof g.disableChromaticAberration === 'function') g.disableChromaticAberration();
37
+ _flatShadeMeshes();
38
+ }
39
+
40
+ /**
41
+ * PlayStation 1 aesthetic: soft bloom, aggressive vignette, chromatic
42
+ * aberration, and flat shading (matches the PSX dithered look).
43
+ */
44
+ function enablePSXMode() {
45
+ const g = globalThis;
46
+ if (typeof g.enableBloom === 'function') g.enableBloom(0.8, 0.6, 0.4);
47
+ if (typeof g.enableVignette === 'function') g.enableVignette(2.0, 0.75);
48
+ if (typeof g.enableChromaticAberration === 'function') g.enableChromaticAberration();
49
+ if (typeof g.enableFXAA === 'function') g.enableFXAA();
50
+ _flatShadeMeshes();
51
+ }
52
+
53
+ /**
54
+ * Low-poly / indie style: flat-shaded meshes, subtle bloom highlight, light
55
+ * vignette, no chromatic aberration.
56
+ */
57
+ function enableLowPolyMode() {
58
+ const g = globalThis;
59
+ if (typeof g.disableChromaticAberration === 'function') g.disableChromaticAberration();
60
+ if (typeof g.enableBloom === 'function') g.enableBloom(0.4, 0.3, 0.7);
61
+ if (typeof g.enableVignette === 'function') g.enableVignette(0.8, 0.95);
62
+ if (typeof g.enableFXAA === 'function') g.enableFXAA();
63
+ _flatShadeMeshes();
64
+ }
65
+
66
+ /** Restore default smooth shading and disable visual presets. */
67
+ function disablePresetMode() {
68
+ const g = globalThis;
69
+ if (typeof g.disableBloom === 'function') g.disableBloom();
70
+ if (typeof g.disableVignette === 'function') g.disableVignette();
71
+ if (typeof g.disableChromaticAberration === 'function') g.disableChromaticAberration();
72
+ _smoothShadeMeshes();
73
+ }
74
+
75
+ return {
76
+ exposeTo(target) {
77
+ Object.assign(target, {
78
+ enableN64Mode,
79
+ enablePSXMode,
80
+ enableLowPolyMode,
81
+ disablePresetMode,
82
+ });
83
+ },
84
+ };
85
+ }