nova64 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/bin/nova64.js +165 -0
- package/dist/assets/console-CY_kygm3.js +14 -0
- package/dist/assets/console-CY_kygm3.js.map +1 -0
- package/dist/assets/main-l0sNRNKZ.js.map +1 -0
- package/dist/assets/sky/studio/nx.png +0 -0
- package/dist/assets/sky/studio/ny.png +0 -0
- package/dist/assets/sky/studio/nz.png +0 -0
- package/dist/assets/sky/studio/px.png +0 -0
- package/dist/assets/sky/studio/py.png +0 -0
- package/dist/assets/sky/studio/pz.png +0 -0
- package/dist/assets/vanilla-Dcuy32gi.js +2 -0
- package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
- package/dist/console.html +899 -0
- package/dist/docs/BENCHMARK.md +77 -0
- package/dist/docs/CHEATSHEET.md +255 -0
- package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
- package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
- package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
- package/dist/docs/FREE_GLB_ASSETS.md +330 -0
- package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
- package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
- package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
- package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
- package/dist/docs/MIGRATION_GUIDE.md +553 -0
- package/dist/docs/NOVA64_3D_API.md +356 -0
- package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
- package/dist/docs/NOVA64_UI_API.md +503 -0
- package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
- package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
- package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
- package/dist/docs/api-3d.html +750 -0
- package/dist/docs/api-effects.html +385 -0
- package/dist/docs/api-improvements.md +121 -0
- package/dist/docs/api-skybox.html +407 -0
- package/dist/docs/api-sprites.html +321 -0
- package/dist/docs/api-voxel.html +337 -0
- package/dist/docs/api.html +543 -0
- package/dist/docs/assets.html +306 -0
- package/dist/docs/audio.html +340 -0
- package/dist/docs/blogs.html +286 -0
- package/dist/docs/collision.html +316 -0
- package/dist/docs/console.html +247 -0
- package/dist/docs/editor.html +297 -0
- package/dist/docs/font.html +247 -0
- package/dist/docs/framebuffer.html +247 -0
- package/dist/docs/fullscreen-button.html +297 -0
- package/dist/docs/gpu-systems.html +247 -0
- package/dist/docs/index.html +580 -0
- package/dist/docs/input.html +491 -0
- package/dist/docs/physics.html +311 -0
- package/dist/docs/screens.html +311 -0
- package/dist/docs/storage.html +311 -0
- package/dist/docs/textinput.html +332 -0
- package/dist/docs/ui.html +488 -0
- package/dist/examples/3d-advanced/code.js +695 -0
- package/dist/examples/adventure-comic-3d/code.js +342 -0
- package/dist/examples/audio-lab/code.js +150 -0
- package/dist/examples/boids-flocking/code.js +270 -0
- package/dist/examples/crystal-cathedral-3d/code.js +706 -0
- package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
- package/dist/examples/demoscene/README.md +192 -0
- package/dist/examples/demoscene/code.js +1081 -0
- package/dist/examples/demoscene/meta.json +21 -0
- package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
- package/dist/examples/f-zero-nova-3d/code.js +865 -0
- package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
- package/dist/examples/fps-demo-3d/code.js +744 -0
- package/dist/examples/game-of-life-3d/code.js +338 -0
- package/dist/examples/generative-art/code.js +632 -0
- package/dist/examples/hello-3d/code.js +325 -0
- package/dist/examples/hello-skybox/code.js +183 -0
- package/dist/examples/hello-world/code.js +19 -0
- package/dist/examples/input-showcase/code.js +109 -0
- package/dist/examples/instancing-demo/code.js +315 -0
- package/dist/examples/minecraft-demo/code.js +387 -0
- package/dist/examples/model-viewer-3d/code.js +114 -0
- package/dist/examples/mystical-realm-3d/code.js +1203 -0
- package/dist/examples/nature-explorer-3d/code.js +1318 -0
- package/dist/examples/particles-demo/code.js +522 -0
- package/dist/examples/pbr-showcase/code.js +140 -0
- package/dist/examples/physics-demo-3d/code.js +948 -0
- package/dist/examples/screen-demo/code.js +267 -0
- package/dist/examples/shooter-demo-3d/code.js +1286 -0
- package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
- package/dist/examples/space-combat-3d/README.md +135 -0
- package/dist/examples/space-combat-3d/code.js +1332 -0
- package/dist/examples/space-harrier-3d/code.js +923 -0
- package/dist/examples/star-fox-nova-3d/code.js +1116 -0
- package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
- package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
- package/dist/examples/storage-quest/code.js +209 -0
- package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
- package/dist/examples/strider-demo-3d/cache-test.html +132 -0
- package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
- package/dist/examples/strider-demo-3d/code-old.js +1537 -0
- package/dist/examples/strider-demo-3d/code.js +1462 -0
- package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
- package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
- package/dist/examples/super-plumber-64/README.md +128 -0
- package/dist/examples/super-plumber-64/code.js +1185 -0
- package/dist/examples/super-plumber-64/index.html +88 -0
- package/dist/examples/test-2d-overlay/code.js +32 -0
- package/dist/examples/test-font/code.js +51 -0
- package/dist/examples/test-minimal/code.js +21 -0
- package/dist/examples/ui-demo/code.js +306 -0
- package/dist/examples/wing-commander-space/README.md +180 -0
- package/dist/examples/wing-commander-space/code.js +1285 -0
- package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
- package/dist/examples/wizardry-3d/code.js +3928 -0
- package/dist/index.html +666 -0
- package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/dist/os9-shell/index.html +23 -0
- package/dist/os9-shell/nova-icon.svg +12 -0
- package/index.html +6 -1
- package/package.json +37 -32
- package/public/assets/sky/studio/nx.png +0 -0
- package/public/assets/sky/studio/ny.png +0 -0
- package/public/assets/sky/studio/nz.png +0 -0
- package/public/assets/sky/studio/px.png +0 -0
- package/public/assets/sky/studio/py.png +0 -0
- package/public/assets/sky/studio/pz.png +0 -0
- package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/public/os9-shell/index.html +10 -1
- package/runtime/api-2d.js +301 -21
- package/runtime/api-3d/pbr.js +45 -1
- package/runtime/api-3d.js +1 -0
- package/runtime/api-effects.js +90 -3
- package/runtime/api-gameutils.js +476 -0
- package/runtime/api-generative.js +610 -0
- package/runtime/api-skybox.js +54 -0
- package/runtime/api-voxel.js +139 -28
- package/runtime/gpu-threejs.js +13 -9
- package/runtime/ui.js +2 -2
- package/src/main.js +24 -1
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- 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
|
+
}
|
package/runtime/api-skybox.js
CHANGED
|
@@ -83,6 +83,59 @@ export function skyboxApi(gpu) {
|
|
|
83
83
|
gpu.scene.background = new THREE.Color(color);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// ── Image-based (cube map) skybox ────────────────────────────────────────────
|
|
87
|
+
/**
|
|
88
|
+
* Load a cube-map skybox from six image URLs.
|
|
89
|
+
* The cube map is also wired up as the scene environment map so PBR metallic
|
|
90
|
+
* surfaces automatically reflect the sky.
|
|
91
|
+
*
|
|
92
|
+
* @param {string[]} urls - Six face URLs in order: [+X, -X, +Y, -Y, +Z, -Z]
|
|
93
|
+
* i.e. [right, left, top, bottom, front, back]
|
|
94
|
+
* @returns {Promise<THREE.CubeTexture>} Resolves when all six faces are loaded.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* await createImageSkybox([
|
|
98
|
+
* '/assets/sky/px.jpg', '/assets/sky/nx.jpg',
|
|
99
|
+
* '/assets/sky/py.jpg', '/assets/sky/ny.jpg',
|
|
100
|
+
* '/assets/sky/pz.jpg', '/assets/sky/nz.jpg',
|
|
101
|
+
* ]);
|
|
102
|
+
*/
|
|
103
|
+
function createImageSkybox(urls) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
if (!Array.isArray(urls) || urls.length !== 6) {
|
|
106
|
+
reject(
|
|
107
|
+
new Error('createImageSkybox: requires exactly 6 face URLs [+X, -X, +Y, -Y, +Z, -Z]')
|
|
108
|
+
);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_clearSky();
|
|
113
|
+
|
|
114
|
+
const loader = new THREE.CubeTextureLoader();
|
|
115
|
+
loader.load(
|
|
116
|
+
urls,
|
|
117
|
+
cubeTexture => {
|
|
118
|
+
cubeTexture.colorSpace = THREE.SRGBColorSpace;
|
|
119
|
+
gpu.scene.background = cubeTexture;
|
|
120
|
+
|
|
121
|
+
// Process through PMREMGenerator so metallic/PBR surfaces reflect the sky
|
|
122
|
+
try {
|
|
123
|
+
const pmrem = new THREE.PMREMGenerator(gpu.renderer);
|
|
124
|
+
pmrem.compileCubemapShader();
|
|
125
|
+
gpu.scene.environment = pmrem.fromCubemap(cubeTexture).texture;
|
|
126
|
+
pmrem.dispose();
|
|
127
|
+
} catch (_) {
|
|
128
|
+
// PMREM is optional; the skybox still renders without env reflections
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
resolve(cubeTexture);
|
|
132
|
+
},
|
|
133
|
+
undefined,
|
|
134
|
+
err => reject(err)
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
86
139
|
// ── Shared gradient-sphere helper ────────────────────────────────────────────
|
|
87
140
|
function _gradientSphere(topColor, bottomColor) {
|
|
88
141
|
const geo = new THREE.SphereGeometry(800, 32, 32);
|
|
@@ -167,6 +220,7 @@ export function skyboxApi(gpu) {
|
|
|
167
220
|
createSpaceSkybox,
|
|
168
221
|
createGradientSkybox,
|
|
169
222
|
createSolidSkybox,
|
|
223
|
+
createImageSkybox,
|
|
170
224
|
animateSkybox,
|
|
171
225
|
setSkyboxSpeed,
|
|
172
226
|
enableSkyboxAutoAnimate,
|