nova64 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/examples/strider-demo-3d/fix-game.sh +0 -0
- package/dist/os9-shell/assets/index-3Hr_q5dj.js +483 -0
- package/dist/os9-shell/assets/index-3Hr_q5dj.js.map +1 -0
- package/dist/os9-shell/index.html +2 -1
- package/dist/runtime/api-2d.js +1158 -0
- package/dist/runtime/api-3d/camera.js +73 -0
- package/dist/runtime/api-3d/instancing.js +180 -0
- package/dist/runtime/api-3d/lights.js +51 -0
- package/dist/runtime/api-3d/materials.js +47 -0
- package/dist/runtime/api-3d/models.js +84 -0
- package/dist/runtime/api-3d/particles.js +296 -0
- package/dist/runtime/api-3d/pbr.js +113 -0
- package/dist/runtime/api-3d/primitives.js +304 -0
- package/dist/runtime/api-3d/scene.js +169 -0
- package/dist/runtime/api-3d/transforms.js +161 -0
- package/dist/runtime/api-3d.js +166 -0
- package/dist/runtime/api-effects.js +840 -0
- package/dist/runtime/api-gameutils.js +476 -0
- package/dist/runtime/api-generative.js +610 -0
- package/dist/runtime/api-presets.js +85 -0
- package/dist/runtime/api-skybox.js +232 -0
- package/dist/runtime/api-sprites.js +100 -0
- package/dist/runtime/api-voxel.js +712 -0
- package/dist/runtime/api.js +201 -0
- package/dist/runtime/assets.js +27 -0
- package/dist/runtime/audio.js +114 -0
- package/dist/runtime/collision.js +47 -0
- package/dist/runtime/console.js +101 -0
- package/dist/runtime/editor.js +233 -0
- package/dist/runtime/font.js +233 -0
- package/dist/runtime/framebuffer.js +28 -0
- package/dist/runtime/fullscreen-button.js +185 -0
- package/dist/runtime/gpu-canvas2d.js +47 -0
- package/dist/runtime/gpu-threejs.js +643 -0
- package/dist/runtime/gpu-webgl2.js +310 -0
- package/dist/runtime/index.d.ts +682 -0
- package/dist/runtime/index.js +22 -0
- package/dist/runtime/input.js +225 -0
- package/dist/runtime/logger.js +60 -0
- package/dist/runtime/physics.js +101 -0
- package/dist/runtime/screens.js +213 -0
- package/dist/runtime/storage.js +38 -0
- package/dist/runtime/store.js +151 -0
- package/dist/runtime/textinput.js +68 -0
- package/dist/runtime/ui/buttons.js +124 -0
- package/dist/runtime/ui/panels.js +105 -0
- package/dist/runtime/ui/text.js +86 -0
- package/dist/runtime/ui/widgets.js +141 -0
- package/dist/runtime/ui.js +111 -0
- package/package.json +34 -32
- package/public/os9-shell/assets/index-3Hr_q5dj.js +483 -0
- package/public/os9-shell/assets/index-3Hr_q5dj.js.map +1 -0
- package/public/os9-shell/index.html +2 -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
|
+
}
|