@nexart/ui-renderer 0.7.0 → 0.8.0
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 +132 -323
- package/dist/capabilities.d.ts +5 -7
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +6 -8
- package/dist/compiler.d.ts +3 -3
- package/dist/compiler.js +4 -4
- package/dist/index.d.ts +33 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -30
- package/dist/preview/canvas-scaler.d.ts +49 -0
- package/dist/preview/canvas-scaler.d.ts.map +1 -0
- package/dist/preview/canvas-scaler.js +74 -0
- package/dist/preview/code-renderer.d.ts +11 -28
- package/dist/preview/code-renderer.d.ts.map +1 -1
- package/dist/preview/code-renderer.js +115 -660
- package/dist/preview/frame-budget.d.ts +43 -0
- package/dist/preview/frame-budget.d.ts.map +1 -0
- package/dist/preview/frame-budget.js +78 -0
- package/dist/preview/preview-engine.d.ts +42 -0
- package/dist/preview/preview-engine.d.ts.map +1 -0
- package/dist/preview/preview-engine.js +201 -0
- package/dist/preview/preview-runtime.d.ts +28 -0
- package/dist/preview/preview-runtime.d.ts.map +1 -0
- package/dist/preview/preview-runtime.js +512 -0
- package/dist/preview/preview-types.d.ts +116 -0
- package/dist/preview/preview-types.d.ts.map +1 -0
- package/dist/preview/preview-types.js +36 -0
- package/dist/preview/renderer.d.ts +3 -3
- package/dist/preview/renderer.js +3 -3
- package/dist/preview/unified-renderer.d.ts.map +1 -1
- package/dist/preview/unified-renderer.js +45 -22
- package/dist/system.d.ts +2 -2
- package/dist/system.d.ts.map +1 -1
- package/dist/system.js +8 -10
- package/dist/types.d.ts +9 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -5
- package/package.json +2 -2
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexart/ui-renderer - Preview Runtime
|
|
3
|
+
*
|
|
4
|
+
* Simplified p5-like runtime for preview rendering.
|
|
5
|
+
* Same API surface as Code Mode, but optimized for performance.
|
|
6
|
+
*
|
|
7
|
+
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
8
|
+
* ║ PREVIEW RUNTIME — SAME API, SIMPLIFIED INTERNALS ║
|
|
9
|
+
* ║ ║
|
|
10
|
+
* ║ This runtime: ║
|
|
11
|
+
* ║ - Uses same function names as Code Mode ║
|
|
12
|
+
* ║ - Is NOT deterministic (performance > fidelity) ║
|
|
13
|
+
* ║ - May have simplified implementations ║
|
|
14
|
+
* ║ - Pixel operations may be capped ║
|
|
15
|
+
* ║ ║
|
|
16
|
+
* ║ For canonical output: use @nexart/codemode-sdk ║
|
|
17
|
+
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
18
|
+
*/
|
|
19
|
+
function createSeededRNG(seed = 123456) {
|
|
20
|
+
let a = seed >>> 0;
|
|
21
|
+
return () => {
|
|
22
|
+
a += 0x6D2B79F5;
|
|
23
|
+
let t = Math.imul(a ^ (a >>> 15), a | 1);
|
|
24
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
25
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function createSimpleNoise(seed = 0) {
|
|
29
|
+
const rng = createSeededRNG(seed);
|
|
30
|
+
const perm = [];
|
|
31
|
+
for (let i = 0; i < 256; i++)
|
|
32
|
+
perm[i] = i;
|
|
33
|
+
for (let i = 255; i > 0; i--) {
|
|
34
|
+
const j = Math.floor(rng() * (i + 1));
|
|
35
|
+
[perm[i], perm[j]] = [perm[j], perm[i]];
|
|
36
|
+
}
|
|
37
|
+
for (let i = 0; i < 256; i++)
|
|
38
|
+
perm[256 + i] = perm[i];
|
|
39
|
+
const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
|
|
40
|
+
const lerp = (a, b, t) => a + t * (b - a);
|
|
41
|
+
const grad = (hash, x, y, z) => {
|
|
42
|
+
const h = hash & 15;
|
|
43
|
+
const u = h < 8 ? x : y;
|
|
44
|
+
const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
|
|
45
|
+
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
|
|
46
|
+
};
|
|
47
|
+
return (x, y = 0, z = 0) => {
|
|
48
|
+
const X = Math.floor(x) & 255;
|
|
49
|
+
const Y = Math.floor(y) & 255;
|
|
50
|
+
const Z = Math.floor(z) & 255;
|
|
51
|
+
x -= Math.floor(x);
|
|
52
|
+
y -= Math.floor(y);
|
|
53
|
+
z -= Math.floor(z);
|
|
54
|
+
const u = fade(x), v = fade(y), w = fade(z);
|
|
55
|
+
const A = perm[X] + Y, AA = perm[A] + Z, AB = perm[A + 1] + Z;
|
|
56
|
+
const B = perm[X + 1] + Y, BA = perm[B] + Z, BB = perm[B + 1] + Z;
|
|
57
|
+
return (lerp(lerp(lerp(grad(perm[AA], x, y, z), grad(perm[BA], x - 1, y, z), u), lerp(grad(perm[AB], x, y - 1, z), grad(perm[BB], x - 1, y - 1, z), u), v), lerp(lerp(grad(perm[AA + 1], x, y, z - 1), grad(perm[BA + 1], x - 1, y, z - 1), u), lerp(grad(perm[AB + 1], x, y - 1, z - 1), grad(perm[BB + 1], x - 1, y - 1, z - 1), u), v), w) + 1) / 2;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function normalizeVars(vars) {
|
|
61
|
+
if (!vars || !Array.isArray(vars)) {
|
|
62
|
+
return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
63
|
+
}
|
|
64
|
+
const result = [];
|
|
65
|
+
for (let i = 0; i < Math.min(vars.length, 10); i++) {
|
|
66
|
+
const v = vars[i];
|
|
67
|
+
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
68
|
+
result.push(Math.max(0, Math.min(100, v)));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
result.push(0);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
while (result.length < 10)
|
|
75
|
+
result.push(0);
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
export function createPreviewRuntime(canvas, width, height, seed = 12345, vars = []) {
|
|
79
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
80
|
+
let currentFill = 'rgba(255, 255, 255, 1)';
|
|
81
|
+
let currentStroke = 'rgba(0, 0, 0, 1)';
|
|
82
|
+
let strokeEnabled = true;
|
|
83
|
+
let fillEnabled = true;
|
|
84
|
+
let currentStrokeWeight = 1;
|
|
85
|
+
let colorMode = { mode: 'RGB', maxR: 255, maxG: 255, maxB: 255, maxA: 255 };
|
|
86
|
+
let shapeStarted = false;
|
|
87
|
+
let rng = createSeededRNG(seed);
|
|
88
|
+
let noiseFunc = createSimpleNoise(seed);
|
|
89
|
+
let noiseOctaves = 4;
|
|
90
|
+
let noiseFalloff = 0.5;
|
|
91
|
+
const normalizedVars = normalizeVars(vars);
|
|
92
|
+
const frozenVars = Object.freeze([...normalizedVars]);
|
|
93
|
+
const parseColor = (...args) => {
|
|
94
|
+
if (args.length === 0)
|
|
95
|
+
return 'rgba(0, 0, 0, 1)';
|
|
96
|
+
const { mode, maxR, maxG, maxB, maxA } = colorMode;
|
|
97
|
+
if (args.length === 1) {
|
|
98
|
+
const val = args[0];
|
|
99
|
+
if (typeof val === 'string')
|
|
100
|
+
return val;
|
|
101
|
+
if (mode === 'HSB')
|
|
102
|
+
return `hsla(${val}, 100%, 50%, 1)`;
|
|
103
|
+
const gray = Math.round((val / maxR) * 255);
|
|
104
|
+
return `rgba(${gray}, ${gray}, ${gray}, 1)`;
|
|
105
|
+
}
|
|
106
|
+
if (args.length === 2) {
|
|
107
|
+
const [gray, alpha] = args;
|
|
108
|
+
const g = Math.round((gray / maxR) * 255);
|
|
109
|
+
return `rgba(${g}, ${g}, ${g}, ${alpha / maxA})`;
|
|
110
|
+
}
|
|
111
|
+
if (args.length === 3) {
|
|
112
|
+
const [r, g, b] = args;
|
|
113
|
+
if (mode === 'HSB') {
|
|
114
|
+
return `hsla(${(r / maxR) * 360}, ${(g / maxG) * 100}%, ${(b / maxB) * 100}%, 1)`;
|
|
115
|
+
}
|
|
116
|
+
return `rgba(${Math.round((r / maxR) * 255)}, ${Math.round((g / maxG) * 255)}, ${Math.round((b / maxB) * 255)}, 1)`;
|
|
117
|
+
}
|
|
118
|
+
if (args.length === 4) {
|
|
119
|
+
const [r, g, b, a] = args;
|
|
120
|
+
if (mode === 'HSB') {
|
|
121
|
+
return `hsla(${(r / maxR) * 360}, ${(g / maxG) * 100}%, ${(b / maxB) * 100}%, ${a / maxA})`;
|
|
122
|
+
}
|
|
123
|
+
return `rgba(${Math.round((r / maxR) * 255)}, ${Math.round((g / maxG) * 255)}, ${Math.round((b / maxB) * 255)}, ${a / maxA})`;
|
|
124
|
+
}
|
|
125
|
+
return 'rgba(0, 0, 0, 1)';
|
|
126
|
+
};
|
|
127
|
+
const p = {
|
|
128
|
+
mode: 'preview',
|
|
129
|
+
width,
|
|
130
|
+
height,
|
|
131
|
+
frameCount: 0,
|
|
132
|
+
VAR: frozenVars,
|
|
133
|
+
PI: Math.PI,
|
|
134
|
+
TWO_PI: Math.PI * 2,
|
|
135
|
+
HALF_PI: Math.PI / 2,
|
|
136
|
+
QUARTER_PI: Math.PI / 4,
|
|
137
|
+
CORNER: 'corner',
|
|
138
|
+
CENTER: 'center',
|
|
139
|
+
CORNERS: 'corners',
|
|
140
|
+
RADIUS: 'radius',
|
|
141
|
+
ROUND: 'round',
|
|
142
|
+
SQUARE: 'square',
|
|
143
|
+
PROJECT: 'butt',
|
|
144
|
+
MITER: 'miter',
|
|
145
|
+
BEVEL: 'bevel',
|
|
146
|
+
CLOSE: 'close',
|
|
147
|
+
background: (...args) => {
|
|
148
|
+
ctx.fillStyle = parseColor(...args);
|
|
149
|
+
ctx.fillRect(0, 0, width, height);
|
|
150
|
+
},
|
|
151
|
+
fill: (...args) => {
|
|
152
|
+
currentFill = parseColor(...args);
|
|
153
|
+
fillEnabled = true;
|
|
154
|
+
},
|
|
155
|
+
noFill: () => { fillEnabled = false; },
|
|
156
|
+
stroke: (...args) => {
|
|
157
|
+
currentStroke = parseColor(...args);
|
|
158
|
+
strokeEnabled = true;
|
|
159
|
+
},
|
|
160
|
+
noStroke: () => { strokeEnabled = false; },
|
|
161
|
+
strokeWeight: (w) => {
|
|
162
|
+
currentStrokeWeight = w;
|
|
163
|
+
ctx.lineWidth = w;
|
|
164
|
+
},
|
|
165
|
+
strokeCap: (cap) => { ctx.lineCap = cap; },
|
|
166
|
+
strokeJoin: (join) => { ctx.lineJoin = join; },
|
|
167
|
+
colorMode: (mode, max1, max2, max3, maxA) => {
|
|
168
|
+
colorMode.mode = mode;
|
|
169
|
+
if (max1 !== undefined) {
|
|
170
|
+
colorMode.maxR = max1;
|
|
171
|
+
colorMode.maxG = max2 ?? max1;
|
|
172
|
+
colorMode.maxB = max3 ?? max1;
|
|
173
|
+
colorMode.maxA = maxA ?? max1;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
push: () => { ctx.save(); },
|
|
177
|
+
pop: () => { ctx.restore(); },
|
|
178
|
+
translate: (x, y) => { ctx.translate(x, y); },
|
|
179
|
+
rotate: (angle) => { ctx.rotate(angle); },
|
|
180
|
+
scale: (sx, sy) => { ctx.scale(sx, sy ?? sx); },
|
|
181
|
+
ellipse: (x, y, w, h) => {
|
|
182
|
+
const rw = w / 2, rh = (h ?? w) / 2;
|
|
183
|
+
ctx.beginPath();
|
|
184
|
+
ctx.ellipse(x, y, rw, rh, 0, 0, Math.PI * 2);
|
|
185
|
+
if (fillEnabled) {
|
|
186
|
+
ctx.fillStyle = currentFill;
|
|
187
|
+
ctx.fill();
|
|
188
|
+
}
|
|
189
|
+
if (strokeEnabled) {
|
|
190
|
+
ctx.strokeStyle = currentStroke;
|
|
191
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
192
|
+
ctx.stroke();
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
circle: (x, y, d) => { p.ellipse(x, y, d, d); },
|
|
196
|
+
rect: (x, y, w, h, r) => {
|
|
197
|
+
const ht = h ?? w;
|
|
198
|
+
ctx.beginPath();
|
|
199
|
+
if (r && r > 0)
|
|
200
|
+
ctx.roundRect(x, y, w, ht, r);
|
|
201
|
+
else
|
|
202
|
+
ctx.rect(x, y, w, ht);
|
|
203
|
+
if (fillEnabled) {
|
|
204
|
+
ctx.fillStyle = currentFill;
|
|
205
|
+
ctx.fill();
|
|
206
|
+
}
|
|
207
|
+
if (strokeEnabled) {
|
|
208
|
+
ctx.strokeStyle = currentStroke;
|
|
209
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
210
|
+
ctx.stroke();
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
square: (x, y, s, r) => { p.rect(x, y, s, s, r); },
|
|
214
|
+
line: (x1, y1, x2, y2) => {
|
|
215
|
+
ctx.beginPath();
|
|
216
|
+
ctx.moveTo(x1, y1);
|
|
217
|
+
ctx.lineTo(x2, y2);
|
|
218
|
+
if (strokeEnabled) {
|
|
219
|
+
ctx.strokeStyle = currentStroke;
|
|
220
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
221
|
+
ctx.stroke();
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
point: (x, y) => {
|
|
225
|
+
ctx.beginPath();
|
|
226
|
+
ctx.arc(x, y, currentStrokeWeight / 2, 0, Math.PI * 2);
|
|
227
|
+
ctx.fillStyle = currentStroke;
|
|
228
|
+
ctx.fill();
|
|
229
|
+
},
|
|
230
|
+
triangle: (x1, y1, x2, y2, x3, y3) => {
|
|
231
|
+
ctx.beginPath();
|
|
232
|
+
ctx.moveTo(x1, y1);
|
|
233
|
+
ctx.lineTo(x2, y2);
|
|
234
|
+
ctx.lineTo(x3, y3);
|
|
235
|
+
ctx.closePath();
|
|
236
|
+
if (fillEnabled) {
|
|
237
|
+
ctx.fillStyle = currentFill;
|
|
238
|
+
ctx.fill();
|
|
239
|
+
}
|
|
240
|
+
if (strokeEnabled) {
|
|
241
|
+
ctx.strokeStyle = currentStroke;
|
|
242
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
243
|
+
ctx.stroke();
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
quad: (x1, y1, x2, y2, x3, y3, x4, y4) => {
|
|
247
|
+
ctx.beginPath();
|
|
248
|
+
ctx.moveTo(x1, y1);
|
|
249
|
+
ctx.lineTo(x2, y2);
|
|
250
|
+
ctx.lineTo(x3, y3);
|
|
251
|
+
ctx.lineTo(x4, y4);
|
|
252
|
+
ctx.closePath();
|
|
253
|
+
if (fillEnabled) {
|
|
254
|
+
ctx.fillStyle = currentFill;
|
|
255
|
+
ctx.fill();
|
|
256
|
+
}
|
|
257
|
+
if (strokeEnabled) {
|
|
258
|
+
ctx.strokeStyle = currentStroke;
|
|
259
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
260
|
+
ctx.stroke();
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
arc: (x, y, w, h, start, stop, mode) => {
|
|
264
|
+
ctx.beginPath();
|
|
265
|
+
ctx.ellipse(x, y, w / 2, h / 2, 0, start, stop);
|
|
266
|
+
if (mode === 'close' || mode === 'chord')
|
|
267
|
+
ctx.closePath();
|
|
268
|
+
else if (mode === 'pie') {
|
|
269
|
+
ctx.lineTo(x, y);
|
|
270
|
+
ctx.closePath();
|
|
271
|
+
}
|
|
272
|
+
if (fillEnabled) {
|
|
273
|
+
ctx.fillStyle = currentFill;
|
|
274
|
+
ctx.fill();
|
|
275
|
+
}
|
|
276
|
+
if (strokeEnabled) {
|
|
277
|
+
ctx.strokeStyle = currentStroke;
|
|
278
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
279
|
+
ctx.stroke();
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
beginShape: () => { ctx.beginPath(); shapeStarted = true; },
|
|
283
|
+
vertex: (x, y) => {
|
|
284
|
+
if (!shapeStarted) {
|
|
285
|
+
ctx.beginPath();
|
|
286
|
+
ctx.moveTo(x, y);
|
|
287
|
+
shapeStarted = true;
|
|
288
|
+
}
|
|
289
|
+
else
|
|
290
|
+
ctx.lineTo(x, y);
|
|
291
|
+
},
|
|
292
|
+
curveVertex: (x, y) => { ctx.lineTo(x, y); },
|
|
293
|
+
bezierVertex: (x2, y2, x3, y3, x4, y4) => {
|
|
294
|
+
ctx.bezierCurveTo(x2, y2, x3, y3, x4, y4);
|
|
295
|
+
},
|
|
296
|
+
quadraticVertex: (cx, cy, x3, y3) => {
|
|
297
|
+
ctx.quadraticCurveTo(cx, cy, x3, y3);
|
|
298
|
+
},
|
|
299
|
+
endShape: (close) => {
|
|
300
|
+
if (close === 'close')
|
|
301
|
+
ctx.closePath();
|
|
302
|
+
if (fillEnabled) {
|
|
303
|
+
ctx.fillStyle = currentFill;
|
|
304
|
+
ctx.fill();
|
|
305
|
+
}
|
|
306
|
+
if (strokeEnabled) {
|
|
307
|
+
ctx.strokeStyle = currentStroke;
|
|
308
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
309
|
+
ctx.stroke();
|
|
310
|
+
}
|
|
311
|
+
shapeStarted = false;
|
|
312
|
+
},
|
|
313
|
+
bezier: (x1, y1, x2, y2, x3, y3, x4, y4) => {
|
|
314
|
+
ctx.beginPath();
|
|
315
|
+
ctx.moveTo(x1, y1);
|
|
316
|
+
ctx.bezierCurveTo(x2, y2, x3, y3, x4, y4);
|
|
317
|
+
if (strokeEnabled) {
|
|
318
|
+
ctx.strokeStyle = currentStroke;
|
|
319
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
320
|
+
ctx.stroke();
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
curve: (x1, y1, x2, y2, x3, y3, x4, y4) => {
|
|
324
|
+
ctx.beginPath();
|
|
325
|
+
ctx.moveTo(x2, y2);
|
|
326
|
+
ctx.bezierCurveTo(x2, y2, x3, y3, x3, y3);
|
|
327
|
+
if (strokeEnabled) {
|
|
328
|
+
ctx.strokeStyle = currentStroke;
|
|
329
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
330
|
+
ctx.stroke();
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
text: (str, x, y) => {
|
|
334
|
+
if (fillEnabled) {
|
|
335
|
+
ctx.fillStyle = currentFill;
|
|
336
|
+
ctx.fillText(String(str), x, y);
|
|
337
|
+
}
|
|
338
|
+
if (strokeEnabled) {
|
|
339
|
+
ctx.strokeStyle = currentStroke;
|
|
340
|
+
ctx.strokeText(String(str), x, y);
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
textSize: (size) => { ctx.font = `${size}px sans-serif`; },
|
|
344
|
+
textAlign: (h, v) => {
|
|
345
|
+
ctx.textAlign = h;
|
|
346
|
+
if (v)
|
|
347
|
+
ctx.textBaseline = v;
|
|
348
|
+
},
|
|
349
|
+
random: (min, max) => {
|
|
350
|
+
if (min === undefined)
|
|
351
|
+
return rng();
|
|
352
|
+
if (max === undefined)
|
|
353
|
+
return rng() * min;
|
|
354
|
+
return min + rng() * (max - min);
|
|
355
|
+
},
|
|
356
|
+
randomSeed: (s) => { rng = createSeededRNG(s); },
|
|
357
|
+
noise: (x, y, z) => {
|
|
358
|
+
let total = 0, freq = 1, amp = 1, maxVal = 0;
|
|
359
|
+
for (let i = 0; i < noiseOctaves; i++) {
|
|
360
|
+
total += noiseFunc(x * freq, (y ?? 0) * freq, (z ?? 0) * freq) * amp;
|
|
361
|
+
maxVal += amp;
|
|
362
|
+
amp *= noiseFalloff;
|
|
363
|
+
freq *= 2;
|
|
364
|
+
}
|
|
365
|
+
return total / maxVal;
|
|
366
|
+
},
|
|
367
|
+
noiseSeed: (s) => { noiseFunc = createSimpleNoise(s); },
|
|
368
|
+
noiseDetail: (octaves, falloff) => {
|
|
369
|
+
noiseOctaves = octaves;
|
|
370
|
+
if (falloff !== undefined)
|
|
371
|
+
noiseFalloff = falloff;
|
|
372
|
+
},
|
|
373
|
+
sin: Math.sin, cos: Math.cos, tan: Math.tan,
|
|
374
|
+
asin: Math.asin, acos: Math.acos, atan: Math.atan, atan2: Math.atan2,
|
|
375
|
+
abs: Math.abs, ceil: Math.ceil, floor: Math.floor, round: Math.round,
|
|
376
|
+
min: Math.min, max: Math.max, pow: Math.pow, sqrt: Math.sqrt,
|
|
377
|
+
exp: Math.exp, log: Math.log, sq: (n) => n * n,
|
|
378
|
+
int: Math.floor,
|
|
379
|
+
map: (v, s1, e1, s2, e2) => s2 + (e2 - s2) * ((v - s1) / (e1 - s1)),
|
|
380
|
+
constrain: (v, lo, hi) => Math.max(lo, Math.min(hi, v)),
|
|
381
|
+
lerp: (a, b, t) => a + (b - a) * t,
|
|
382
|
+
dist: (x1, y1, x2, y2) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2),
|
|
383
|
+
mag: (x, y) => Math.sqrt(x * x + y * y),
|
|
384
|
+
norm: (v, s, e) => (v - s) / (e - s),
|
|
385
|
+
radians: (d) => d * (Math.PI / 180),
|
|
386
|
+
degrees: (r) => r * (180 / Math.PI),
|
|
387
|
+
color: (...args) => parseColor(...args),
|
|
388
|
+
lerpColor: (c1, c2, amt) => c1,
|
|
389
|
+
red: (c) => { const m = c.match(/rgba?\((\d+)/); return m ? parseInt(m[1], 10) : 0; },
|
|
390
|
+
green: (c) => { const m = c.match(/rgba?\(\d+,\s*(\d+)/); return m ? parseInt(m[1], 10) : 0; },
|
|
391
|
+
blue: (c) => { const m = c.match(/rgba?\(\d+,\s*\d+,\s*(\d+)/); return m ? parseInt(m[1], 10) : 0; },
|
|
392
|
+
alpha: (c) => { const m = c.match(/rgba\(\d+,\s*\d+,\s*\d+,\s*([\d.]+)\)/); return m ? Math.round(parseFloat(m[1]) * 255) : 255; },
|
|
393
|
+
hue: () => 0, saturation: () => 0, brightness: () => 0,
|
|
394
|
+
blendMode: (mode) => { ctx.globalCompositeOperation = mode; },
|
|
395
|
+
clear: () => { ctx.clearRect(0, 0, width, height); },
|
|
396
|
+
print: console.log,
|
|
397
|
+
println: console.log,
|
|
398
|
+
loop: () => { },
|
|
399
|
+
noLoop: () => { },
|
|
400
|
+
fract: (x) => x - Math.floor(x),
|
|
401
|
+
sign: Math.sign,
|
|
402
|
+
vec: (x, y) => ({ x, y }),
|
|
403
|
+
vecAdd: (a, b) => ({ x: a.x + b.x, y: a.y + b.y }),
|
|
404
|
+
vecSub: (a, b) => ({ x: a.x - b.x, y: a.y - b.y }),
|
|
405
|
+
vecMult: (v, s) => ({ x: v.x * s, y: v.y * s }),
|
|
406
|
+
vecMag: (v) => Math.sqrt(v.x * v.x + v.y * v.y),
|
|
407
|
+
vecNorm: (v) => { const m = Math.sqrt(v.x * v.x + v.y * v.y) || 1; return { x: v.x / m, y: v.y / m }; },
|
|
408
|
+
vecDist: (a, b) => Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2),
|
|
409
|
+
polygon: (x, y, r, n) => {
|
|
410
|
+
ctx.beginPath();
|
|
411
|
+
for (let i = 0; i < n; i++) {
|
|
412
|
+
const angle = (Math.PI * 2 * i) / n - Math.PI / 2;
|
|
413
|
+
const px = x + r * Math.cos(angle);
|
|
414
|
+
const py = y + r * Math.sin(angle);
|
|
415
|
+
if (i === 0)
|
|
416
|
+
ctx.moveTo(px, py);
|
|
417
|
+
else
|
|
418
|
+
ctx.lineTo(px, py);
|
|
419
|
+
}
|
|
420
|
+
ctx.closePath();
|
|
421
|
+
if (fillEnabled) {
|
|
422
|
+
ctx.fillStyle = currentFill;
|
|
423
|
+
ctx.fill();
|
|
424
|
+
}
|
|
425
|
+
if (strokeEnabled) {
|
|
426
|
+
ctx.strokeStyle = currentStroke;
|
|
427
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
428
|
+
ctx.stroke();
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
star: (x, y, r1, r2, n) => {
|
|
432
|
+
ctx.beginPath();
|
|
433
|
+
for (let i = 0; i < n * 2; i++) {
|
|
434
|
+
const angle = (Math.PI * i) / n - Math.PI / 2;
|
|
435
|
+
const r = i % 2 === 0 ? r2 : r1;
|
|
436
|
+
const px = x + r * Math.cos(angle);
|
|
437
|
+
const py = y + r * Math.sin(angle);
|
|
438
|
+
if (i === 0)
|
|
439
|
+
ctx.moveTo(px, py);
|
|
440
|
+
else
|
|
441
|
+
ctx.lineTo(px, py);
|
|
442
|
+
}
|
|
443
|
+
ctx.closePath();
|
|
444
|
+
if (fillEnabled) {
|
|
445
|
+
ctx.fillStyle = currentFill;
|
|
446
|
+
ctx.fill();
|
|
447
|
+
}
|
|
448
|
+
if (strokeEnabled) {
|
|
449
|
+
ctx.strokeStyle = currentStroke;
|
|
450
|
+
ctx.lineWidth = currentStrokeWeight;
|
|
451
|
+
ctx.stroke();
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
easeIn: (t) => t * t,
|
|
455
|
+
easeOut: (t) => 1 - (1 - t) * (1 - t),
|
|
456
|
+
easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
|
|
457
|
+
easeCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
|
|
458
|
+
easeExpo: (t) => t === 0 ? 0 : t === 1 ? 1 : t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2,
|
|
459
|
+
fbm: (x, y, z, octaves = 4) => {
|
|
460
|
+
let val = 0, amp = 0.5, freq = 1;
|
|
461
|
+
for (let i = 0; i < octaves; i++) {
|
|
462
|
+
val += amp * noiseFunc(x * freq, (y ?? 0) * freq, (z ?? 0) * freq);
|
|
463
|
+
freq *= 2;
|
|
464
|
+
amp *= 0.5;
|
|
465
|
+
}
|
|
466
|
+
return val;
|
|
467
|
+
},
|
|
468
|
+
ridgedNoise: (x, y, z) => {
|
|
469
|
+
return 1 - Math.abs(noiseFunc(x, y ?? 0, z ?? 0) * 2 - 1);
|
|
470
|
+
},
|
|
471
|
+
curlNoise: (x, y, epsilon = 0.001) => {
|
|
472
|
+
const n1 = noiseFunc(x, y + epsilon);
|
|
473
|
+
const n2 = noiseFunc(x, y - epsilon);
|
|
474
|
+
const n3 = noiseFunc(x + epsilon, y);
|
|
475
|
+
const n4 = noiseFunc(x - epsilon, y);
|
|
476
|
+
return { x: (n1 - n2) / (2 * epsilon), y: -(n3 - n4) / (2 * epsilon) };
|
|
477
|
+
},
|
|
478
|
+
createGraphics: (w, h) => {
|
|
479
|
+
const offCanvas = document.createElement('canvas');
|
|
480
|
+
offCanvas.width = Math.min(w, 900);
|
|
481
|
+
offCanvas.height = Math.min(h, 900);
|
|
482
|
+
return createPreviewRuntime(offCanvas, offCanvas.width, offCanvas.height, seed, vars);
|
|
483
|
+
},
|
|
484
|
+
image: (img, x, y, w, h) => {
|
|
485
|
+
try {
|
|
486
|
+
if (img && img.width && img.height) {
|
|
487
|
+
const srcCanvas = document.createElement('canvas');
|
|
488
|
+
srcCanvas.width = img.width;
|
|
489
|
+
srcCanvas.height = img.height;
|
|
490
|
+
const srcCtx = srcCanvas.getContext('2d');
|
|
491
|
+
if (srcCtx && img.background) {
|
|
492
|
+
srcCtx.drawImage(srcCanvas, 0, 0);
|
|
493
|
+
}
|
|
494
|
+
ctx.drawImage(srcCanvas, x, y, w ?? img.width, h ?? img.height);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// Silently fail for preview
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
loadPixels: () => { },
|
|
502
|
+
updatePixels: () => { },
|
|
503
|
+
get: () => [0, 0, 0, 255],
|
|
504
|
+
set: () => { },
|
|
505
|
+
pixels: [],
|
|
506
|
+
totalFrames: 120,
|
|
507
|
+
t: 0,
|
|
508
|
+
time: 0,
|
|
509
|
+
tGlobal: 0,
|
|
510
|
+
};
|
|
511
|
+
return p;
|
|
512
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexart/ui-renderer - Preview Runtime Types
|
|
3
|
+
*
|
|
4
|
+
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
5
|
+
* ║ PREVIEW RUNTIME — NON-AUTHORITATIVE ║
|
|
6
|
+
* ║ ║
|
|
7
|
+
* ║ This renderer is a preview-only runtime. ║
|
|
8
|
+
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
9
|
+
* ║ ║
|
|
10
|
+
* ║ For minting, export, or validation: use @nexart/codemode-sdk ║
|
|
11
|
+
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Runtime profile - this renderer is preview-only.
|
|
15
|
+
* Canonical execution remains SDK-only.
|
|
16
|
+
*/
|
|
17
|
+
export type RuntimeProfile = 'preview';
|
|
18
|
+
/**
|
|
19
|
+
* Execution budget limits (MANDATORY).
|
|
20
|
+
* These prevent browser freezes and ensure UI responsiveness.
|
|
21
|
+
*/
|
|
22
|
+
export declare const PREVIEW_BUDGET: {
|
|
23
|
+
/** Maximum frames rendered before auto-termination */
|
|
24
|
+
readonly MAX_FRAMES: 30;
|
|
25
|
+
/** Maximum total execution time in milliseconds */
|
|
26
|
+
readonly MAX_TOTAL_TIME_MS: 500;
|
|
27
|
+
/** Target frame time (best effort, ~60fps) */
|
|
28
|
+
readonly TARGET_FRAME_TIME_MS: 16;
|
|
29
|
+
/** Frame stride for performance (render every Nth frame) */
|
|
30
|
+
readonly FRAME_STRIDE: 3;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Canvas resolution limits.
|
|
34
|
+
* Preview renderer does not render at full mint resolution.
|
|
35
|
+
*/
|
|
36
|
+
export declare const CANVAS_LIMITS: {
|
|
37
|
+
/** Maximum dimension for preview canvas */
|
|
38
|
+
readonly MAX_DIMENSION: 900;
|
|
39
|
+
/** Minimum dimension */
|
|
40
|
+
readonly MIN_DIMENSION: 100;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Preview mode options
|
|
44
|
+
*/
|
|
45
|
+
export type PreviewMode = 'static' | 'loop';
|
|
46
|
+
/**
|
|
47
|
+
* Preview engine configuration
|
|
48
|
+
*/
|
|
49
|
+
export interface PreviewEngineConfig {
|
|
50
|
+
/** Canvas element to render to */
|
|
51
|
+
canvas: HTMLCanvasElement;
|
|
52
|
+
/** Source code to execute */
|
|
53
|
+
source: string;
|
|
54
|
+
/** Preview mode */
|
|
55
|
+
mode: PreviewMode;
|
|
56
|
+
/** Original width (will be scaled down) */
|
|
57
|
+
width: number;
|
|
58
|
+
/** Original height (will be scaled down) */
|
|
59
|
+
height: number;
|
|
60
|
+
/** Random seed for deterministic preview */
|
|
61
|
+
seed?: number;
|
|
62
|
+
/** VAR array (0-10 elements, 0-100 range) */
|
|
63
|
+
vars?: number[];
|
|
64
|
+
/** Total frames for loop mode (for t calculation) */
|
|
65
|
+
totalFrames?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Preview render result
|
|
69
|
+
*/
|
|
70
|
+
export interface PreviewRenderResult {
|
|
71
|
+
/** Whether rendering completed successfully */
|
|
72
|
+
success: boolean;
|
|
73
|
+
/** Frames rendered */
|
|
74
|
+
framesRendered: number;
|
|
75
|
+
/** Total execution time in ms */
|
|
76
|
+
executionTimeMs: number;
|
|
77
|
+
/** Whether execution was terminated early due to limits */
|
|
78
|
+
terminatedEarly: boolean;
|
|
79
|
+
/** Termination reason if applicable */
|
|
80
|
+
terminationReason?: 'frame_limit' | 'time_limit' | 'user_stop' | 'error';
|
|
81
|
+
/** Error message if any (non-throwing) */
|
|
82
|
+
errorMessage?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Preview renderer interface
|
|
86
|
+
*/
|
|
87
|
+
export interface PreviewRenderer {
|
|
88
|
+
/** Render a single frame (static mode) */
|
|
89
|
+
renderStatic: () => PreviewRenderResult;
|
|
90
|
+
/** Start loop rendering */
|
|
91
|
+
startLoop: () => void;
|
|
92
|
+
/** Stop loop rendering */
|
|
93
|
+
stopLoop: () => void;
|
|
94
|
+
/** Destroy renderer and clean up */
|
|
95
|
+
destroy: () => void;
|
|
96
|
+
/** Check if currently rendering */
|
|
97
|
+
isRendering: () => boolean;
|
|
98
|
+
/** This renderer is NOT canonical */
|
|
99
|
+
readonly isCanonical: false;
|
|
100
|
+
/** This renderer is NOT archival */
|
|
101
|
+
readonly isArchival: false;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Frame budget state for tracking execution limits
|
|
105
|
+
*/
|
|
106
|
+
export interface FrameBudgetState {
|
|
107
|
+
/** Frames rendered so far */
|
|
108
|
+
framesRendered: number;
|
|
109
|
+
/** Start time of execution */
|
|
110
|
+
startTimeMs: number;
|
|
111
|
+
/** Whether budget is exhausted */
|
|
112
|
+
exhausted: boolean;
|
|
113
|
+
/** Reason for exhaustion */
|
|
114
|
+
exhaustionReason?: 'frame_limit' | 'time_limit';
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=preview-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-types.d.ts","sourceRoot":"","sources":["../../src/preview/preview-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,cAAc;IACzB,sDAAsD;;IAEtD,mDAAmD;;IAEnD,8CAA8C;;IAE9C,4DAA4D;;CAEpD,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,aAAa;IACxB,2CAA2C;;IAE3C,wBAAwB;;CAEhB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,sBAAsB;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,eAAe,EAAE,OAAO,CAAC;IACzB,uCAAuC;IACvC,iBAAiB,CAAC,EAAE,aAAa,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;IACzE,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,YAAY,EAAE,MAAM,mBAAmB,CAAC;IACxC,2BAA2B;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,oCAAoC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,mCAAmC;IACnC,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,qCAAqC;IACrC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,gBAAgB,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;CACjD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexart/ui-renderer - Preview Runtime Types
|
|
3
|
+
*
|
|
4
|
+
* ╔══════════════════════════════════════════════════════════════════════════╗
|
|
5
|
+
* ║ PREVIEW RUNTIME — NON-AUTHORITATIVE ║
|
|
6
|
+
* ║ ║
|
|
7
|
+
* ║ This renderer is a preview-only runtime. ║
|
|
8
|
+
* ║ It does not guarantee determinism or protocol compliance. ║
|
|
9
|
+
* ║ ║
|
|
10
|
+
* ║ For minting, export, or validation: use @nexart/codemode-sdk ║
|
|
11
|
+
* ╚══════════════════════════════════════════════════════════════════════════╝
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Execution budget limits (MANDATORY).
|
|
15
|
+
* These prevent browser freezes and ensure UI responsiveness.
|
|
16
|
+
*/
|
|
17
|
+
export const PREVIEW_BUDGET = {
|
|
18
|
+
/** Maximum frames rendered before auto-termination */
|
|
19
|
+
MAX_FRAMES: 30,
|
|
20
|
+
/** Maximum total execution time in milliseconds */
|
|
21
|
+
MAX_TOTAL_TIME_MS: 500,
|
|
22
|
+
/** Target frame time (best effort, ~60fps) */
|
|
23
|
+
TARGET_FRAME_TIME_MS: 16,
|
|
24
|
+
/** Frame stride for performance (render every Nth frame) */
|
|
25
|
+
FRAME_STRIDE: 3,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Canvas resolution limits.
|
|
29
|
+
* Preview renderer does not render at full mint resolution.
|
|
30
|
+
*/
|
|
31
|
+
export const CANVAS_LIMITS = {
|
|
32
|
+
/** Maximum dimension for preview canvas */
|
|
33
|
+
MAX_DIMENSION: 900,
|
|
34
|
+
/** Minimum dimension */
|
|
35
|
+
MIN_DIMENSION: 100,
|
|
36
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nexart/ui-renderer v0.
|
|
2
|
+
* @nexart/ui-renderer v0.8.0 - Preview Renderer
|
|
3
3
|
*
|
|
4
4
|
* Renders visual approximations of NexArt systems.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Lightweight preview runtime — NOT canonical, NOT archival.
|
|
6
|
+
* For canonical output: use @nexart/codemode-sdk
|
|
7
7
|
*/
|
|
8
8
|
import type { NexArtSystem, PreviewOptions } from '../types';
|
|
9
9
|
export interface PreviewRenderer {
|