@viji-dev/core 0.3.21 → 0.3.22
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 +1439 -1439
- package/dist/artist-dts-p5.js +1 -1
- package/dist/artist-dts.js +1 -1
- package/dist/artist-global.d.ts +59 -58
- package/dist/artist-js-ambient.d.ts +37 -37
- package/dist/artist-jsdoc.d.ts +32 -32
- package/dist/assets/{viji.worker-bm-hvzXt.js → viji.worker-DTQvTudb.js} +26052 -25975
- package/dist/assets/viji.worker-DTQvTudb.js.map +1 -0
- package/dist/docs-api.d.ts +52 -0
- package/dist/docs-api.js +1195 -0
- package/dist/{essentia-wasm.web-C1URJxCY.js → essentia-wasm.web-0nilrUD3.js} +5696 -5696
- package/dist/{essentia-wasm.web-C1URJxCY.js.map → essentia-wasm.web-0nilrUD3.js.map} +1 -1
- package/dist/essentia.js-core.es-DnrJE0uR.js +3174 -3174
- package/dist/{index-trkn0FNW.js → index-Bu1euCdl.js} +16280 -16280
- package/dist/index-Bu1euCdl.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -8
- package/dist/shader-uniforms.js +1172 -1167
- package/package.json +91 -86
- package/dist/assets/cv-tasks.worker.js +0 -878
- package/dist/assets/viji.worker-bm-hvzXt.js.map +0 -1
- package/dist/assets/vision_bundle.js +0 -2
- package/dist/assets/wasm/essentia-wasm.web.wasm +0 -0
- package/dist/assets/wasm/vision_wasm_internal.js +0 -20
- package/dist/assets/wasm/vision_wasm_internal.wasm +0 -0
- package/dist/assets/wasm/vision_wasm_nosimd_internal.js +0 -20
- package/dist/assets/wasm/vision_wasm_nosimd_internal.wasm +0 -0
- package/dist/index-trkn0FNW.js.map +0 -1
package/dist/docs-api.js
ADDED
|
@@ -0,0 +1,1195 @@
|
|
|
1
|
+
export const docsApi = {
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"coreVersion": "0.3.21",
|
|
4
|
+
"generatedAt": "2026-03-15T16:09:18.953Z",
|
|
5
|
+
"navigation": [
|
|
6
|
+
{
|
|
7
|
+
"id": "getting-started",
|
|
8
|
+
"title": "Getting Started",
|
|
9
|
+
"icon": "play",
|
|
10
|
+
"pages": [
|
|
11
|
+
{
|
|
12
|
+
"id": "overview",
|
|
13
|
+
"title": "Overview",
|
|
14
|
+
"path": "getting-started/overview"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "renderers-overview",
|
|
18
|
+
"title": "Renderers Overview",
|
|
19
|
+
"path": "getting-started/renderers-overview"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "best-practices",
|
|
23
|
+
"title": "Best Practices",
|
|
24
|
+
"path": "getting-started/best-practices"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "common-mistakes",
|
|
28
|
+
"title": "Common Mistakes",
|
|
29
|
+
"path": "getting-started/common-mistakes"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "ai-prompts",
|
|
35
|
+
"title": "AI Scene Development",
|
|
36
|
+
"icon": "sparkles",
|
|
37
|
+
"pages": [
|
|
38
|
+
{
|
|
39
|
+
"id": "ai-create-scene",
|
|
40
|
+
"title": "Create Your First Scene",
|
|
41
|
+
"path": "ai-prompts/create-first-scene"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "ai-prompt-native",
|
|
45
|
+
"title": "Prompt: Native Scenes",
|
|
46
|
+
"path": "ai-prompts/native-prompt"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "ai-prompt-p5",
|
|
50
|
+
"title": "Prompt: P5 Scenes",
|
|
51
|
+
"path": "ai-prompts/p5-prompt"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "ai-prompt-shader",
|
|
55
|
+
"title": "Prompt: Shader Scenes",
|
|
56
|
+
"path": "ai-prompts/shader-prompt"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "ai-prompting-tips",
|
|
60
|
+
"title": "Prompting Tips",
|
|
61
|
+
"path": "ai-prompts/prompting-tips"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "ai-convert-p5",
|
|
65
|
+
"title": "Convert: P5 Sketches",
|
|
66
|
+
"path": "ai-prompts/convert-p5"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "ai-convert-shadertoy",
|
|
70
|
+
"title": "Convert: Shadertoy",
|
|
71
|
+
"path": "ai-prompts/convert-shadertoy"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "ai-convert-threejs",
|
|
75
|
+
"title": "Convert: Three.js",
|
|
76
|
+
"path": "ai-prompts/convert-threejs"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "native",
|
|
82
|
+
"title": "Native Renderer",
|
|
83
|
+
"icon": "code",
|
|
84
|
+
"pages": [
|
|
85
|
+
{
|
|
86
|
+
"id": "native-quickstart",
|
|
87
|
+
"title": "Quick Start",
|
|
88
|
+
"path": "native/quickstart"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "native-api-reference",
|
|
92
|
+
"title": "API Reference",
|
|
93
|
+
"path": "native/api-reference"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "native-canvas",
|
|
97
|
+
"title": "Canvas & Context",
|
|
98
|
+
"path": "native/canvas-context"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "native-timing",
|
|
102
|
+
"title": "Timing",
|
|
103
|
+
"path": "native/timing"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "native-external-libs",
|
|
107
|
+
"title": "External Libraries",
|
|
108
|
+
"path": "native/external-libraries"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "native-parameters",
|
|
112
|
+
"title": "Parameters",
|
|
113
|
+
"path": "native/parameters",
|
|
114
|
+
"children": [
|
|
115
|
+
{
|
|
116
|
+
"id": "native-param-slider",
|
|
117
|
+
"title": "Slider",
|
|
118
|
+
"path": "native/parameters/slider"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "native-param-color",
|
|
122
|
+
"title": "Color",
|
|
123
|
+
"path": "native/parameters/color"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "native-param-toggle",
|
|
127
|
+
"title": "Toggle",
|
|
128
|
+
"path": "native/parameters/toggle"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "native-param-select",
|
|
132
|
+
"title": "Select",
|
|
133
|
+
"path": "native/parameters/select"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "native-param-number",
|
|
137
|
+
"title": "Number",
|
|
138
|
+
"path": "native/parameters/number"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"id": "native-param-text",
|
|
142
|
+
"title": "Text",
|
|
143
|
+
"path": "native/parameters/text"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"id": "native-param-image",
|
|
147
|
+
"title": "Image",
|
|
148
|
+
"path": "native/parameters/image"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "native-param-grouping",
|
|
152
|
+
"title": "Grouping",
|
|
153
|
+
"path": "native/parameters/grouping"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"id": "native-param-categories",
|
|
157
|
+
"title": "Categories",
|
|
158
|
+
"path": "native/parameters/categories"
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"id": "native-audio",
|
|
164
|
+
"title": "Audio",
|
|
165
|
+
"path": "native/audio",
|
|
166
|
+
"children": [
|
|
167
|
+
{
|
|
168
|
+
"id": "native-audio-is-connected",
|
|
169
|
+
"title": "isConnected",
|
|
170
|
+
"path": "native/audio/is-connected"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"id": "native-audio-volume",
|
|
174
|
+
"title": "Volume",
|
|
175
|
+
"path": "native/audio/volume"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"id": "native-audio-bands",
|
|
179
|
+
"title": "Frequency Bands",
|
|
180
|
+
"path": "native/audio/bands"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "native-audio-beat",
|
|
184
|
+
"title": "Beat Detection",
|
|
185
|
+
"path": "native/audio/beat"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "native-audio-spectral",
|
|
189
|
+
"title": "Spectral Analysis",
|
|
190
|
+
"path": "native/audio/spectral"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"id": "native-audio-frequency-data",
|
|
194
|
+
"title": "Frequency Data",
|
|
195
|
+
"path": "native/audio/frequency-data"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"id": "native-audio-waveform",
|
|
199
|
+
"title": "Waveform",
|
|
200
|
+
"path": "native/audio/waveform"
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"id": "native-video",
|
|
206
|
+
"title": "Video & CV",
|
|
207
|
+
"path": "native/video",
|
|
208
|
+
"children": [
|
|
209
|
+
{
|
|
210
|
+
"id": "native-video-is-connected",
|
|
211
|
+
"title": "isConnected",
|
|
212
|
+
"path": "native/video/is-connected"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"id": "native-video-basics",
|
|
216
|
+
"title": "Video Basics",
|
|
217
|
+
"path": "native/video/basics"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"id": "native-cv-face",
|
|
221
|
+
"title": "Face Detection",
|
|
222
|
+
"path": "native/video/face-detection"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"id": "native-cv-face-mesh",
|
|
226
|
+
"title": "Face Mesh",
|
|
227
|
+
"path": "native/video/face-mesh"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"id": "native-cv-emotion",
|
|
231
|
+
"title": "Emotion Detection",
|
|
232
|
+
"path": "native/video/emotion-detection"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"id": "native-cv-hands",
|
|
236
|
+
"title": "Hand Tracking",
|
|
237
|
+
"path": "native/video/hand-tracking"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"id": "native-cv-pose",
|
|
241
|
+
"title": "Pose Detection",
|
|
242
|
+
"path": "native/video/pose-detection"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"id": "native-cv-segmentation",
|
|
246
|
+
"title": "Body Segmentation",
|
|
247
|
+
"path": "native/video/body-segmentation"
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"id": "native-mouse",
|
|
253
|
+
"title": "Mouse",
|
|
254
|
+
"path": "native/mouse"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"id": "native-keyboard",
|
|
258
|
+
"title": "Keyboard",
|
|
259
|
+
"path": "native/keyboard"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"id": "native-touch",
|
|
263
|
+
"title": "Touch & Gestures",
|
|
264
|
+
"path": "native/touch"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"id": "native-sensors",
|
|
268
|
+
"title": "Device Sensors",
|
|
269
|
+
"path": "native/sensors"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"id": "native-external-devices",
|
|
273
|
+
"title": "External Devices",
|
|
274
|
+
"path": "native/external-devices"
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"id": "p5",
|
|
280
|
+
"title": "P5.js Renderer",
|
|
281
|
+
"icon": "brush",
|
|
282
|
+
"pages": [
|
|
283
|
+
{
|
|
284
|
+
"id": "p5-quickstart",
|
|
285
|
+
"title": "Quick Start",
|
|
286
|
+
"path": "p5/quickstart"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"id": "p5-api-reference",
|
|
290
|
+
"title": "API Reference",
|
|
291
|
+
"path": "p5/api-reference"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"id": "p5-scene-structure",
|
|
295
|
+
"title": "Scene Structure",
|
|
296
|
+
"path": "p5/scene-structure"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"id": "p5-drawing",
|
|
300
|
+
"title": "Drawing with P5",
|
|
301
|
+
"path": "p5/drawing"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"id": "p5-converting",
|
|
305
|
+
"title": "Converting P5 Sketches",
|
|
306
|
+
"path": "p5/converting-sketches"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"id": "p5-timing",
|
|
310
|
+
"title": "Timing",
|
|
311
|
+
"path": "p5/timing"
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"id": "p5-parameters",
|
|
315
|
+
"title": "Parameters",
|
|
316
|
+
"path": "p5/parameters",
|
|
317
|
+
"children": [
|
|
318
|
+
{
|
|
319
|
+
"id": "p5-param-slider",
|
|
320
|
+
"title": "Slider",
|
|
321
|
+
"path": "p5/parameters/slider"
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"id": "p5-param-color",
|
|
325
|
+
"title": "Color",
|
|
326
|
+
"path": "p5/parameters/color"
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"id": "p5-param-toggle",
|
|
330
|
+
"title": "Toggle",
|
|
331
|
+
"path": "p5/parameters/toggle"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"id": "p5-param-select",
|
|
335
|
+
"title": "Select",
|
|
336
|
+
"path": "p5/parameters/select"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"id": "p5-param-number",
|
|
340
|
+
"title": "Number",
|
|
341
|
+
"path": "p5/parameters/number"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"id": "p5-param-text",
|
|
345
|
+
"title": "Text",
|
|
346
|
+
"path": "p5/parameters/text"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"id": "p5-param-image",
|
|
350
|
+
"title": "Image",
|
|
351
|
+
"path": "p5/parameters/image"
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"id": "p5-param-grouping",
|
|
355
|
+
"title": "Grouping",
|
|
356
|
+
"path": "p5/parameters/grouping"
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"id": "p5-param-categories",
|
|
360
|
+
"title": "Categories",
|
|
361
|
+
"path": "p5/parameters/categories"
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"id": "p5-audio",
|
|
367
|
+
"title": "Audio",
|
|
368
|
+
"path": "p5/audio",
|
|
369
|
+
"children": [
|
|
370
|
+
{
|
|
371
|
+
"id": "p5-audio-is-connected",
|
|
372
|
+
"title": "isConnected",
|
|
373
|
+
"path": "p5/audio/is-connected"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"id": "p5-audio-volume",
|
|
377
|
+
"title": "Volume",
|
|
378
|
+
"path": "p5/audio/volume"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"id": "p5-audio-bands",
|
|
382
|
+
"title": "Frequency Bands",
|
|
383
|
+
"path": "p5/audio/bands"
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"id": "p5-audio-beat",
|
|
387
|
+
"title": "Beat Detection",
|
|
388
|
+
"path": "p5/audio/beat"
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"id": "p5-audio-spectral",
|
|
392
|
+
"title": "Spectral Analysis",
|
|
393
|
+
"path": "p5/audio/spectral"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"id": "p5-audio-frequency-data",
|
|
397
|
+
"title": "Frequency Data",
|
|
398
|
+
"path": "p5/audio/frequency-data"
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
"id": "p5-audio-waveform",
|
|
402
|
+
"title": "Waveform",
|
|
403
|
+
"path": "p5/audio/waveform"
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"id": "p5-video",
|
|
409
|
+
"title": "Video & CV",
|
|
410
|
+
"path": "p5/video",
|
|
411
|
+
"children": [
|
|
412
|
+
{
|
|
413
|
+
"id": "p5-video-is-connected",
|
|
414
|
+
"title": "isConnected",
|
|
415
|
+
"path": "p5/video/is-connected"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"id": "p5-video-basics",
|
|
419
|
+
"title": "Video Basics",
|
|
420
|
+
"path": "p5/video/basics"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"id": "p5-cv-face",
|
|
424
|
+
"title": "Face Detection",
|
|
425
|
+
"path": "p5/video/face-detection"
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
"id": "p5-cv-face-mesh",
|
|
429
|
+
"title": "Face Mesh",
|
|
430
|
+
"path": "p5/video/face-mesh"
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
"id": "p5-cv-emotion",
|
|
434
|
+
"title": "Emotion Detection",
|
|
435
|
+
"path": "p5/video/emotion-detection"
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"id": "p5-cv-hands",
|
|
439
|
+
"title": "Hand Tracking",
|
|
440
|
+
"path": "p5/video/hand-tracking"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"id": "p5-cv-pose",
|
|
444
|
+
"title": "Pose Detection",
|
|
445
|
+
"path": "p5/video/pose-detection"
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"id": "p5-cv-segmentation",
|
|
449
|
+
"title": "Body Segmentation",
|
|
450
|
+
"path": "p5/video/body-segmentation"
|
|
451
|
+
}
|
|
452
|
+
]
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"id": "p5-mouse",
|
|
456
|
+
"title": "Mouse",
|
|
457
|
+
"path": "p5/mouse"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"id": "p5-keyboard",
|
|
461
|
+
"title": "Keyboard",
|
|
462
|
+
"path": "p5/keyboard"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"id": "p5-touch",
|
|
466
|
+
"title": "Touch & Gestures",
|
|
467
|
+
"path": "p5/touch"
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
"id": "p5-sensors",
|
|
471
|
+
"title": "Device Sensors",
|
|
472
|
+
"path": "p5/sensors"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
"id": "p5-external-devices",
|
|
476
|
+
"title": "External Devices",
|
|
477
|
+
"path": "p5/external-devices"
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"id": "shader",
|
|
483
|
+
"title": "Shader Renderer",
|
|
484
|
+
"icon": "zap",
|
|
485
|
+
"pages": [
|
|
486
|
+
{
|
|
487
|
+
"id": "shader-quickstart",
|
|
488
|
+
"title": "Quick Start",
|
|
489
|
+
"path": "shader/quickstart"
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"id": "shader-api-reference",
|
|
493
|
+
"title": "API Reference",
|
|
494
|
+
"path": "shader/api-reference"
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
"id": "shader-basics",
|
|
498
|
+
"title": "Shader Basics",
|
|
499
|
+
"path": "shader/basics"
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
"id": "shader-parameters",
|
|
503
|
+
"title": "Parameters",
|
|
504
|
+
"path": "shader/parameters",
|
|
505
|
+
"children": [
|
|
506
|
+
{
|
|
507
|
+
"id": "shader-param-slider",
|
|
508
|
+
"title": "Slider",
|
|
509
|
+
"path": "shader/parameters/slider"
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
"id": "shader-param-color",
|
|
513
|
+
"title": "Color",
|
|
514
|
+
"path": "shader/parameters/color"
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
"id": "shader-param-toggle",
|
|
518
|
+
"title": "Toggle",
|
|
519
|
+
"path": "shader/parameters/toggle"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
"id": "shader-param-select",
|
|
523
|
+
"title": "Select",
|
|
524
|
+
"path": "shader/parameters/select"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"id": "shader-param-number",
|
|
528
|
+
"title": "Number",
|
|
529
|
+
"path": "shader/parameters/number"
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
"id": "shader-param-image",
|
|
533
|
+
"title": "Image",
|
|
534
|
+
"path": "shader/parameters/image"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
"id": "shader-param-accumulator",
|
|
538
|
+
"title": "Accumulator",
|
|
539
|
+
"path": "shader/parameters/accumulator"
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
"id": "shader-param-grouping",
|
|
543
|
+
"title": "Grouping",
|
|
544
|
+
"path": "shader/parameters/grouping"
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"id": "shader-param-categories",
|
|
548
|
+
"title": "Categories",
|
|
549
|
+
"path": "shader/parameters/categories"
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
"id": "shader-audio",
|
|
555
|
+
"title": "Audio Uniforms",
|
|
556
|
+
"path": "shader/audio",
|
|
557
|
+
"children": [
|
|
558
|
+
{
|
|
559
|
+
"id": "shader-audio-volume",
|
|
560
|
+
"title": "Volume",
|
|
561
|
+
"path": "shader/audio/volume"
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"id": "shader-audio-bands",
|
|
565
|
+
"title": "Frequency Bands",
|
|
566
|
+
"path": "shader/audio/bands"
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
"id": "shader-audio-beat",
|
|
570
|
+
"title": "Beat Detection",
|
|
571
|
+
"path": "shader/audio/beat"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
"id": "shader-audio-spectral",
|
|
575
|
+
"title": "Spectral Analysis",
|
|
576
|
+
"path": "shader/audio/spectral"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"id": "shader-audio-fft",
|
|
580
|
+
"title": "FFT Texture",
|
|
581
|
+
"path": "shader/audio/fft"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"id": "shader-audio-waveform",
|
|
585
|
+
"title": "Waveform Texture",
|
|
586
|
+
"path": "shader/audio/waveform"
|
|
587
|
+
}
|
|
588
|
+
]
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
"id": "shader-video",
|
|
592
|
+
"title": "Video & CV Uniforms",
|
|
593
|
+
"path": "shader/video",
|
|
594
|
+
"children": [
|
|
595
|
+
{
|
|
596
|
+
"id": "shader-video-basics",
|
|
597
|
+
"title": "Video Basics",
|
|
598
|
+
"path": "shader/video/basics"
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
"id": "shader-cv-face",
|
|
602
|
+
"title": "Face Detection",
|
|
603
|
+
"path": "shader/video/face-detection"
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
"id": "shader-cv-face-mesh",
|
|
607
|
+
"title": "Face Mesh Uniforms",
|
|
608
|
+
"path": "shader/video/face-mesh"
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
"id": "shader-cv-emotion",
|
|
612
|
+
"title": "Emotion Uniforms",
|
|
613
|
+
"path": "shader/video/emotion-detection"
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
"id": "shader-cv-hands",
|
|
617
|
+
"title": "Hand Tracking",
|
|
618
|
+
"path": "shader/video/hand-tracking"
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
"id": "shader-cv-pose",
|
|
622
|
+
"title": "Pose Detection",
|
|
623
|
+
"path": "shader/video/pose-detection"
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
"id": "shader-cv-segmentation",
|
|
627
|
+
"title": "Body Segmentation",
|
|
628
|
+
"path": "shader/video/body-segmentation"
|
|
629
|
+
}
|
|
630
|
+
]
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
"id": "shader-mouse",
|
|
634
|
+
"title": "Mouse Uniforms",
|
|
635
|
+
"path": "shader/mouse"
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
"id": "shader-keyboard",
|
|
639
|
+
"title": "Keyboard Uniforms",
|
|
640
|
+
"path": "shader/keyboard"
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
"id": "shader-touch",
|
|
644
|
+
"title": "Touch Uniforms",
|
|
645
|
+
"path": "shader/touch"
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
"id": "shader-sensors",
|
|
649
|
+
"title": "Sensor Uniforms",
|
|
650
|
+
"path": "shader/sensors"
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
"id": "shader-external-devices",
|
|
654
|
+
"title": "External Device Uniforms",
|
|
655
|
+
"path": "shader/external-devices"
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
"id": "shader-backbuffer",
|
|
659
|
+
"title": "Backbuffer & Feedback",
|
|
660
|
+
"path": "shader/backbuffer"
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
"id": "shader-shadertoy",
|
|
664
|
+
"title": "Shadertoy Compatibility",
|
|
665
|
+
"path": "shader/shadertoy"
|
|
666
|
+
}
|
|
667
|
+
]
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
"id": "advanced",
|
|
671
|
+
"title": "Advanced",
|
|
672
|
+
"icon": "settings",
|
|
673
|
+
"pages": [
|
|
674
|
+
{
|
|
675
|
+
"id": "advanced-multi-stream",
|
|
676
|
+
"title": "Multi-Stream",
|
|
677
|
+
"path": "advanced/multi-stream"
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"id": "advanced-performance",
|
|
681
|
+
"title": "Performance",
|
|
682
|
+
"path": "advanced/performance"
|
|
683
|
+
}
|
|
684
|
+
]
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
"id": "platform-features",
|
|
688
|
+
"title": "Platform Features",
|
|
689
|
+
"icon": "layers",
|
|
690
|
+
"pages": [
|
|
691
|
+
{
|
|
692
|
+
"id": "platform-compositor",
|
|
693
|
+
"title": "Compositor",
|
|
694
|
+
"path": "platform/compositor"
|
|
695
|
+
}
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
],
|
|
699
|
+
"pages": {
|
|
700
|
+
"overview": {
|
|
701
|
+
"id": "overview",
|
|
702
|
+
"title": "Overview",
|
|
703
|
+
"description": "Introduction to the Viji Artist API — write code that reacts to audio, video, user interaction, and more.",
|
|
704
|
+
"content": [
|
|
705
|
+
{
|
|
706
|
+
"type": "text",
|
|
707
|
+
"markdown": "# Viji Artist API\n\nViji is a creative platform for generative art and visuals. You write scene code, and Viji gives you a canvas, real-time audio analysis, video feeds, computer vision, user interaction, device sensors, and a parameter system that connects your code to a UI — all without any setup.\n\n## Your First Scene\n\nA circle orbiting the center, with a slider to control its speed — try editing the code:"
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
"type": "live-example",
|
|
711
|
+
"title": "Hello Viji",
|
|
712
|
+
"sceneCode": "const speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\n\nlet angle = 0;\n\nfunction render(viji) {\n angle += speed.value * viji.deltaTime;\n\n const ctx = viji.useContext('2d');\n ctx.clearRect(0, 0, viji.width, viji.height);\n\n const x = viji.width / 2 + Math.cos(angle) * viji.width * 0.3;\n const y = viji.height / 2 + Math.sin(angle) * viji.height * 0.3;\n const r = Math.min(viji.width, viji.height) * 0.05;\n\n ctx.beginPath();\n ctx.arc(x, y, r, 0, Math.PI * 2);\n ctx.fillStyle = '#ff6600';\n ctx.fill();\n}\n",
|
|
713
|
+
"sceneFile": "hello-viji.scene.js"
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
"type": "text",
|
|
717
|
+
"markdown": "A few things to notice:\n\n- **`viji.slider()`** creates a UI slider the user can adjust — defined once at the top level, read via `.value` inside `render()`.\n- **`viji.deltaTime`** is the time since the last frame in seconds — use it with an accumulator (`angle +=`) for smooth, frame-rate-independent animation that doesn't jump when you change parameters.\n- **`viji.width` / `viji.height`** keep your scene resolution-agnostic.\n- **`render(viji)`** is called every frame. This is where you draw.\n\n## What You Can Access\n\nEverything is available through the `viji` object:\n\n| Category | What It Gives You |\n|----------|------------------|\n| **Canvas** | `viji.canvas`, `viji.width`, `viji.height`, `viji.pixelRatio` |\n| **Timing** | `viji.time`, `viji.deltaTime`, `viji.frameCount`, `viji.fps` |\n| **Parameters** | `viji.slider()`, `viji.color()`, `viji.toggle()`, `viji.select()`, `viji.number()`, `viji.text()`, `viji.image()` |\n| **Audio** | Volume, frequency bands, beat detection, spectral analysis, FFT & waveform data |\n| **Video & CV** | Video frames, face detection, face mesh, emotion detection, hand tracking, pose detection, body segmentation |\n| **Interaction** | Mouse position & buttons, keyboard state, multi-touch & gestures |\n| **Sensors** | Accelerometer, gyroscope, device orientation |\n\n## Three Ways to Create\n\nViji supports three rendering modes:\n\n| Renderer | Best For | Entry Point |\n|----------|----------|-------------|\n| **Native** | Full control — Canvas 2D, WebGL, Three.js | `render(viji)` |\n| **P5.js** | Artists familiar with Processing / P5.js | `render(viji, p5)` |\n| **Shader** | GPU effects, raymarching, generative patterns | `void main()` in GLSL |\n\nAll three share the same audio, video, parameter, and interaction APIs. See [Renderers Overview](../renderers-overview/) for how each works.\n\n## Next Steps\n\n- [Renderers Overview](../renderers-overview/) — how to choose and use each renderer\n- [Best Practices](../best-practices/) — essential patterns for robust, performant scenes\n- [Common Mistakes](../common-mistakes/) — pitfalls to avoid\n- [Native Quick Start](/native/quickstart) — build with JavaScript and full canvas control\n- [P5 Quick Start](/p5/quickstart) — build with the familiar P5.js API\n- [Shader Quick Start](/shader/quickstart) — build with GLSL fragment shaders"
|
|
718
|
+
}
|
|
719
|
+
]
|
|
720
|
+
},
|
|
721
|
+
"renderers-overview": {
|
|
722
|
+
"id": "renderers-overview",
|
|
723
|
+
"title": "Renderers Overview",
|
|
724
|
+
"description": "How to choose and activate each of Viji's three rendering modes — Native, P5.js, and Shader — and what makes each one unique.",
|
|
725
|
+
"content": [
|
|
726
|
+
{
|
|
727
|
+
"type": "text",
|
|
728
|
+
"markdown": "# Renderers Overview\n\nViji supports three rendering modes. Each produces visuals on the same canvas and shares the same Artist API for parameters, audio, video, interaction, and sensors. The difference is the language and paradigm you use to draw.\n\n## Choosing a Renderer\n\nThe renderer is selected by a **comment directive** at the top of your scene code:\n\n```javascript\n// @renderer p5 → P5.js renderer\n// @renderer shader → Shader renderer\n// (no directive) → Native renderer (default)\n```\n\nIf no `@renderer` directive is present, the scene runs in **Native** mode. You can also write `// @renderer native` for clarity, but it's not required.\n\n> [!IMPORTANT]\n> P5 and shader scenes must declare their renderer type as the first comment:\n> ```\n> // @renderer p5\n> ```\n> or\n> ```\n> // @renderer shader\n> ```\n> Without this directive, the scene defaults to the native renderer.\n\n---\n\n## Native Renderer\n\nThe native renderer gives you direct access to the canvas. Write standard JavaScript (or TypeScript) and use any rendering approach: Canvas 2D, WebGL, or external libraries like Three.js.\n\n**Entry point:** `render(viji)`"
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
"type": "live-example",
|
|
732
|
+
"title": "Native — Canvas 2D",
|
|
733
|
+
"sceneCode": "const speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\n\nlet angle = 0;\n\nfunction render(viji) {\n angle += speed.value * viji.deltaTime;\n\n const ctx = viji.useContext('2d');\n ctx.clearRect(0, 0, viji.width, viji.height);\n\n const x = viji.width / 2 + Math.cos(angle) * viji.width * 0.3;\n const y = viji.height / 2 + Math.sin(angle) * viji.height * 0.3;\n const r = Math.min(viji.width, viji.height) * 0.05;\n\n ctx.beginPath();\n ctx.arc(x, y, r, 0, Math.PI * 2);\n ctx.fillStyle = '#ff6600';\n ctx.fill();\n}\n",
|
|
734
|
+
"sceneFile": "renderers-native.scene.js"
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
"type": "text",
|
|
738
|
+
"markdown": "**Key characteristics:**\n\n- **No setup function.** All initialization happens at the top level of your scene code. Top-level `await` is supported, which enables dynamic imports.\n- **Full canvas control.** Call `viji.useContext('2d')` for Canvas 2D, `viji.useContext('webgl')` for WebGL 1, or `viji.useContext('webgl2')` for WebGL 2. Choose one and use it consistently — switching context types discards the previous one.\n- **External libraries** can be loaded via dynamic import from a CDN. Here's a full Three.js scene running inside the native renderer:"
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
"type": "live-example",
|
|
742
|
+
"title": "Native — Three.js",
|
|
743
|
+
"sceneCode": "const THREE = await import('https://esm.sh/three@0.160.0');\n\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, viji.width / viji.height, 0.1, 1000);\ncamera.position.z = 3;\n\nconst renderer = new THREE.WebGLRenderer({ canvas: viji.canvas, antialias: true });\nrenderer.setSize(viji.width, viji.height, false);\n\nconst geometry = new THREE.BoxGeometry();\nconst material = new THREE.MeshNormalMaterial();\nconst cube = new THREE.Mesh(geometry, material);\nscene.add(cube);\n\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\n\nfunction render(viji) {\n cube.rotation.x += speed.value * viji.deltaTime * 0.5;\n cube.rotation.y += speed.value * viji.deltaTime;\n\n if (viji.width !== renderer.domElement.width || viji.height !== renderer.domElement.height) {\n renderer.setSize(viji.width, viji.height, false);\n camera.aspect = viji.width / viji.height;\n camera.updateProjectionMatrix();\n }\n\n renderer.render(scene, camera);\n}\n",
|
|
744
|
+
"sceneFile": "renderers-threejs.scene.js"
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
"type": "text",
|
|
748
|
+
"markdown": "See [External Libraries](/native/external-libraries) for detailed patterns with Three.js and other libraries.\n\n---\n\n## P5.js Renderer\n\nThe P5.js renderer provides the familiar Processing/P5.js creative coding API. Viji loads P5.js automatically when you use `// @renderer p5` — no installation or setup required.\n\n**Entry points:** `render(viji, p5)` (required), `setup(viji, p5)` (optional)"
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
"type": "live-example",
|
|
752
|
+
"title": "P5.js Renderer",
|
|
753
|
+
"sceneCode": "// @renderer p5\n\nconst speed = viji.slider(2, { min: 0.5, max: 10, label: 'Speed' });\n\nlet angle = 0;\n\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB);\n}\n\nfunction render(viji, p5) {\n angle += speed.value * viji.deltaTime;\n\n p5.background(0);\n\n const x = p5.width / 2 + p5.cos(angle) * p5.width * 0.3;\n const y = p5.height / 2 + p5.sin(angle) * p5.height * 0.3;\n const size = p5.min(p5.width, p5.height) * 0.1;\n\n p5.fill(viji.time * 50 % 360, 80, 100);\n p5.noStroke();\n p5.circle(x, y, size);\n}\n",
|
|
754
|
+
"sceneFile": "renderers-p5.scene.js"
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
"type": "text",
|
|
758
|
+
"markdown": "> [!WARNING]\n> Viji uses P5 in **instance mode**. All P5 functions require the `p5.` prefix:\n> ```javascript\n> // Correct\n> p5.background(0);\n> p5.circle(p5.width / 2, p5.height / 2, 100);\n>\n> // Wrong — will throw ReferenceError\n> background(0);\n> circle(width / 2, height / 2, 100);\n> ```\n\n**Key characteristics:**\n\n- **`setup()` is optional.** Use it for one-time configuration like `p5.colorMode()`. If you don't need it, omit it entirely.\n- **`render()` replaces `draw()`.** P5's built-in draw loop is disabled; Viji calls your `render()` function each frame.\n- **No `createCanvas()`.** The canvas is created and managed by Viji.\n- **Viji APIs for input.** Use `viji.mouse`, `viji.keyboard`, `viji.touches` instead of P5's `mouseX`, `keyIsPressed`, etc.\n- **No `preload()`.** Load assets using Viji's `viji.image()` parameter, or use `fetch()` in `setup()`.\n\nIf you have existing P5.js sketches, see [Converting P5 Sketches](/p5/converting-sketches) for a step-by-step migration guide.\n\n---\n\n## Shader Renderer\n\nThe shader renderer lets you write GLSL fragment shaders that run directly on the GPU. Viji automatically injects all uniform declarations — you write only your helper functions and `void main()`.\n\n**Entry point:** `void main()` (GLSL)"
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
"type": "live-example",
|
|
762
|
+
"title": "Shader Renderer",
|
|
763
|
+
"sceneCode": "// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0 step:0.1\n// @viji-color:baseColor label:\"Base Color\" default:#ff6600\n// @viji-accumulator:phase rate:speed\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n\n float wave = sin(uv.x * 10.0 + phase) * 0.5 + 0.5;\n vec3 color = baseColor * wave;\n\n gl_FragColor = vec4(color, 1.0);\n}\n",
|
|
764
|
+
"sceneFile": "renderers-shader.scene.glsl"
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
"type": "text",
|
|
768
|
+
"markdown": "> [!NOTE]\n> The Viji shader renderer automatically injects `precision mediump float;` and all `uniform` declarations. Write only your helper functions and `void main() { ... }`. Do NOT redeclare `precision` or built-in uniforms — they will conflict.\n\n**Key characteristics:**\n\n- **Fragment shader only.** Viji renders a fullscreen quad; your shader defines the color of every pixel.\n- **GLSL ES 1.00 by default.** If you add `#version 300 es` as the first line, Viji switches to WebGL 2. Note that ES 3.00 requires `out vec4` for output instead of `gl_FragColor`, and `texture()` instead of `texture2D()`. ES 1.00 is recommended for maximum compatibility.\n- **Built-in uniforms** like `u_time`, `u_resolution`, `u_mouse`, `u_audioVolume`, `u_video`, and many more are always available — no declaration needed.\n- **Parameters via comments.** Declare parameters with `// @viji-TYPE:uniformName key:value` syntax. They become uniforms automatically.\n- **Accumulators for smooth animation.** Use `// @viji-accumulator:phase rate:speed` instead of `u_time * speed` — the value grows smoothly without jumping when the rate parameter changes.\n- **No `u_` prefix for your parameters.** The `u_` prefix is reserved for Viji's built-in uniforms. Name your parameters descriptively: `speed`, `colorMix`, `intensity`.\n\nIf you have existing Shadertoy shaders, see [Shadertoy Compatibility](/shader/shadertoy) for a compatibility layer that lets you paste code with minimal changes.\n\n---\n\n## Comparison\n\n| | Native | P5.js | Shader |\n|---|--------|-------|--------|\n| **Language** | JavaScript / TypeScript | JavaScript with P5 API | GLSL ES 1.00 (or 3.00 with `#version 300 es`) |\n| **Directive** | None (default) | `// @renderer p5` | `// @renderer shader` |\n| **Entry point** | `render(viji)` | `render(viji, p5)` | `void main()` |\n| **Setup** | Top-level code + `await` | Optional `setup(viji, p5)` | N/A |\n| **Canvas access** | `viji.useContext('2d'/'webgl'/'webgl2')` | P5 drawing functions | Automatic fullscreen quad |\n| **External libraries** | Yes (`await import(...)`) | P5.js only | No |\n| **Best for** | Full control, WebGL, Three.js | Familiar P5 workflows | GPU effects, raymarching |\n| **Parameters** | `viji.slider()`, etc. | `viji.slider()`, etc. | `// @viji-slider:name ...` |\n\n## Next Steps\n\n- [Native Quick Start](/native/quickstart) — build your first native scene\n- [P5 Quick Start](/p5/quickstart) — build your first P5.js scene\n- [Shader Quick Start](/shader/quickstart) — build your first shader\n- [Best Practices](../best-practices/) — essential patterns all artists should follow"
|
|
769
|
+
}
|
|
770
|
+
]
|
|
771
|
+
},
|
|
772
|
+
"best-practices": {
|
|
773
|
+
"id": "best-practices",
|
|
774
|
+
"title": "Best Practices",
|
|
775
|
+
"description": "Essential patterns every Viji artist should follow — resolution-agnostic design, time-based animation, memory management, and more.",
|
|
776
|
+
"content": [
|
|
777
|
+
{
|
|
778
|
+
"type": "text",
|
|
779
|
+
"markdown": "# Best Practices\n\nThese practices apply to all three renderers (Native, P5, Shader). Following them ensures your scenes look correct at any resolution, run smoothly at any frame rate, and work reliably across devices.\n\n---\n\n## Use `viji.time` and `viji.deltaTime` for Animation\n\nViji provides two timing values. Use the right one for the job:\n\n- **`viji.time`** — seconds since the scene started. Use this for most animations (oscillations, rotations, color cycling). This is the most common choice.\n- **`viji.deltaTime`** — seconds since the last frame. Use this when you need to accumulate values smoothly regardless of frame rate (movement, physics, fading).\n\n```javascript\n// viji.time — animation that looks identical regardless of frame rate\nconst angle = viji.time * speed.value;\nconst x = Math.cos(angle) * radius;\n\n// viji.deltaTime — accumulation that stays smooth at any FPS\nposition += velocity * viji.deltaTime;\nopacity -= fadeRate * viji.deltaTime;\n```\n\nFor shaders, the equivalents are `u_time` and `u_deltaTime`. When animation speed is driven by a parameter, use an **accumulator** to avoid jumps:\n\n```glsl\n// Instead of: float wave = sin(u_time * speed); ← jumps when slider moves\n// @viji-accumulator:phase rate:speed\nfloat wave = sin(phase + uv.x * 10.0); // smooth at any slider value\n```\n\n> [!NOTE]\n> Always use `viji.time` or `viji.deltaTime` for animation. Never count frames or assume a specific frame rate — the host application may run your scene at different rates (`full` or `half` mode) or the actual FPS may vary by device.\n\n---\n\n## Design for Any Resolution\n\nThe host application controls your scene's resolution. It may change at any time (window resize, resolution scaling for performance, high-DPI displays). Never hardcode pixel values.\n\n**Use `viji.width` and `viji.height`** for all positioning and sizing:\n\n```javascript\n// Good — scales to any resolution\nconst centerX = viji.width / 2;\nconst centerY = viji.height / 2;\nconst radius = Math.min(viji.width, viji.height) * 0.1;\n\n// Bad — breaks at different resolutions\nconst centerX = 960;\nconst centerY = 540;\nconst radius = 50;\n```\n\nFor parameters that control sizes, use normalized values (0–1) and multiply by canvas dimensions:\n\n```javascript\nconst size = viji.slider(0.15, { min: 0.02, max: 0.5, label: 'Size' });\n\nfunction render(viji) {\n const pixelSize = size.value * Math.min(viji.width, viji.height);\n}\n```\n\nFor shaders, use `u_resolution`:\n\n```glsl\nvec2 uv = gl_FragCoord.xy / u_resolution; // normalized 0–1 coordinates\n```\n\n> [!NOTE]\n> Always use `viji.width` and `viji.height` for positioning and sizing, and `viji.deltaTime` for frame-rate-independent animation. Never hardcode pixel values or assume a specific frame rate.\n\n---\n\n## Declare Parameters at the Top Level\n\nParameter functions (`viji.slider()`, `viji.color()`, etc.) register controls with the host application. They must be called **once**, at the top level of your scene code — never inside `render()`.\n\n```javascript\n// Correct — declared once at top level\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nconst bgColor = viji.color('#1a1a2e', { label: 'Background' });\n\nfunction render(viji) {\n // Read current values inside render\n const s = speed.value;\n const bg = bgColor.value;\n}\n```\n\n> [!NOTE]\n> Parameters must be defined at the top level of your scene, not inside `render()`. They are registered once during initialization. Defining them inside `render()` would create duplicate parameters on every frame.\n\n---\n\n## Avoid Allocations in the Render Loop\n\nCreating objects, arrays, or strings inside `render()` triggers garbage collection, causing frame drops and stuttering. Pre-allocate at the top level and reuse.\n\n> [!TIP]\n> Avoid allocating objects, arrays, or strings inside `render()`. Pre-allocate at the top level and reuse them:\n> ```javascript\n> // Good — pre-allocated\n> const pos = { x: 0, y: 0 };\n> function render(viji) {\n> pos.x = viji.width / 2;\n> pos.y = viji.height / 2;\n> }\n>\n> // Bad — creates a new object every frame\n> function render(viji) {\n> const pos = { x: viji.width / 2, y: viji.height / 2 };\n> }\n> ```\n\nThis is especially important for particle systems, arrays of positions, or any data structure that persists across frames.\n\n---\n\n## No DOM APIs (but `fetch` Is Fine)\n\nYour scene runs in a Web Worker. Standard DOM APIs are not available:\n\n- No `window`, `document`, `Image()`, `localStorage`\n- No `createElement`, `querySelector`, `addEventListener`\n\nHowever, **`fetch()` works** and can be used to load JSON, text, or other data from external URLs:\n\n```javascript\n// This works — fetch is available in workers\nconst response = await fetch('https://cdn.example.com/data.json');\nconst data = await response.json();\n```\n\nFor images, use Viji's `viji.image()` parameter — the host application handles file selection and transfers the image to the worker.\n\n> [!WARNING]\n> Scenes run in a Web Worker — there is no `window`, `document`, `Image()`, `localStorage`, or any DOM API. All inputs (audio, video, images) are provided through the Viji API. Note: `fetch()` IS available and can be used to load external data (JSON, etc.) from CDNs.\n\n---\n\n## Guard Audio and Video with `isConnected`\n\nAudio and video streams are provided by the host and may not always be available. Always check `isConnected` before using audio or video data:\n\n```javascript\nfunction render(viji) {\n if (viji.audio.isConnected) {\n const bass = viji.audio.bands.low;\n // ... use audio data\n }\n\n if (viji.video.isConnected && viji.video.currentFrame) {\n ctx.drawImage(viji.video.currentFrame, 0, 0, viji.width, viji.height);\n }\n}\n```\n\nWithout this guard, your scene would reference undefined or zero values when no audio/video source is connected.\n\n---\n\n## Be Mindful of Computer Vision Costs\n\nCV features (face detection, hand tracking, pose detection, etc.) are powerful but expensive. Each feature runs ML inference in its own WebGL context.\n\n| Feature | Relative Cost | Notes |\n|---------|--------------|-------|\n| Face Detection | Low | Bounding box + basic landmarks only |\n| Face Mesh | Medium-High | 468 facial landmarks |\n| Emotion Detection | High | 7 expressions + 52 blendshape coefficients |\n| Hand Tracking | Medium | Up to 2 hands, 21 landmarks each |\n| Pose Detection | Medium | 33 body landmarks |\n| Body Segmentation | High | Per-pixel mask, large tensor output |\n\n> [!WARNING]\n> **WebGL Context Limits:** Each CV feature requires its own WebGL context for ML inference. Browsers typically allow 8-16 active WebGL contexts. Enabling too many CV features simultaneously can cause context eviction, potentially breaking the scene's own rendering. Use only the CV features you need.\n\n**Don't enable CV features by default.** Instead, expose a toggle parameter so users can activate them on capable devices:\n\n> [!TIP]\n> **Best practice:** Don't enable CV features by default. Instead, expose a toggle parameter so users can activate them on capable devices:\n> ```javascript\n> const useFace = viji.toggle(false, { label: 'Enable Face Detection', category: 'video' });\n> if (useFace.value) {\n> await viji.video.cv.enableFaceDetection(true);\n> }\n> ```\n\n---\n\n## Pick One Canvas Context Type\n\nThe native renderer lets you choose between 2D and WebGL contexts via `viji.useContext()`. Options are `'2d'`, `'webgl'` (WebGL 1), and `'webgl2'` (WebGL 2). Pick one and stick with it.\n\n> [!WARNING]\n> Calling `useContext('2d')` and `useContext('webgl')`/`useContext('webgl2')` on the same canvas is mutually exclusive. Once a context type is obtained, switching to the other discards the previous one. Choose one context type and use it consistently.\n\n---\n\n## Related\n\n- [Common Mistakes](../common-mistakes/) — specific wrong/right code examples\n- [Performance](/advanced/performance) — deep dive into optimization\n- [Renderers Overview](../renderers-overview/) — choosing the right renderer"
|
|
780
|
+
}
|
|
781
|
+
]
|
|
782
|
+
},
|
|
783
|
+
"common-mistakes": {
|
|
784
|
+
"id": "common-mistakes",
|
|
785
|
+
"title": "Common Mistakes",
|
|
786
|
+
"description": "Frequent pitfalls when writing Viji scenes — with wrong and right code examples for each.",
|
|
787
|
+
"content": [
|
|
788
|
+
{
|
|
789
|
+
"type": "text",
|
|
790
|
+
"markdown": "# Common Mistakes\n\nThis page collects the most frequent mistakes artists make when writing Viji scenes. Each section shows the wrong approach and the correct alternative.\n\n---\n\n## Using DOM APIs\n\nScenes run in a Web Worker. There is no DOM.\n\n```javascript\n// Wrong — DOM APIs don't exist in workers\nconst img = new Image();\nimg.src = 'photo.jpg';\n\ndocument.createElement('canvas');\nwindow.innerWidth;\nlocalStorage.setItem('key', 'value');\n```\n\n```javascript\n// Right — use Viji's API for inputs\nconst photo = viji.image(null, { label: 'Photo' });\n\n// Use viji.canvas, viji.width, viji.height instead\n// Use fetch() for loading external data:\nconst data = await fetch('https://cdn.example.com/data.json').then(r => r.json());\n```\n\n---\n\n## Declaring Parameters Inside `render()`\n\nParameter functions register UI controls with the host. Calling them in `render()` creates duplicates every frame.\n\n```javascript\n// Wrong — creates a new slider every frame (60+ times per second)\nfunction render(viji) {\n const speed = viji.slider(1, { min: 0, max: 5, label: 'Speed' });\n // ...\n}\n```\n\n```javascript\n// Right — declare once at top level, read .value in render()\nconst speed = viji.slider(1, { min: 0, max: 5, label: 'Speed' });\n\nfunction render(viji) {\n const s = speed.value;\n // ...\n}\n```\n\n---\n\n## Forgetting `.value` on Parameters\n\nParameter objects are not raw values. You need to access `.value` to get the current value.\n\n```javascript\n// Wrong — uses the parameter object, not its value\nconst radius = viji.slider(50, { min: 10, max: 200, label: 'Radius' });\n\nfunction render(viji) {\n ctx.arc(x, y, radius, 0, Math.PI * 2); // radius is an object, not a number\n}\n```\n\n```javascript\n// Right — access .value\nfunction render(viji) {\n ctx.arc(x, y, radius.value, 0, Math.PI * 2);\n}\n```\n\n---\n\n## Hardcoding Pixel Values\n\nThe host controls your scene's resolution. Hardcoded values break at different sizes.\n\n```javascript\n// Wrong — only looks right at one specific resolution\nfunction render(viji) {\n ctx.arc(960, 540, 50, 0, Math.PI * 2);\n}\n```\n\n```javascript\n// Right — adapts to any resolution\nfunction render(viji) {\n const cx = viji.width / 2;\n const cy = viji.height / 2;\n const r = Math.min(viji.width, viji.height) * 0.05;\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n}\n```\n\n---\n\n## Frame-Rate-Dependent Animation\n\nCounting frames or using fixed increments makes animation speed depend on the device's frame rate.\n\n```javascript\n// Wrong — faster on 120Hz displays, slower on 30Hz\nlet angle = 0;\nfunction render(viji) {\n angle += 0.02;\n}\n```\n\n```javascript\n// Right — use viji.time for consistent speed regardless of FPS\nfunction render(viji) {\n const angle = viji.time * speed.value;\n}\n\n// Or use viji.deltaTime for accumulation\nlet position = 0;\nfunction render(viji) {\n position += velocity * viji.deltaTime;\n}\n```\n\n---\n\n## Allocating Objects in `render()`\n\nCreating new objects every frame causes garbage collection pauses.\n\n```javascript\n// Wrong — new object every frame\nfunction render(viji) {\n const particles = [];\n for (let i = 0; i < 100; i++) {\n particles.push({ x: Math.random() * viji.width, y: Math.random() * viji.height });\n }\n}\n```\n\n```javascript\n// Right — pre-allocate and reuse\nconst particles = Array.from({ length: 100 }, () => ({ x: 0, y: 0 }));\n\nfunction render(viji) {\n for (const p of particles) {\n p.x = Math.random() * viji.width;\n p.y = Math.random() * viji.height;\n }\n}\n```\n\n---\n\n## P5: Missing the `p5.` Prefix\n\nViji runs P5 in **instance mode**. All P5 functions must be called on the `p5` object.\n\n```javascript\n// @renderer p5\n\n// Wrong — global P5 functions don't exist\nfunction render(viji, p5) {\n background(0); // ReferenceError\n fill(255, 0, 0); // ReferenceError\n circle(width / 2, height / 2, 100); // ReferenceError\n}\n```\n\n```javascript\n// @renderer p5\n\n// Right — use p5. prefix for P5 functions, viji.* for dimensions\nfunction render(viji, p5) {\n p5.background(0);\n p5.fill(255, 0, 0);\n p5.circle(viji.width / 2, viji.height / 2, 100);\n}\n```\n\n---\n\n## P5: Using `draw()` Instead of `render()`\n\nP5's built-in draw loop is disabled in Viji. Your function must be named `render`, not `draw`.\n\n```javascript\n// @renderer p5\n\n// Wrong — Viji never calls draw()\nfunction draw(viji, p5) {\n p5.background(0);\n}\n```\n\n```javascript\n// @renderer p5\n\n// Right — Viji calls render() every frame\nfunction render(viji, p5) {\n p5.background(0);\n}\n```\n\n---\n\n## P5: Calling `createCanvas()`\n\nThe canvas is created and managed by Viji. Calling `createCanvas()` creates a second canvas that won't be displayed.\n\n```javascript\n// @renderer p5\n\n// Wrong — creates a separate, invisible canvas\nfunction setup(viji, p5) {\n p5.createCanvas(800, 600);\n}\n```\n\n```javascript\n// @renderer p5\n\n// Right — canvas is already provided, just configure settings\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB);\n}\n```\n\n---\n\n## P5: Using Event Callbacks\n\nP5 event callbacks like `mousePressed()`, `keyPressed()`, `touchStarted()` do not work in Viji's worker environment. Use Viji's interaction APIs instead.\n\n```javascript\n// @renderer p5\n\n// Wrong — these callbacks are never called\nfunction mousePressed() {\n console.log('clicked');\n}\n```\n\n```javascript\n// @renderer p5\n\n// Right — check Viji's interaction state in render()\nfunction render(viji, p5) {\n if (viji.mouse.wasPressed) {\n console.log('clicked');\n }\n}\n```\n\n---\n\n## Shader: Redeclaring Auto-Injected Code\n\nViji auto-injects `precision` and all built-in uniform declarations. Redeclaring them causes conflicts.\n\n```glsl\n// @renderer shader\n\n// Wrong — these are already injected by Viji\nprecision mediump float;\nuniform vec2 u_resolution;\nuniform float u_time;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\n}\n```\n\n```glsl\n// @renderer shader\n\n// Right — just write your code, uniforms are available automatically\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv, sin(u_time), 1.0);\n}\n```\n\n---\n\n## Shader: Using `u_` Prefix for Parameters\n\nThe `u_` prefix is reserved for Viji's built-in uniforms. Using it for your parameters risks naming collisions.\n\n```glsl\n// Wrong — u_ prefix is reserved\n// @viji-slider:u_speed label:\"Speed\" default:1.0\n```\n\n```glsl\n// Right — use descriptive names without u_ prefix\n// @viji-slider:speed label:\"Speed\" default:1.0\n```\n\n---\n\n## Shader: Missing `@renderer shader`\n\nWithout the directive, your GLSL code is treated as JavaScript and will throw syntax errors.\n\n```glsl\n// Wrong — no directive, treated as JavaScript\nvoid main() {\n gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n}\n```\n\n```glsl\n// Right — directive tells Viji to use the shader renderer\n// @renderer shader\n\nvoid main() {\n gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n}\n```\n\n---\n\n## Shader: Using Block Comments for `@viji-*` Parameters\n\nThe `@viji-*` parameter declarations only work with single-line `//` comments. Block comments `/* */` are silently ignored.\n\n```glsl\n// @renderer shader\n\n// Wrong — block comments are not parsed for parameters\n/* @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0 */\n\nvoid main() {\n gl_FragColor = vec4(speed, 0.0, 0.0, 1.0); // speed is undefined\n}\n```\n\n```glsl\n// @renderer shader\n\n// Right — use single-line comments for parameter declarations\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0\n\nvoid main() {\n gl_FragColor = vec4(speed, 0.0, 0.0, 1.0);\n}\n```\n\n> [!NOTE]\n> The `@renderer` directive supports both `//` and `/* */` styles, but `@viji-*` parameter declarations require `//`.\n\n---\n\n## Shader: Using `u_time * speed` for Parameter-Driven Animation\n\nMultiplying `u_time` by a parameter causes the entire phase to jump when the slider moves, because the full history is recalculated instantly.\n\n```glsl\n// Wrong — animation jumps when speed slider changes\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\nvoid main() {\n float wave = sin(u_time * speed);\n gl_FragColor = vec4(vec3(wave * 0.5 + 0.5), 1.0);\n}\n```\n\n```glsl\n// Right — accumulator integrates smoothly, no jumps\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\n// @viji-accumulator:phase rate:speed\nvoid main() {\n float wave = sin(phase);\n gl_FragColor = vec4(vec3(wave * 0.5 + 0.5), 1.0);\n}\n```\n\n---\n\n## Not Checking `isConnected` for Audio/Video\n\nAudio and video streams may not be available. Accessing their properties without checking `isConnected` gives meaningless zero values with no indication that something is missing.\n\n```javascript\n// Wrong — no guard, silently uses zero values\nfunction render(viji) {\n const bass = viji.audio.bands.low;\n ctx.drawImage(viji.video.currentFrame, 0, 0);\n}\n```\n\n```javascript\n// Right — check connection state first\nfunction render(viji) {\n if (viji.audio.isConnected) {\n const bass = viji.audio.bands.low;\n // ... react to audio\n }\n\n if (viji.video.isConnected && viji.video.currentFrame) {\n ctx.drawImage(viji.video.currentFrame, 0, 0, viji.width, viji.height);\n }\n}\n```\n\n---\n\n## Enabling All CV Features by Default\n\nEnabling CV features without user consent wastes resources on devices that can't handle it, and risks WebGL context loss.\n\n```javascript\n// Wrong — activates expensive CV on every device\nawait viji.video.cv.enableFaceDetection(true);\nawait viji.video.cv.enableHandTracking(true);\nawait viji.video.cv.enablePoseDetection(true);\nawait viji.video.cv.enableBodySegmentation(true);\n```\n\n```javascript\n// Right — let the user opt in\nconst useFace = viji.toggle(false, { label: 'Enable Face Tracking', category: 'video' });\nconst useHands = viji.toggle(false, { label: 'Enable Hand Tracking', category: 'video' });\n\nfunction render(viji) {\n if (useFace.value) await viji.video.cv.enableFaceDetection(true);\n else await viji.video.cv.enableFaceDetection(false);\n\n if (useHands.value) await viji.video.cv.enableHandTracking(true);\n else await viji.video.cv.enableHandTracking(false);\n}\n```\n\n---\n\n## Related\n\n- [Best Practices](../best-practices/) — positive guidance for writing robust scenes\n- [Performance](/advanced/performance) — deep dive into optimization\n- [Renderers Overview](../renderers-overview/) — choosing the right renderer"
|
|
791
|
+
}
|
|
792
|
+
]
|
|
793
|
+
},
|
|
794
|
+
"ai-convert-p5": {
|
|
795
|
+
"id": "ai-convert-p5",
|
|
796
|
+
"title": "\"Convert: P5 Sketches to Viji\"",
|
|
797
|
+
"description": "A self-contained AI prompt for converting standard P5.js sketches into Viji-P5 scenes.",
|
|
798
|
+
"content": [
|
|
799
|
+
{
|
|
800
|
+
"type": "text",
|
|
801
|
+
"markdown": "# Convert: P5 Sketches to Viji\n\nCopy the prompt below and paste it into your AI assistant along with the P5.js sketch you want to convert. The prompt contains all the rules the AI needs to produce a correct Viji-P5 scene.\n\n## The Prompt\n\n````\nYou are converting a standard P5.js sketch into a Viji-P5 scene.\nViji scenes run inside an OffscreenCanvas Web Worker. Apply every rule below exactly.\n\n## RULES\n\n1. ALWAYS add `// @renderer p5` as the very first line.\n2. ALWAYS rename `draw()` to `render(viji, p5)`.\n3. If `setup()` exists, change its signature to `setup(viji, p5)`. If it doesn't exist, do NOT add one.\n4. ALWAYS prefix every P5 function and constant with `p5.`:\n - `background(0)` → `p5.background(0)`\n - `fill(255)` → `p5.fill(255)`\n - `PI` → `p5.PI`, `TWO_PI` → `p5.TWO_PI`, `HSB` → `p5.HSB`\n - `createVector(1, 0)` → `p5.createVector(1, 0)`\n - `map(v, 0, 1, 0, 255)` → `p5.map(v, 0, 1, 0, 255)`\n - `noise(x)` → `p5.noise(x)`\n This applies to ALL P5 functions and constants without exception.\n5. NEVER call `createCanvas()`. The canvas is created and managed by Viji.\n6. NEVER use `preload()`. Use `viji.image(null, { label: 'Name' })` for images, or `fetch()` in an async `setup()` for data.\n7. NEVER use P5 event callbacks: `mousePressed()`, `mouseDragged()`, `mouseReleased()`, `keyPressed()`, `keyReleased()`, `keyTyped()`, `touchStarted()`, `touchMoved()`, `touchEnded()`. Instead, check state in `render()`:\n - `mouseIsPressed` → `viji.mouse.isPressed`\n - `mouseX` / `mouseY` → `viji.mouse.x` / `viji.mouse.y`\n - `keyIsPressed` → `viji.keyboard.isPressed('keyName')`\n - For press-edge detection: track a `wasPressed` variable and compare.\n8. NEVER use `p5.frameRate()`, `p5.save()`, `p5.saveCanvas()`, `p5.saveFrames()`. These are host-level concerns.\n9. NEVER use `loadImage()`, `loadFont()`, `loadJSON()`, `loadModel()`, `loadShader()`. Use `viji.image()` or `fetch()`.\n10. NEVER use `createCapture()` or `createVideo()`. Use `viji.video.*` instead.\n11. NEVER use `p5.dom` or `p5.sound` libraries. Use Viji parameters for UI and `viji.audio.*` for audio.\n12. NEVER access `window`, `document`, `Image()`, or `localStorage`. `fetch()` IS available.\n13. ALWAYS declare parameters at the TOP LEVEL, never inside `render()` or `setup()`:\n ```javascript\n // CORRECT\n const size = viji.slider(50, { min: 10, max: 200, label: 'Size' });\n function render(viji, p5) { p5.circle(0, 0, size.value); }\n\n // WRONG — creates a new parameter every frame\n function render(viji, p5) { const size = viji.slider(50, { ... }); }\n ```\n14. ALWAYS read parameters via `.value`: `size.value`, `color.value`, `toggle.value`.\n15. ALWAYS use `viji.width` and `viji.height` for canvas dimensions. NEVER hardcode pixel sizes.\n16. ALWAYS use `viji.deltaTime` for frame-rate-independent animation. Replace `frameCount * 0.01` patterns with a deltaTime accumulator:\n ```javascript\n let angle = 0;\n function render(viji, p5) {\n angle += speed.value * viji.deltaTime;\n }\n ```\n17. NEVER allocate objects, arrays, or strings inside `render()`. Pre-allocate at the top level and reuse.\n18. For image parameters displayed with P5, use `photo.p5` (not `photo.value`) with `p5.image()`:\n ```javascript\n const photo = viji.image(null, { label: 'Photo' });\n function render(viji, p5) {\n if (photo.value) p5.image(photo.p5, 0, 0, viji.width, viji.height);\n }\n ```\n\n## API MAPPING\n\n| Standard P5.js | Viji-P5 |\n|---|---|\n| `width` / `height` | `viji.width` / `viji.height` |\n| `mouseX` / `mouseY` | `viji.mouse.x` / `viji.mouse.y` |\n| `mouseIsPressed` | `viji.mouse.isPressed` |\n| `mouseButton === LEFT` | `viji.mouse.leftButton` |\n| `keyIsPressed` | `viji.keyboard.isPressed('keyName')` |\n| `key` | `viji.keyboard.lastKey` |\n| `frameCount` | Use `viji.time` or `viji.deltaTime` accumulator |\n| `frameRate(n)` | Remove — host controls frame rate |\n| `createCanvas(w, h)` | Remove — canvas is provided |\n| `preload()` | Remove — use `viji.image()` or `fetch()` in `setup()` |\n| `loadImage(url)` | `viji.image(null, { label: 'Image' })` |\n| `save()` | Remove — host uses `captureFrame()` |\n\n## PARAMETER TYPES\n\n```javascript\nviji.slider(default, { min, max, step, label, group, category }) // returns { value: number }\nviji.color(default, { label, group, category }) // returns { value: '#rrggbb' }\nviji.toggle(default, { label, group, category }) // returns { value: boolean }\nviji.select(default, { options: [...], label, group, category }) // returns { value: string }\nviji.number(default, { min, max, step, label, group, category }) // returns { value: number }\nviji.text(default, { label, group, category }) // returns { value: string }\nviji.image(default, { label, group, category }) // returns { value: ImageBitmap|null, p5: P5Image }\n```\n\n## TEMPLATE\n\n```javascript\n// @renderer p5\n\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\n\nlet angle = 0;\n\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB, 360, 100, 100);\n}\n\nfunction render(viji, p5) {\n angle += speed.value * viji.deltaTime;\n p5.background(0, 0, 10);\n const x = viji.width / 2 + p5.cos(angle) * viji.width * 0.3;\n const y = viji.height / 2 + p5.sin(angle) * viji.height * 0.3;\n p5.noStroke();\n p5.fill(angle * 30 % 360, 80, 100);\n p5.circle(x, y, viji.width * 0.05);\n}\n```\n\nNow convert the P5.js sketch I provide. Return ONLY the converted Viji-P5 scene code.\n````\n\n## Usage\n\n1. Copy the entire prompt block above.\n2. Paste it into your AI assistant.\n3. After the prompt, paste the P5.js sketch you want to convert.\n4. The AI will return a Viji-compatible scene.\n\nFor a detailed human-readable guide, see [Converting P5 Sketches](/p5/converting-sketches).\n\n## Related\n\n- [Converting P5 Sketches](/p5/converting-sketches) — step-by-step manual conversion guide\n- [Prompt: P5 Scenes](/ai-prompts/p5-prompt) — AI prompt for creating new P5 scenes from scratch\n- [P5 Quick Start](/p5/quickstart) — your first Viji-P5 scene"
|
|
802
|
+
}
|
|
803
|
+
]
|
|
804
|
+
},
|
|
805
|
+
"ai-convert-shadertoy": {
|
|
806
|
+
"id": "ai-convert-shadertoy",
|
|
807
|
+
"title": "\"Convert: Shadertoy to Viji\"",
|
|
808
|
+
"description": "A self-contained AI prompt for converting Shadertoy shaders into Viji shader scenes.",
|
|
809
|
+
"content": [
|
|
810
|
+
{
|
|
811
|
+
"type": "text",
|
|
812
|
+
"markdown": "# Convert: Shadertoy to Viji\n\nCopy the prompt below and paste it into your AI assistant along with the Shadertoy shader you want to convert. The prompt contains all the rules the AI needs to produce a correct Viji shader scene.\n\n## The Prompt\n\n````\nYou are converting a Shadertoy shader into a Viji shader scene.\nViji runs GLSL fragment shaders with automatic uniform injection. Apply every rule below exactly.\n\n## RULES\n\n1. ALWAYS add `// @renderer shader` as the very first line (or after `#version 300 es` if using GLSL ES 3.00).\n2. NEVER declare `precision mediump float;` or `precision highp float;` — Viji auto-injects precision.\n3. NEVER redeclare built-in uniforms (`u_time`, `u_resolution`, `u_mouse`, etc.) — they are auto-injected.\n4. NEVER redeclare parameter uniforms — they are auto-generated from `@viji-*` directives.\n5. Convert the `mainImage` signature to a standard `void main()`:\n - Replace `fragCoord` with `gl_FragCoord.xy`\n - Replace `fragColor` with `gl_FragColor`\n - Remove the `mainImage` wrapper entirely.\n6. ALWAYS replace Shadertoy uniforms with Viji equivalents using this mapping:\n\n | Shadertoy | Viji | Notes |\n |---|---|---|\n | `iResolution.xy` | `u_resolution` | Viji's `u_resolution` is `vec2`. For `.z` (aspect ratio), use `u_resolution.x / u_resolution.y`. |\n | `iResolution.x`, `.y` | `u_resolution.x`, `.y` | Direct match. |\n | `iTime` | `u_time` | Elapsed seconds. |\n | `iTimeDelta` | `u_deltaTime` | Seconds since last frame. |\n | `iFrame` | `u_frame` | Frame counter (`int`). |\n | `iMouse.xy` | `u_mouse` | Current mouse position in pixels. |\n | `iMouse.z` | Approximate with `u_mouseLeft ? u_mouse.x : 0.0` | Viji does not track click-origin. |\n | `iMouse.w` | Approximate with `u_mouseLeft ? u_mouse.y : 0.0` | Viji does not track click-origin. |\n | `iChannel0`–`3` | Declare `@viji-image` parameters | See texture rules below. |\n | `iChannelResolution` | Not available | Track dimensions manually if needed. |\n | `iDate` | Not available | Use `u_time` for elapsed time. |\n | `iSampleRate` | Not available | Not applicable. |\n\n7. For `iChannel` textures, declare `@viji-image` parameters:\n ```glsl\n // @viji-image:channel0 label:\"Texture 1\"\n // @viji-image:channel1 label:\"Texture 2\"\n ```\n Then replace `texture(iChannel0, uv)` with `texture2D(channel0, uv)` (or `texture(channel0, uv)` in ES 3.00).\n\n8. For `iResolution` used as `vec3`, replace with:\n ```glsl\n vec3(u_resolution, u_resolution.x / u_resolution.y)\n ```\n Or refactor to use `u_resolution` as `vec2` directly — most code only needs `.xy`.\n\n9. If the shader uses `iTime` multiplied by a speed factor, ALWAYS use an accumulator instead of `u_time * speed` to prevent animation jumps when the slider changes:\n ```glsl\n // @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\n // @viji-accumulator:phase rate:speed\n ```\n Then replace `iTime` with `phase`.\n\n10. If adding artist-controllable parameters, ALWAYS use `// @viji-*` directives:\n ```glsl\n // @viji-slider:name label:\"Label\" default:1.0 min:0.0 max:5.0\n // @viji-color:name label:\"Label\" default:#ff6600\n // @viji-toggle:name label:\"Label\" default:true\n // @viji-select:name label:\"Label\" default:0 options:[\"A\",\"B\",\"C\"]\n // @viji-image:name label:\"Label\"\n // @viji-accumulator:name rate:source\n ```\n NEVER use the `u_` prefix for parameter names — it is reserved for built-in uniforms.\n\n11. Parameter directives ONLY work with `//` comments. NEVER use `/* */` for `@viji-*` directives.\n\n12. If the shader uses `#version 300 es`:\n - Keep it as the very first line (before `// @renderer shader`).\n - Replace `gl_FragColor = ...` with `out vec4 fragColor;` (declared before `main`) and `fragColor = ...`.\n - Replace `texture2D()` with `texture()`.\n - Default (no `#version`) uses GLSL ES 1.00 for maximum compatibility.\n\n13. Remove any `#ifdef GL_ES` / `precision` blocks — Viji handles this.\n\n14. For feedback effects that used Shadertoy's Buffer tabs, use Viji's backbuffer:\n ```glsl\n vec4 prev = texture2D(backbuffer, uv);\n ```\n `backbuffer` is auto-detected and enabled. This replaces simple Buffer A patterns.\n\n## UNIFORM QUICK REFERENCE\n\nThese are always available — do NOT declare them:\n- `u_resolution` (vec2), `u_time` (float), `u_deltaTime` (float), `u_frame` (int)\n- `u_mouse` (vec2), `u_mousePressed` (bool), `u_mouseLeft` (bool)\n- `u_audioVolume` (float), `u_audioLow/Mid/High` (float), `u_audioKick/Snare/Hat` (float)\n- `u_video` (sampler2D), `backbuffer` (sampler2D, auto-enabled if used)\n\n## PARAMETER TYPE → UNIFORM MAPPING\n\n| Directive | GLSL Type | Example |\n|---|---|---|\n| `@viji-slider` | `uniform float` | `// @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0` |\n| `@viji-number` | `uniform float` | `// @viji-number:count label:\"Count\" default:10.0 min:1.0 max:100.0` |\n| `@viji-color` | `uniform vec3` | `// @viji-color:tint label:\"Tint\" default:#00ffcc` |\n| `@viji-toggle` | `uniform bool` | `// @viji-toggle:invert label:\"Invert\" default:false` |\n| `@viji-select` | `uniform int` | `// @viji-select:mode label:\"Mode\" default:0 options:[\"A\",\"B\"]` |\n| `@viji-image` | `uniform sampler2D` | `// @viji-image:tex label:\"Texture\"` |\n| `@viji-accumulator` | `uniform float` | `// @viji-accumulator:phase rate:speed` |\n\n## CONVERSION TEMPLATE\n\n```glsl\n// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\n// @viji-accumulator:phase rate:speed\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n\n // ... converted shader logic ...\n // Use `phase` instead of `iTime`\n // Use `u_resolution` instead of `iResolution.xy`\n // Use `gl_FragCoord.xy` instead of `fragCoord`\n\n gl_FragColor = vec4(color, 1.0);\n}\n```\n\nNow convert the Shadertoy shader I provide. Return ONLY the converted Viji shader code.\nApply all rules. Do NOT use a compatibility layer — convert directly to native Viji uniforms.\n````\n\n## Usage\n\n1. Copy the entire prompt block above.\n2. Paste it into your AI assistant.\n3. After the prompt, paste the Shadertoy shader code (the `mainImage` function).\n4. The AI will return a native Viji shader scene.\n\n> [!TIP]\n> This prompt converts to **native Viji uniforms** (no `#define` compatibility layer). For a quick-and-dirty conversion using `#define` macros, see the compatibility header in [Shadertoy Compatibility](/shader/shadertoy).\n\n## Related\n\n- [Shadertoy Compatibility](/shader/shadertoy) — manual conversion guide with compatibility layer\n- [Accumulator](/shader/parameters/accumulator) — how accumulators prevent animation jumps\n- [Prompt: Shader Scenes](/ai-prompts/shader-prompt) — AI prompt for creating new shaders from scratch\n- [Shader Quick Start](/shader/quickstart) — your first Viji shader"
|
|
813
|
+
}
|
|
814
|
+
]
|
|
815
|
+
},
|
|
816
|
+
"ai-convert-threejs": {
|
|
817
|
+
"id": "ai-convert-threejs",
|
|
818
|
+
"title": "\"Convert: Three.js to Viji\"",
|
|
819
|
+
"description": "A self-contained AI prompt for converting standalone Three.js applications into Viji native scenes.",
|
|
820
|
+
"content": [
|
|
821
|
+
{
|
|
822
|
+
"type": "text",
|
|
823
|
+
"markdown": "# Convert: Three.js to Viji\n\nCopy the prompt below and paste it into your AI assistant along with the Three.js code you want to convert. The prompt contains all the rules the AI needs to produce a correct Viji native scene with Three.js.\n\n## The Prompt\n\n````\nYou are converting a standalone Three.js application into a Viji native scene.\nViji scenes run inside an OffscreenCanvas Web Worker. Apply every rule below exactly.\n\n## RULES\n\n1. ALWAYS import Three.js dynamically at the top level using `await import()`:\n ```javascript\n const THREE = await import('https://esm.sh/three@0.160.0');\n ```\n NEVER use `<script>` tags, `require()`, or static `import` statements.\n ALWAYS pin the version number in the URL.\n\n2. ALWAYS use `viji.canvas` as the renderer's canvas:\n ```javascript\n const renderer = new THREE.WebGLRenderer({ canvas: viji.canvas, antialias: true });\n renderer.setSize(viji.width, viji.height, false);\n ```\n ALWAYS pass `false` as the third argument to `setSize()` — this prevents Three.js from setting CSS styles, which would fail in the worker.\n\n3. NEVER use `requestAnimationFrame()`. Viji controls the render loop. Write all per-frame logic inside `function render(viji) { ... }` and call `renderer.render(scene, camera)` at the end.\n\n4. ALWAYS handle resize by checking `viji.width` / `viji.height` in `render()`:\n ```javascript\n let prevWidth = viji.width;\n let prevHeight = viji.height;\n\n function render(viji) {\n if (viji.width !== prevWidth || viji.height !== prevHeight) {\n renderer.setSize(viji.width, viji.height, false);\n camera.aspect = viji.width / viji.height;\n camera.updateProjectionMatrix();\n prevWidth = viji.width;\n prevHeight = viji.height;\n }\n renderer.render(scene, camera);\n }\n ```\n\n5. NEVER access `window`, `document`, `Image()`, or `localStorage`. `fetch()` IS available.\n\n6. NEVER use `window.innerWidth` / `window.innerHeight`. Use `viji.width` / `viji.height`.\n\n7. Replace hardcoded values with Viji parameters declared at the top level:\n ```javascript\n const speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\n const color = viji.color('#049ef4', { label: 'Color' });\n ```\n NEVER declare parameters inside `render()`.\n ALWAYS read via `.value`: `speed.value`, `color.value`.\n\n8. ALWAYS use `viji.deltaTime` for animation timing:\n ```javascript\n cube.rotation.y += speed.value * viji.deltaTime;\n ```\n NEVER use `clock.getDelta()` or `Date.now()`. Remove any `THREE.Clock` usage.\n\n9. Replace mouse/keyboard event listeners with Viji APIs:\n - `event.clientX` → `viji.mouse.x`\n - `event.clientY` → `viji.mouse.y`\n - Mouse buttons → `viji.mouse.leftButton`, `viji.mouse.rightButton`\n - Key presses → `viji.keyboard.isPressed('keyName')`\n\n10. `OrbitControls` and other controls that depend on DOM events will NOT work in the worker.\n For camera interaction, read `viji.mouse` and update the camera manually.\n\n11. For Three.js addons, import from the examples directory:\n ```javascript\n const { GLTFLoader } = await import('https://esm.sh/three@0.160.0/examples/jsm/loaders/GLTFLoader.js');\n const { EffectComposer } = await import('https://esm.sh/three@0.160.0/examples/jsm/postprocessing/EffectComposer.js');\n ```\n ALWAYS use the same Three.js version for addons as for the main library.\n\n12. For textures from file inputs, use Viji's image parameters:\n ```javascript\n const photo = viji.image(null, { label: 'Texture' });\n // In render():\n if (photo.value && !texture) {\n texture = new THREE.CanvasTexture(photo.value);\n material.map = texture;\n material.needsUpdate = true;\n }\n ```\n\n13. For video textures, use `viji.video`:\n ```javascript\n if (viji.video.isConnected && viji.video.currentFrame) {\n if (!videoTexture) {\n videoTexture = new THREE.CanvasTexture(viji.video.currentFrame);\n material.map = videoTexture;\n }\n videoTexture.needsUpdate = true;\n }\n ```\n\n14. NEVER allocate new objects inside `render()`. Pre-create vectors, colors, and materials at the top level.\n\n15. Remove any `window.addEventListener('resize', ...)` — resize is handled in `render()` (see rule 4).\n\n16. Remove any CSS, HTML, or DOM manipulation code. Viji scenes produce only canvas output.\n\n## PARAMETER TYPES\n\n```javascript\nviji.slider(default, { min, max, step, label, group, category }) // { value: number }\nviji.color(default, { label, group, category }) // { value: '#rrggbb' }\nviji.toggle(default, { label, group, category }) // { value: boolean }\nviji.select(default, { options: [...], label, group, category }) // { value: string }\nviji.number(default, { min, max, step, label, group, category }) // { value: number }\nviji.image(default, { label, group, category }) // { value: ImageBitmap|null }\n```\n\n## VIJI API QUICK REFERENCE\n\n- `viji.canvas` — the OffscreenCanvas (pass to Three.js renderer)\n- `viji.width`, `viji.height` — current canvas dimensions\n- `viji.time` — elapsed seconds since scene start\n- `viji.deltaTime` — seconds since last frame\n- `viji.mouse.x`, `.y`, `.isPressed`, `.leftButton`, `.rightButton`\n- `viji.keyboard.isPressed('key')`, `.lastKey`\n- `viji.audio.isConnected`, `.volume.current`, `.bands.low/mid/high`, `.beat.kick/snare/hat`\n- `viji.video.isConnected`, `.currentFrame`\n\n## TEMPLATE\n\n```javascript\nconst THREE = await import('https://esm.sh/three@0.160.0');\n\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nconst color = viji.color('#049ef4', { label: 'Color' });\n\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(60, viji.width / viji.height, 0.1, 100);\ncamera.position.set(0, 1, 3);\ncamera.lookAt(0, 0, 0);\n\nconst renderer = new THREE.WebGLRenderer({ canvas: viji.canvas, antialias: true });\nrenderer.setSize(viji.width, viji.height, false);\n\nconst geometry = new THREE.BoxGeometry();\nconst material = new THREE.MeshStandardMaterial({ color: color.value });\nconst mesh = new THREE.Mesh(geometry, material);\nscene.add(mesh);\n\nscene.add(new THREE.DirectionalLight(0xffffff, 1.5));\nscene.add(new THREE.AmbientLight(0x404040));\n\nlet prevWidth = viji.width;\nlet prevHeight = viji.height;\n\nfunction render(viji) {\n mesh.rotation.y += speed.value * viji.deltaTime;\n material.color.set(color.value);\n\n if (viji.width !== prevWidth || viji.height !== prevHeight) {\n renderer.setSize(viji.width, viji.height, false);\n camera.aspect = viji.width / viji.height;\n camera.updateProjectionMatrix();\n prevWidth = viji.width;\n prevHeight = viji.height;\n }\n\n renderer.render(scene, camera);\n}\n```\n\nNow convert the Three.js code I provide. Return ONLY the converted Viji scene code.\n````\n\n## Usage\n\n1. Copy the entire prompt block above.\n2. Paste it into your AI assistant.\n3. After the prompt, paste the Three.js code you want to convert.\n4. The AI will return a Viji-compatible native scene.\n\n> [!NOTE]\n> This prompt handles standard Three.js scenes. If the original code uses a framework (React Three Fiber, Drei, etc.), you may need to manually extract the Three.js scene setup first.\n\n## Related\n\n- [External Libraries](/native/external-libraries) — detailed guide for using Three.js and other libraries in Viji\n- [Prompt: Native Scenes](/ai-prompts/native-prompt) — AI prompt for creating new native scenes from scratch\n- [Native Quick Start](/native/quickstart) — your first Viji native scene"
|
|
824
|
+
}
|
|
825
|
+
]
|
|
826
|
+
},
|
|
827
|
+
"native-quickstart": {
|
|
828
|
+
"id": "native-quickstart",
|
|
829
|
+
"title": "Native Quick Start",
|
|
830
|
+
"description": "Build your first Viji scene with JavaScript or TypeScript and full canvas control.",
|
|
831
|
+
"content": [
|
|
832
|
+
{
|
|
833
|
+
"type": "text",
|
|
834
|
+
"markdown": "# Native Quick Start\n\nThe native renderer is the default — no directive needed. You write standard JavaScript or TypeScript with direct canvas access.\n\n## Your First Scene"
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
"type": "live-example",
|
|
838
|
+
"title": "Native — Dot Field",
|
|
839
|
+
"sceneCode": "const bg = viji.color('#0a0a2e', { label: 'Background' });\nconst dotColor = viji.color('#00ffcc', { label: 'Dot Color' });\nconst count = viji.slider(80, { min: 10, max: 200, step: 1, label: 'Dot Count' });\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\n\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n const w = viji.width;\n const h = viji.height;\n const r = Math.min(w, h) * 0.01;\n\n ctx.fillStyle = bg.value;\n ctx.fillRect(0, 0, w, h);\n\n ctx.fillStyle = dotColor.value;\n for (let i = 0; i < count.value; i++) {\n const t = viji.time * speed.value + i * 0.3;\n const x = w / 2 + Math.cos(t + i) * Math.sin(t * 0.3) * w * 0.35;\n const y = h / 2 + Math.sin(t + i * 0.7) * Math.cos(t * 0.2) * h * 0.35;\n ctx.beginPath();\n ctx.arc(x, y, r, 0, Math.PI * 2);\n ctx.fill();\n }\n}\n",
|
|
840
|
+
"sceneFile": "quickstart-native.scene.js"
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
"type": "text",
|
|
844
|
+
"markdown": "### What's Happening\n\n**Top level — runs once:**\n\n- `viji.color()` and `viji.slider()` create parameters that appear in the UI. They must be declared at the top level, not inside `render()`.\n\n**`render(viji)` — called every frame:**\n\n- `viji.useContext('2d')` returns a standard `CanvasRenderingContext2D`. Call it once; the context is cached.\n- `viji.width` and `viji.height` give the current canvas size — use them instead of hardcoded pixels.\n- `viji.time` is elapsed seconds since the scene started — use it for animation.\n- Each parameter's `.value` is read inside `render()` and updates live as the user moves sliders.\n\n> [!NOTE]\n> Parameters must be defined at the top level of your scene, not inside `render()`. They are registered once during initialization. Defining them inside `render()` would create duplicate parameters on every frame.\n\n## Scene Structure\n\nA native scene has two parts:\n\n```javascript\n// 1. Top level — initialization, parameters, state\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nlet angle = 0;\n\n// 2. render() — called every frame\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n angle += speed.value * viji.deltaTime;\n // ... draw using ctx\n}\n```\n\n- **No `setup()` function.** All initialization happens at the top level. Top-level `await` is supported for dynamic imports.\n- **`render(viji)` is the only required function.** It receives the Viji API object with canvas, timing, audio, video, parameters, and interaction data.\n\n## Canvas Context\n\nUse `viji.useContext()` to get a rendering context:\n\n```javascript\nconst ctx = viji.useContext('2d'); // Canvas 2D\nconst gl = viji.useContext('webgl'); // WebGL 1\nconst gl2 = viji.useContext('webgl2'); // WebGL 2\n```\n\nPick one and use it consistently — switching context types discards the previous one.\n\n## Essential Patterns\n\n> [!NOTE]\n> Always use `viji.width` and `viji.height` for positioning and sizing, and `viji.deltaTime` for frame-rate-independent animation. Never hardcode pixel values or assume a specific frame rate.\n\n> [!WARNING]\n> Scenes run in a Web Worker — there is no `window`, `document`, `Image()`, `localStorage`, or any DOM API. All inputs (audio, video, images) are provided through the Viji API. Note: `fetch()` IS available and can be used to load external data (JSON, etc.) from CDNs.\n\n> [!TIP]\n> Avoid allocating objects, arrays, or strings inside `render()`. Pre-allocate at the top level and reuse them:\n> ```javascript\n> // Good — pre-allocated\n> const pos = { x: 0, y: 0 };\n> function render(viji) {\n> pos.x = viji.width / 2;\n> pos.y = viji.height / 2;\n> }\n>\n> // Bad — creates a new object every frame\n> function render(viji) {\n> const pos = { x: viji.width / 2, y: viji.height / 2 };\n> }\n> ```\n\n## External Libraries\n\nTop-level `await` lets you load libraries from a CDN:\n\n```javascript\nconst THREE = await import('https://esm.sh/three@0.160.0');\n// ... set up your Three.js scene at the top level\nfunction render(viji) { /* ... */ }\n```\n\nSee [External Libraries](/native/external-libraries) for detailed patterns.\n\n## TypeScript Support\n\nThe Viji editor provides full TypeScript support with autocomplete and type checking out of the box. All Viji types — `VijiAPI`, `SliderParameter`, `AudioAPI`, `MouseAPI`, and others — are available globally without imports. Add type annotations to get full IntelliSense:\n\n```typescript\nfunction render(viji: VijiAPI) {\n const ctx = viji.useContext('2d'); // ctx is typed as CanvasRenderingContext2D\n // ... full autocomplete for viji.audio, viji.mouse, etc.\n}\n```\n\nTypeScript is optional — the same code works as plain JavaScript without the annotations."
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
"type": "live-example",
|
|
848
|
+
"title": "TypeScript Scene",
|
|
849
|
+
"sceneCode": "const speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nconst size = viji.slider(0.05, { min: 0.02, max: 0.15, step: 0.01, label: 'Size' });\nconst color = viji.color('#ff6600', { label: 'Color' });\n\nlet angle = 0;\n\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n const w = viji.width;\n const h = viji.height;\n\n angle += speed.value * viji.deltaTime;\n\n ctx.fillStyle = '#0a0a2e';\n ctx.fillRect(0, 0, w, h);\n\n const cx = w / 2 + Math.cos(angle) * w * 0.3;\n const cy = h / 2 + Math.sin(angle) * h * 0.3;\n const r = Math.min(w, h) * size.value;\n\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n ctx.fillStyle = color.value;\n ctx.fill();\n}\n",
|
|
850
|
+
"sceneFile": "quickstart-ts.scene.js"
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
"type": "text",
|
|
854
|
+
"markdown": "## Next Steps\n\n- [Canvas & Context](/native/canvas-context) — `useContext` details, 2D vs WebGL\n- [Timing](/native/timing) — `viji.time`, `viji.deltaTime`, frame counting\n- [Parameters](/native/parameters) — sliders, colors, toggles, and more\n- [Audio](/native/audio) — react to music and sound\n- [API Reference](/native/api-reference) — full list of everything available\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
855
|
+
}
|
|
856
|
+
]
|
|
857
|
+
},
|
|
858
|
+
"native-external-libs": {
|
|
859
|
+
"id": "native-external-libs",
|
|
860
|
+
"title": "External Libraries",
|
|
861
|
+
"description": "How to use Three.js, math libraries, and other ES modules in native Viji scenes.",
|
|
862
|
+
"content": [
|
|
863
|
+
{
|
|
864
|
+
"type": "text",
|
|
865
|
+
"markdown": "# External Libraries\n\nThe native renderer supports **top-level `await`**, which means you can dynamically import any ES module from a CDN directly in your scene code. No bundler, no install step — just `await import()`.\n\n> [!TIP]\n> Have an existing Three.js project? See [Convert: Three.js](/ai-prompts/convert-threejs) for a ready-to-paste AI prompt that adapts your code to Viji automatically.\n\n## Importing a Library\n\n```javascript\nconst THREE = await import('https://esm.sh/three@0.160.0');\n```\n\nThis works because Viji scene code runs in a module-like context where top-level `await` is available. The import happens once during scene initialization, before `render()` is ever called.\n\n> [!TIP]\n> [esm.sh](https://esm.sh) is a recommended CDN for ES modules. It serves any npm package as a standard ES module and handles dependency resolution automatically.\n\n## Three.js\n\nThree.js is the most common external library used with Viji. It works naturally in native mode because it accepts an external canvas, has no automatic render loop, and uses standard WebGL."
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
"type": "live-example",
|
|
869
|
+
"title": "Three.js — Lit Cube",
|
|
870
|
+
"sceneCode": "const THREE = await import('https://esm.sh/three@0.160.0');\n\nconst rotationSpeed = viji.slider(1, { min: 0.1, max: 5, label: 'Rotation Speed' });\nconst cubeColor = viji.color('#049ef4', { label: 'Cube Color' });\n\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(60, viji.width / viji.height, 0.1, 100);\ncamera.position.set(2, 1.5, 3);\ncamera.lookAt(0, 0, 0);\n\nconst renderer = new THREE.WebGLRenderer({ canvas: viji.canvas, antialias: true });\nrenderer.setSize(viji.width, viji.height, false);\n\nconst geometry = new THREE.BoxGeometry();\nconst material = new THREE.MeshStandardMaterial({ color: cubeColor.value });\nconst cube = new THREE.Mesh(geometry, material);\nscene.add(cube);\n\nconst light = new THREE.DirectionalLight(0xffffff, 1.5);\nlight.position.set(3, 4, 2);\nscene.add(light);\nscene.add(new THREE.AmbientLight(0x404040));\n\nlet prevWidth = viji.width;\nlet prevHeight = viji.height;\n\nfunction render(viji) {\n cube.rotation.x += rotationSpeed.value * viji.deltaTime * 0.5;\n cube.rotation.y += rotationSpeed.value * viji.deltaTime;\n material.color.set(cubeColor.value);\n\n if (viji.width !== prevWidth || viji.height !== prevHeight) {\n renderer.setSize(viji.width, viji.height, false);\n camera.aspect = viji.width / viji.height;\n camera.updateProjectionMatrix();\n prevWidth = viji.width;\n prevHeight = viji.height;\n }\n\n renderer.render(scene, camera);\n}\n",
|
|
871
|
+
"sceneFile": "threejs-scene.scene.js"
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
"type": "text",
|
|
875
|
+
"markdown": "### Key Integration Points\n\n**Canvas:** Pass `viji.canvas` to the Three.js renderer:\n\n```javascript\nconst renderer = new THREE.WebGLRenderer({\n canvas: viji.canvas,\n antialias: true\n});\n```\n\n**Resize:** Three.js doesn't auto-resize. Check for dimension changes in `render()`:\n\n```javascript\nlet prevWidth = viji.width;\nlet prevHeight = viji.height;\n\nfunction render(viji) {\n if (viji.width !== prevWidth || viji.height !== prevHeight) {\n renderer.setSize(viji.width, viji.height, false);\n camera.aspect = viji.width / viji.height;\n camera.updateProjectionMatrix();\n prevWidth = viji.width;\n prevHeight = viji.height;\n }\n\n renderer.render(scene, camera);\n}\n```\n\n> [!IMPORTANT]\n> Pass `false` as the third argument to `renderer.setSize()` — this prevents Three.js from setting CSS styles on the canvas, which would fail in the worker environment.\n\n**No `requestAnimationFrame`:** Viji controls the render loop. Write your animation logic inside `render(viji)` and call `renderer.render(scene, camera)` at the end.\n\n**Parameters:** All Viji parameter types work as expected:\n\n```javascript\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nconst color = viji.color('#049ef4', { label: 'Color' });\n\nfunction render(viji) {\n cube.rotation.y += speed.value * viji.deltaTime;\n material.color.set(color.value);\n renderer.render(scene, camera);\n}\n```\n\n### Three.js Addons\n\nImport addons from the Three.js examples directory:\n\n```javascript\nconst THREE = await import('https://esm.sh/three@0.160.0');\nconst { OrbitControls } = await import('https://esm.sh/three@0.160.0/examples/jsm/controls/OrbitControls.js');\nconst { GLTFLoader } = await import('https://esm.sh/three@0.160.0/examples/jsm/loaders/GLTFLoader.js');\nconst { EffectComposer } = await import('https://esm.sh/three@0.160.0/examples/jsm/postprocessing/EffectComposer.js');\n```\n\n> [!NOTE]\n> `OrbitControls` requires DOM events and will not work as expected in the worker environment. For camera interaction, use `viji.mouse` to drive the camera manually.\n\n### Video and Image Textures\n\nThree.js can consume Viji's video frames and image parameters directly:\n\n```javascript\nconst photo = viji.image(null, { label: 'Texture' });\nlet texture = null;\n\nfunction render(viji) {\n if (photo.value && !texture) {\n texture = new THREE.CanvasTexture(photo.value);\n material.map = texture;\n material.needsUpdate = true;\n }\n\n if (viji.video.isConnected && viji.video.currentFrame) {\n if (!videoTexture) {\n videoTexture = new THREE.CanvasTexture(viji.video.currentFrame);\n videoMaterial.map = videoTexture;\n }\n videoTexture.needsUpdate = true;\n }\n\n renderer.render(scene, camera);\n}\n```\n\n## Other Libraries\n\nAny library published as an ES module works the same way. Examples:\n\n**Math / utility libraries:**\n\n```javascript\nconst { vec3, mat4 } = await import('https://esm.sh/gl-matrix@3.4.3');\nconst SimplexNoise = (await import('https://esm.sh/simplex-noise@4.0.1')).createNoise3D;\n```\n\n**2D rendering libraries:**\n\n```javascript\nconst PIXI = await import('https://esm.sh/pixi.js@7.3.2');\n```\n\n> [!NOTE]\n> Some libraries expect a DOM environment and may not work in the worker context. Libraries that accept an external canvas or work with pure data (math, noise, data structures) are the best candidates.\n\n## WebGL Context\n\nWhen using a library that creates its own WebGL context (like Three.js or Pixi.js), keep in mind:\n\n- The library consumes the canvas's WebGL context. You cannot also call `viji.useContext('2d')` or `viji.useContext('webgl')` for the same canvas — pick one rendering approach.\n- CV features (face detection, hand tracking, etc.) use additional WebGL contexts internally. Browsers have a limit of 8–16 contexts. Combining a WebGL library with multiple CV features may cause context loss.\n\nSee [Canvas & Context](/native/canvas-context) for details on `useContext()`.\n\n## Tips\n\n- **Pin library versions** in the import URL (e.g., `three@0.160.0`). Unpinned versions may break when the library updates.\n- **Import once at the top level.** Never put `await import()` inside `render()` — it would re-fetch the library every frame.\n- **Check worker compatibility** before choosing a library. Libraries that require `document`, `window`, or `Image()` will not work. `fetch()` is available.\n- **Use `viji.deltaTime`** for animation instead of the library's own timer, to stay frame-rate-independent and synchronized with other Viji features.\n\n## Related\n\n- [Canvas & Context](/native/canvas-context) — `useContext('2d')`, `useContext('webgl')`, `useContext('webgl2')`\n- [Native Quick Start](/native/quickstart) — building native scenes from scratch\n- [Parameters](/native/parameters) — sliders, colors, toggles\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
876
|
+
}
|
|
877
|
+
]
|
|
878
|
+
},
|
|
879
|
+
"native-parameters": {
|
|
880
|
+
"id": "native-parameters",
|
|
881
|
+
"title": "Parameters",
|
|
882
|
+
"description": "The Viji parameter system — sliders, colors, toggles, and more for artist-controllable scene inputs.",
|
|
883
|
+
"content": [
|
|
884
|
+
{
|
|
885
|
+
"type": "text",
|
|
886
|
+
"markdown": "# Parameters\n\nParameters are the primary way to give users control over your scene. You define them at the top level of your code, and Viji renders corresponding UI controls (sliders, color pickers, toggles, etc.) in the host application. Read `.value` inside `render()` to get the current state — values update in real-time as users interact with the controls.\n\n## Parameter Types\n\n| Type | Function | Value | Use For |\n|---|---|---|---|\n| [Slider](slider/) | `viji.slider(default, config)` | `number` | Continuous numeric ranges (speed, size, opacity) |\n| [Number](number/) | `viji.number(default, config)` | `number` | Precise numeric input (counts, thresholds) |\n| [Color](color/) | `viji.color(default, config)` | `string` | Hex color values (`'#rrggbb'`) |\n| [Toggle](toggle/) | `viji.toggle(default, config)` | `boolean` | On/off switches (enable audio, show trail) |\n| [Select](select/) | `viji.select(default, config)` | `string \\| number` | Dropdown from predefined options (blend mode, shape type) |\n| [Text](text/) | `viji.text(default, config)` | `string` | Free-form text input (titles, labels) |\n| [Image](image/) | `viji.image(default, config)` | `ImageBitmap \\| null` | User-uploaded images and textures |\n\n## Basic Pattern\n\n```javascript\n// 1. Define at top level — runs once\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nconst color = viji.color('#ff6600', { label: 'Color' });\nconst mirror = viji.toggle(false, { label: 'Mirror' });\n\n// 2. Read .value in render() — updates in real-time\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n ctx.fillStyle = color.value;\n // speed.value, mirror.value, etc.\n}\n```\n\n> [!WARNING]\n> Parameters must be declared at the **top level** of your scene, never inside `render()`. They are registered once during initialization. Declaring them inside `render()` would create duplicate parameters on every frame.\n\n## Common Config Keys\n\nAll parameter types share these optional configuration keys:\n\n| Key | Type | Default | Description |\n|---|---|---|---|\n| `label` | `string` | **(required)** | Display name shown in the parameter UI |\n| `description` | `string` | — | Tooltip or help text |\n| `group` | `string` | `'general'` | Group name for organizing parameters — see [Grouping](grouping/) |\n| `category` | `ParameterCategory` | `'general'` | Controls visibility based on active capabilities — see [Categories](categories/) |\n\n## Organization\n\nAs scenes grow, you'll want to organize parameters into logical sections and control when they're visible:\n\n- **[Grouping](grouping/)** — Collect related parameters under named groups (e.g., \"animation\", \"shape\", \"audio\"). Parameters with the same `group` string appear together in the UI.\n- **[Categories](categories/)** — Tag parameters as `'general'`, `'audio'`, `'video'`, or `'interaction'` to automatically show/hide them based on what inputs are currently active.\n\n## Related\n\n- [Slider](slider/) — the most common parameter type\n- [Grouping](grouping/) — organizing parameters into named groups\n- [Categories](categories/) — visibility based on capabilities\n- [P5 Parameters](/p5/parameters) — same system in the P5 renderer\n- [Shader Parameters](/shader/parameters) — comment-directive syntax for shaders\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
887
|
+
}
|
|
888
|
+
]
|
|
889
|
+
},
|
|
890
|
+
"native-param-slider": {
|
|
891
|
+
"id": "native-param-slider",
|
|
892
|
+
"title": "Slider Parameter",
|
|
893
|
+
"description": "Create a numeric slider control with configurable range, step size, and grouping.",
|
|
894
|
+
"content": [
|
|
895
|
+
{
|
|
896
|
+
"type": "text",
|
|
897
|
+
"markdown": "# viji.slider()\n\n```\nslider(defaultValue: number, config: SliderConfig): SliderParameter\n```\n\nCreates a numeric slider parameter that the host application renders as a draggable slider control. The artist defines the slider at the top level of the scene, and reads its current value inside `render()`.\n\n## Parameters\n\n| Name | Type | Required | Default | Description |\n|------|------|----------|---------|-------------|\n| `defaultValue` | `number` | Yes | — | Initial value of the slider |\n| `config.min` | `number` | No | `0` | Minimum allowed value |\n| `config.max` | `number` | No | `100` | Maximum allowed value |\n| `config.step` | `number` | No | `1` | Increment between values |\n| `config.label` | `string` | Yes | — | Display name shown in the parameter UI |\n| `config.description` | `string` | No | — | Tooltip or help text |\n| `config.group` | `string` | No | `'general'` | Group name for organizing parameters — see [Grouping](../grouping/) |\n| `config.category` | `ParameterCategory` | No | `'general'` | Controls visibility based on capabilities — see [Categories](../categories/) |\n\n## Return Value\n\nReturns a `SliderParameter` object:\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `value` | `number` | Current slider value. Updates in real-time when the user moves the slider. |\n| `min` | `number` | Minimum value |\n| `max` | `number` | Maximum value |\n| `step` | `number` | Step increment |\n| `label` | `string` | Display label |\n| `description` | `string \\| undefined` | Description text |\n| `group` | `string` | Group name |\n| `category` | `ParameterCategory` | Parameter category |\n\n## Usage\n\nDefine sliders at the **top level** of your scene code, before the `render()` function. Read `.value` inside `render()` to get the current value.\n\n```javascript\nconst radius = viji.slider(50, {\n min: 10,\n max: 200,\n step: 1,\n label: 'Circle Radius',\n group: 'shape'\n});\n\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n ctx.clearRect(0, 0, viji.width, viji.height);\n ctx.beginPath();\n ctx.arc(viji.width / 2, viji.height / 2, radius.value, 0, Math.PI * 2);\n ctx.fill();\n}\n```\n\n> [!NOTE]\n> Parameters must be defined at the top level, not inside `render()`. They are registered once during scene initialization. Defining them inside `render()` would create duplicate parameters on every frame."
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
"type": "live-example",
|
|
901
|
+
"title": "Basic Slider",
|
|
902
|
+
"sceneCode": "const radius = viji.slider(0.15, {\n min: 0.02,\n max: 0.4,\n step: 0.01,\n label: 'Radius',\n description: 'Size of the circle relative to canvas',\n group: 'shape'\n});\n\nconst speed = viji.slider(1.0, {\n min: 0,\n max: 3.0,\n step: 0.1,\n label: 'Pulse Speed',\n description: 'Speed of the breathing animation',\n group: 'animation'\n});\n\nconst fillColor = viji.color('#4ecdc4', {\n label: 'Fill Color',\n group: 'style'\n});\n\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n const w = viji.width;\n const h = viji.height;\n\n ctx.fillStyle = '#1a1a2e';\n ctx.fillRect(0, 0, w, h);\n\n const baseRadius = radius.value * Math.min(w, h);\n const pulse = 1 + 0.15 * Math.sin(viji.time * speed.value * Math.PI * 2);\n const r = baseRadius * pulse;\n\n ctx.beginPath();\n ctx.arc(w / 2, h / 2, r, 0, Math.PI * 2);\n ctx.fillStyle = fillColor.value;\n ctx.fill();\n\n ctx.beginPath();\n ctx.arc(w / 2, h / 2, r, 0, Math.PI * 2);\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';\n ctx.lineWidth = Math.max(1, Math.min(w, h) * 0.003);\n ctx.stroke();\n}\n",
|
|
903
|
+
"sceneFile": "slider-basic.scene.js"
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
"type": "text",
|
|
907
|
+
"markdown": "## Resolution-Agnostic Sizing\n\nWhen using a slider to control sizes or positions, always scale relative to `viji.width` and `viji.height` so the scene looks the same at any resolution. A slider value of `0` to `1` works well as a normalized proportion:\n\n```javascript\nconst size = viji.slider(0.15, {\n min: 0.02,\n max: 0.5,\n step: 0.01,\n label: 'Size',\n group: 'shape'\n});\n\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n const pixelSize = size.value * Math.min(viji.width, viji.height);\n // pixelSize adapts automatically to any resolution\n}\n```\n\n## Related\n\n- [Color](../color/) — color picker parameter\n- [Number](../number/) — numeric input without a slider track\n- [Select](../select/) — dropdown selection from predefined options\n- [Grouping](../grouping/) — organizing parameters into named groups\n- [Categories](../categories/) — controlling parameter visibility based on capabilities\n- [Shader Slider](/shader/parameters/slider) — equivalent for the Shader renderer"
|
|
908
|
+
}
|
|
909
|
+
]
|
|
910
|
+
},
|
|
911
|
+
"native-param-grouping": {
|
|
912
|
+
"id": "native-param-grouping",
|
|
913
|
+
"title": "Grouping Parameters",
|
|
914
|
+
"description": "Organize parameters into named groups so related controls appear together in the UI.",
|
|
915
|
+
"content": [
|
|
916
|
+
{
|
|
917
|
+
"type": "text",
|
|
918
|
+
"markdown": "# Grouping Parameters\n\nAs your scene grows, the parameter list can become unwieldy. Groups let you collect related parameters under a shared heading in the host UI. Parameters with the same `group` string appear together, regardless of declaration order.\n\n## Usage\n\nSet the `group` config key on any parameter:\n\n```javascript\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed', group: 'animation' });\nconst trail = viji.toggle(true, { label: 'Trail', group: 'animation' });\n\nconst size = viji.slider(0.05, { min: 0.01, max: 0.15, step: 0.01, label: 'Size', group: 'shape' });\nconst color = viji.color('#ff6600', { label: 'Color', group: 'shape' });\n```\n\nThe host application receives the group names and renders them as collapsible sections or visual separators.\n\n## How It Works\n\n- The `group` key is a **freeform string** — any name works. There is no fixed list.\n- If you omit `group`, the parameter defaults to `'general'`.\n- Parameters with the same `group` value are collected together when sent to the host UI.\n- Group names are displayed as-is, so use readable names like `'animation'`, `'visual style'`, `'audio settings'`.\n- Declaration order within a group is preserved.\n\n## Live Example\n\nThis scene uses two groups: \"animation\" (speed and trail toggle) and \"shape\" (size, color, and shape type):"
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
"type": "live-example",
|
|
922
|
+
"title": "Grouped Parameters",
|
|
923
|
+
"sceneCode": "const speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed', group: 'animation' });\nconst trail = viji.toggle(true, { label: 'Trail', group: 'animation' });\n\nconst size = viji.slider(0.05, { min: 0.01, max: 0.15, step: 0.01, label: 'Size', group: 'shape' });\nconst color = viji.color('#ff6600', { label: 'Color', group: 'shape' });\nconst sides = viji.select('circle', { options: ['circle', 'square'], label: 'Shape', group: 'shape' });\n\nlet angle = 0;\n\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n const w = viji.width;\n const h = viji.height;\n\n if (trail.value) {\n ctx.fillStyle = 'rgba(10, 10, 30, 0.1)';\n ctx.fillRect(0, 0, w, h);\n } else {\n ctx.clearRect(0, 0, w, h);\n ctx.fillStyle = '#0a0a1e';\n ctx.fillRect(0, 0, w, h);\n }\n\n angle += speed.value * viji.deltaTime;\n\n const cx = w / 2 + Math.cos(angle) * w * 0.3;\n const cy = h / 2 + Math.sin(angle) * h * 0.3;\n const r = Math.min(w, h) * size.value;\n\n ctx.fillStyle = color.value;\n if (sides.value === 'circle') {\n ctx.beginPath();\n ctx.arc(cx, cy, r, 0, Math.PI * 2);\n ctx.fill();\n } else {\n ctx.fillRect(cx - r, cy - r, r * 2, r * 2);\n }\n}\n",
|
|
924
|
+
"sceneFile": "grouping-demo.scene.js"
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
"type": "text",
|
|
928
|
+
"markdown": "## Mixing Types\n\nGroups can contain any mix of parameter types — sliders, colors, toggles, selects, and more. There is no restriction on which types can share a group:\n\n```javascript\nconst brightness = viji.slider(1, { min: 0, max: 2, label: 'Brightness', group: 'visual' });\nconst tint = viji.color('#ffffff', { label: 'Tint', group: 'visual' });\nconst showGrid = viji.toggle(false, { label: 'Grid Overlay', group: 'visual' });\nconst blendMode = viji.select('normal', { options: ['normal', 'add', 'multiply'], label: 'Blend', group: 'visual' });\n```\n\n## Groups and Categories\n\n`group` and `category` are independent. A group organizes the visual layout; a category controls visibility based on active capabilities. You can combine them:\n\n```javascript\nconst volume = viji.slider(1, {\n label: 'Volume Scale',\n group: 'audio settings',\n category: 'audio'\n});\n```\n\nSee [Categories](../categories/) for details on how `category` controls parameter visibility.\n\n## Related\n\n- [Parameters Overview](../) — all parameter types and the basic pattern\n- [Categories](../categories/) — visibility based on active capabilities\n- [Slider](../slider/) — the most common parameter type\n- [P5 Grouping](/p5/parameters/grouping) — same concept in the P5 renderer\n- [Shader Grouping](/shader/parameters/grouping) — same concept with `@viji-*` directives"
|
|
929
|
+
}
|
|
930
|
+
]
|
|
931
|
+
},
|
|
932
|
+
"native-param-categories": {
|
|
933
|
+
"id": "native-param-categories",
|
|
934
|
+
"title": "Parameter Categories",
|
|
935
|
+
"description": "Control parameter visibility based on active capabilities like audio, video, and interaction.",
|
|
936
|
+
"content": [
|
|
937
|
+
{
|
|
938
|
+
"type": "text",
|
|
939
|
+
"markdown": "# Parameter Categories\n\nCategories let you tag parameters so they're only visible when the corresponding capability is active. An \"Audio Pulse\" slider is useless if no audio source is connected — with `category: 'audio'`, it automatically appears when audio is available and hides when it's not.\n\n## The Four Categories\n\n| Category | Visible When | Use For |\n|---|---|---|\n| `'general'` | Always | Colors, sizes, speeds, shapes — anything that works without external input |\n| `'audio'` | Audio source is connected | Volume scaling, beat reactivity, frequency controls |\n| `'video'` | Video/camera source is connected | Video opacity, CV sensitivity, segmentation controls |\n| `'interaction'` | User interaction is enabled | Mouse effects, keyboard bindings, touch sensitivity |\n\n## Usage\n\nSet the `category` config key on any parameter:\n\n```javascript\nconst baseColor = viji.color('#4488ff', { label: 'Base Color', category: 'general' });\nconst pulseAmount = viji.slider(0.3, { min: 0, max: 1, label: 'Audio Pulse', category: 'audio' });\nconst showMouse = viji.toggle(true, { label: 'Mouse Trail', category: 'interaction' });\n```\n\n- `baseColor` is always visible — it has no external dependency.\n- `pulseAmount` only appears when the host connects an audio source.\n- `showMouse` only appears when user interaction is enabled.\n\nIf you omit `category`, it defaults to `'general'` (always visible).\n\n## How It Works\n\n1. The artist sets `category` on each parameter during scene initialization.\n2. When the host application requests parameters, Viji filters them based on the current `CoreCapabilities`:\n - `hasAudio` — is an audio stream connected?\n - `hasVideo` — is a video/camera stream connected?\n - `hasInteraction` — is user interaction enabled?\n - `hasGeneral` — always `true`.\n3. Only parameters matching active capabilities are sent to the UI.\n\n> [!NOTE]\n> Categories filter at both the **group level** and the **individual parameter level**. If a group's category doesn't match, the entire group is hidden. If individual parameters within a visible group have non-matching categories, those parameters are hidden while the group remains visible.\n\n## Live Example\n\nThis scene has parameters in three categories — `general` (always visible), `audio` (needs audio), and `interaction` (needs mouse):"
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
"type": "live-example",
|
|
943
|
+
"title": "Parameter Categories",
|
|
944
|
+
"sceneCode": "const baseColor = viji.color('#4488ff', { label: 'Base Color', category: 'general' });\nconst pulseAmount = viji.slider(0.3, { min: 0, max: 1, step: 0.01, label: 'Audio Pulse', category: 'audio' });\nconst showMouse = viji.toggle(true, { label: 'Mouse Trail', category: 'interaction' });\n\nlet angle = 0;\nlet mouseTrailX = 0;\nlet mouseTrailY = 0;\n\nfunction render(viji) {\n const ctx = viji.useContext('2d');\n const w = viji.width;\n const h = viji.height;\n\n ctx.fillStyle = 'rgba(10, 10, 30, 0.15)';\n ctx.fillRect(0, 0, w, h);\n\n angle += viji.deltaTime;\n\n const r = parseInt(baseColor.value.slice(1, 3), 16);\n const g = parseInt(baseColor.value.slice(3, 5), 16);\n const b = parseInt(baseColor.value.slice(5, 7), 16);\n\n let pulse = 0;\n if (viji.audio.isConnected) {\n pulse = viji.audio.volume.current * pulseAmount.value;\n }\n\n const baseR = Math.min(w, h) * (0.1 + pulse * 0.15);\n const cx = w / 2 + Math.cos(angle) * w * 0.2;\n const cy = h / 2 + Math.sin(angle * 0.7) * h * 0.2;\n\n ctx.beginPath();\n ctx.arc(cx, cy, baseR, 0, Math.PI * 2);\n ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;\n ctx.fill();\n\n if (showMouse.value && viji.mouse.isInCanvas) {\n mouseTrailX += (viji.mouse.x - mouseTrailX) * 0.1;\n mouseTrailY += (viji.mouse.y - mouseTrailY) * 0.1;\n ctx.beginPath();\n ctx.arc(mouseTrailX, mouseTrailY, Math.min(w, h) * 0.02, 0, Math.PI * 2);\n ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';\n ctx.fill();\n }\n}\n",
|
|
945
|
+
"sceneFile": "categories-demo.scene.js"
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
"type": "text",
|
|
949
|
+
"markdown": "## Design Guidelines\n\n- **Default to `'general'`** unless the parameter genuinely depends on an external input.\n- **Use `'audio'` for parameters that only make sense with sound** — beat sensitivity, frequency scaling, audio decay.\n- **Use `'video'` for parameters tied to camera/video** — segmentation threshold, face tracking sensitivity.\n- **Use `'interaction'` for parameters that need user input** — mouse effect radius, keyboard shortcut configuration.\n- **Don't use categories as a replacement for grouping.** Categories control *visibility*; groups control *layout*. Use both when appropriate.\n\n## Categories and Groups\n\n`category` and `group` are orthogonal. A parameter in group `'effects'` with category `'audio'` will appear under the \"effects\" group heading, but only when audio is connected:\n\n```javascript\nconst bassReact = viji.slider(0.5, {\n label: 'Bass Reactivity',\n group: 'effects',\n category: 'audio'\n});\n\nconst colorShift = viji.slider(0.2, {\n label: 'Color Shift',\n group: 'effects',\n category: 'general'\n});\n```\n\nBoth parameters appear in the \"effects\" group, but `bassReact` only shows when audio is active.\n\n## Related\n\n- [Parameters Overview](../) — all parameter types and the basic pattern\n- [Grouping](../grouping/) — organizing parameters into named groups\n- [P5 Categories](/p5/parameters/categories) — same concept in the P5 renderer\n- [Shader Categories](/shader/parameters/categories) — same concept with `@viji-*` directives\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
950
|
+
}
|
|
951
|
+
]
|
|
952
|
+
},
|
|
953
|
+
"p5-quickstart": {
|
|
954
|
+
"id": "p5-quickstart",
|
|
955
|
+
"title": "P5.js Quick Start",
|
|
956
|
+
"description": "Build your first Viji scene using the familiar P5.js creative coding API.",
|
|
957
|
+
"content": [
|
|
958
|
+
{
|
|
959
|
+
"type": "text",
|
|
960
|
+
"markdown": "# P5.js Quick Start\n\nThe P5.js renderer gives you the familiar Processing/P5.js drawing API. Viji loads P5.js automatically — no installation needed.\n\n> [!IMPORTANT]\n> P5 and shader scenes must declare their renderer type as the first comment:\n> ```\n> // @renderer p5\n> ```\n> Without this directive, the scene defaults to the native renderer.\n\n## Your First Scene"
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
"type": "live-example",
|
|
964
|
+
"title": "P5 — Rainbow Trail",
|
|
965
|
+
"sceneCode": "// @renderer p5\n\nconst trailLength = viji.slider(40, { min: 5, max: 100, step: 1, label: 'Trail Length' });\nconst hueSpeed = viji.slider(30, { min: 5, max: 100, label: 'Hue Speed' });\n\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB, 360, 100, 100, 100);\n}\n\nfunction render(viji, p5) {\n p5.background(0, 0, 10, 15);\n\n for (let i = 0; i < trailLength.value; i++) {\n const t = viji.time - i * 0.02;\n const x = viji.width / 2 + p5.cos(t * 1.5) * viji.width * 0.3;\n const y = viji.height / 2 + p5.sin(t * 2.0) * viji.height * 0.25;\n const hue = (viji.time * hueSpeed.value + i * 3) % 360;\n const size = p5.map(i, 0, trailLength.value, viji.width * 0.04, viji.width * 0.005);\n\n p5.noStroke();\n p5.fill(hue, 80, 100, p5.map(i, 0, trailLength.value, 100, 0));\n p5.circle(x, y, size);\n }\n}\n",
|
|
966
|
+
"sceneFile": "quickstart-p5.scene.js"
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
"type": "text",
|
|
970
|
+
"markdown": "### What's Happening\n\n**Top level — runs once:**\n\n- `// @renderer p5` tells Viji to use the P5 renderer.\n- `viji.slider()` creates UI parameters — declared at the top level, read via `.value` in `render()`.\n\n**`setup(viji, p5)` — optional, runs once:**\n\n- Use for one-time configuration like `p5.colorMode()`. If you don't need it, omit it entirely.\n\n**`render(viji, p5)` — called every frame:**\n\n- `p5` is a full P5.js instance in **instance mode** — all P5 functions require the `p5.` prefix.\n- `viji.width` and `viji.height` give the canvas size — use them for resolution-agnostic positioning.\n- `viji.time` is elapsed seconds — use it for animation.\n\n> [!NOTE]\n> Parameters must be defined at the top level of your scene, not inside `render()`. They are registered once during initialization. Defining them inside `render()` would create duplicate parameters on every frame.\n\n## Scene Structure\n\n```javascript\n// @renderer p5\n\n// 1. Top level — parameters and state\nconst size = viji.slider(50, { min: 10, max: 200, label: 'Size' });\n\n// 2. setup() — optional one-time config\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB);\n}\n\n// 3. render() — called every frame\nfunction render(viji, p5) {\n p5.background(0);\n p5.circle(viji.width / 2, viji.height / 2, size.value);\n}\n```\n\n- **`render(viji, p5)` is required.** It replaces P5's `draw()`.\n- **`setup(viji, p5)` is optional.** Use it for one-time configuration.\n- **No `createCanvas()`.** The canvas is created and managed by Viji.\n- **No `preload()`.** Use `viji.image()` parameters or `fetch()` in `setup()`.\n\n## Instance Mode\n\n> [!WARNING]\n> Viji uses P5 in **instance mode**. All P5 functions require the `p5.` prefix:\n> ```javascript\n> // Correct\n> p5.background(0);\n> p5.circle(viji.width / 2, viji.height / 2, 100);\n>\n> // Wrong — will throw ReferenceError\n> background(0);\n> circle(width / 2, height / 2, 100);\n> ```\n\n## Input and Interaction\n\nP5's built-in input globals (`mouseX`, `mouseY`, `keyIsPressed`, etc.) are not updated in the worker environment. Use the Viji API instead:\n\n```javascript\nfunction render(viji, p5) {\n if (viji.mouse.isPressed) {\n p5.circle(viji.mouse.x, viji.mouse.y, 20);\n }\n}\n```\n\n## Essential Patterns\n\n> [!NOTE]\n> Always use `viji.width` and `viji.height` for positioning and sizing, and `viji.deltaTime` for frame-rate-independent animation. Never hardcode pixel values or assume a specific frame rate.\n\n> [!WARNING]\n> Scenes run in a Web Worker — there is no `window`, `document`, `Image()`, `localStorage`, or any DOM API. All inputs (audio, video, images) are provided through the Viji API. Note: `fetch()` IS available and can be used to load external data (JSON, etc.) from CDNs.\n\n> [!TIP]\n> Avoid allocating objects, arrays, or strings inside `render()`. Pre-allocate at the top level and reuse them.\n\n## Converting Existing Sketches\n\nIf you have existing P5.js sketches, see [Converting P5 Sketches](/p5/converting-sketches) for a step-by-step migration guide. Key differences: `draw()` → `render()`, instance mode, no `createCanvas()`, Viji APIs for input.\n\n## Next Steps\n\n- [Scene Structure](/p5/scene-structure) — `setup()`, `render()`, and lifecycle details\n- [Drawing with P5](/p5/drawing) — P5 drawing functions in Viji\n- [Converting P5 Sketches](/p5/converting-sketches) — migrate existing sketches\n- [Parameters](/p5/parameters) — sliders, colors, toggles, and more\n- [Audio](/p5/audio) — react to music and sound\n- [API Reference](/p5/api-reference) — full list of everything available\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
971
|
+
}
|
|
972
|
+
]
|
|
973
|
+
},
|
|
974
|
+
"p5-converting": {
|
|
975
|
+
"id": "p5-converting",
|
|
976
|
+
"title": "Converting P5 Sketches",
|
|
977
|
+
"description": "Step-by-step guide to converting standard P5.js sketches into Viji scenes.",
|
|
978
|
+
"content": [
|
|
979
|
+
{
|
|
980
|
+
"type": "text",
|
|
981
|
+
"markdown": "# Converting P5 Sketches\n\nThis guide shows how to take any standard P5.js sketch and convert it into a Viji scene. The changes are mechanical — once you learn the pattern, converting takes a few minutes.\n\n> [!TIP]\n> Want an AI to do it for you? See [Convert: P5 Sketches](/ai-prompts/convert-p5) for a ready-to-paste prompt that applies all the rules below automatically.\n\n## Quick Reference\n\n| Standard P5.js | Viji-P5 |\n|---|---|\n| `function setup() { ... }` | `function setup(viji, p5) { ... }` |\n| `function draw() { ... }` | `function render(viji, p5) { ... }` |\n| `createCanvas(800, 600)` | Remove — canvas is provided |\n| `background(0)` | `p5.background(0)` |\n| `ellipse(x, y, d)` | `p5.ellipse(x, y, d)` |\n| `mouseX`, `mouseY` | `viji.mouse.x`, `viji.mouse.y` |\n| `keyIsPressed` | `viji.keyboard.isPressed('a')` |\n| `width`, `height` | `viji.width`, `viji.height` |\n| `frameRate(30)` | Remove — host controls frame rate |\n| `preload()` | Remove — use `viji.image()` or `fetch()` in `setup()` |\n| `save()` / `saveCanvas()` | Remove — host-side `captureFrame()` |\n| `loadImage('url')` | `viji.image(null, { label: 'Image' })` |\n\n## Step by Step\n\n### 1. Add the renderer directive\n\nAdd `// @renderer p5` as the very first line:\n\n```javascript\n// @renderer p5\n```\n\n> [!IMPORTANT]\n> Without `// @renderer p5`, the scene defaults to the native renderer and the `p5` parameter will be `undefined`.\n\n### 2. Rename `draw()` to `render(viji, p5)`\n\nStandard P5:\n```javascript\nfunction draw() {\n background(0);\n ellipse(width / 2, height / 2, 100);\n}\n```\n\nViji-P5:\n```javascript\nfunction render(viji, p5) {\n p5.background(0);\n p5.ellipse(viji.width / 2, viji.height / 2, 100);\n}\n```\n\nBoth `viji` and `p5` are required parameters. `viji` gives access to the Viji API; `p5` is the P5.js instance.\n\n### 3. Add the `p5.` prefix to all P5 functions\n\n> [!WARNING]\n> Viji uses P5 in **instance mode**. Every P5 function and constant needs the `p5.` prefix. This is the most common source of errors during conversion.\n\n```javascript\n// Standard P5.js (global mode)\ncolorMode(HSB);\nfill(255, 80, 100);\nrect(10, 10, 50, 50);\nlet v = createVector(1, 0);\n\n// Viji-P5 (instance mode)\np5.colorMode(p5.HSB);\np5.fill(255, 80, 100);\np5.rect(10, 10, 50, 50);\nlet v = p5.createVector(1, 0);\n```\n\nThis applies to constants too: `PI` → `p5.PI`, `TWO_PI` → `p5.TWO_PI`, `HALF_PI` → `p5.HALF_PI`, `HSB` → `p5.HSB`, `WEBGL` → `p5.WEBGL`.\n\n### 4. Remove `createCanvas()`\n\nViji creates and manages the canvas for you. Remove any `createCanvas()` call:\n\n```javascript\n// Standard P5.js\nfunction setup() {\n createCanvas(800, 600);\n}\n\n// Viji-P5 — no createCanvas() needed\nfunction setup(viji, p5) {\n p5.colorMode(p5.HSB);\n}\n```\n\nFor resolution-agnostic sizing, use `viji.width` and `viji.height` instead of hardcoded values.\n\n### 5. Replace P5 input globals with Viji APIs\n\nP5's built-in input variables (`mouseX`, `mouseY`, `keyIsPressed`, etc.) are not available in the worker environment. Use the Viji API instead:\n\n```javascript\n// Standard P5.js\nfunction draw() {\n if (mouseIsPressed) {\n ellipse(mouseX, mouseY, 50);\n }\n if (keyIsPressed && key === 'r') {\n background(255, 0, 0);\n }\n}\n\n// Viji-P5\nfunction render(viji, p5) {\n if (viji.mouse.isPressed) {\n p5.ellipse(viji.mouse.x, viji.mouse.y, 50);\n }\n if (viji.keyboard.isPressed('r')) {\n p5.background(255, 0, 0);\n }\n}\n```\n\n### 6. Remove event callbacks\n\nP5 event callbacks (`mousePressed()`, `mouseDragged()`, `keyPressed()`, etc.) do not work in the worker environment. Check state in `render()` instead:\n\n```javascript\n// Standard P5.js\nfunction mousePressed() {\n particles.push(new Particle(mouseX, mouseY));\n}\n\n// Viji-P5 — track state manually\nlet wasPressed = false;\n\nfunction render(viji, p5) {\n if (viji.mouse.leftButton && !wasPressed) {\n particles.push(new Particle(viji.mouse.x, viji.mouse.y));\n }\n wasPressed = viji.mouse.leftButton;\n}\n```\n\n### 7. Replace `preload()` and `loadImage()`\n\nThere is no `preload()` phase in Viji. For images, use Viji's image parameter or `fetch()` in `setup()`:\n\n```javascript\n// Standard P5.js\nlet img;\nfunction preload() {\n img = loadImage('photo.jpg');\n}\nfunction draw() {\n image(img, 0, 0);\n}\n\n// Viji-P5 — use image parameter\nconst photo = viji.image(null, { label: 'Photo' });\n\nfunction render(viji, p5) {\n if (photo.value) {\n p5.image(photo.p5, 0, 0, viji.width, viji.height);\n }\n}\n```\n\n> [!NOTE]\n> Use `photo.p5` (not `photo.value`) when passing images to P5 drawing functions like `p5.image()`. The `.p5` property provides a P5-compatible wrapper around the raw image data.\n\nFor JSON or text data, use `fetch()` in an async `setup()`:\n\n```javascript\nlet data = null;\n\nasync function setup(viji, p5) {\n const response = await fetch('https://cdn.example.com/data.json');\n data = await response.json();\n}\n```\n\n### 8. Replace `save()` and `frameRate()`\n\nThese host-level concerns are handled outside the scene:\n\n- **Saving frames**: The host application uses `core.captureFrame()`.\n- **Frame rate**: The host controls it via `core.setFrameRate()`.\n\nSimply remove these calls from your scene code.\n\n## Complete Conversion Example\n\nHere is the same scene implemented both ways, followed by the live Viji version:\n\n**Standard P5.js:**\n\n```javascript\nfunction setup() {\n createCanvas(400, 400);\n colorMode(HSB, 360, 100, 100, 100);\n}\n\nfunction draw() {\n background(0, 0, 10);\n let count = 8;\n let radius = 120;\n for (let i = 0; i < count; i++) {\n let a = frameCount * 0.02 + (i / count) * TWO_PI;\n let x = width / 2 + cos(a) * radius;\n let y = height / 2 + sin(a) * radius;\n noStroke();\n fill(255, 150, 0);\n circle(x, y, 16);\n }\n}\n```\n\n**Converted Viji-P5:**"
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
"type": "live-example",
|
|
985
|
+
"title": "Converted Sketch — Orbiting Dots",
|
|
986
|
+
"sceneCode": "// @renderer p5\n\nconst speed = viji.slider(2, { min: 0.5, max: 8, label: 'Speed' });\nconst count = viji.slider(8, { min: 3, max: 20, step: 1, label: 'Count' });\nconst dotColor = viji.color('#ff6600', { label: 'Color' });\n\nlet angle = 0;\n\nfunction render(viji, p5) {\n angle += speed.value * viji.deltaTime;\n\n p5.background(10);\n\n const cx = viji.width / 2;\n const cy = viji.height / 2;\n const radius = Math.min(viji.width, viji.height) * 0.3;\n const dotSize = Math.min(viji.width, viji.height) * 0.04;\n\n const r = parseInt(dotColor.value.slice(1, 3), 16);\n const g = parseInt(dotColor.value.slice(3, 5), 16);\n const b = parseInt(dotColor.value.slice(5, 7), 16);\n\n for (let i = 0; i < count.value; i++) {\n const a = angle + (i / count.value) * p5.TWO_PI;\n const x = cx + p5.cos(a) * radius;\n const y = cy + p5.sin(a) * radius;\n\n p5.noStroke();\n p5.fill(r, g, b);\n p5.circle(x, y, dotSize);\n }\n}\n",
|
|
987
|
+
"sceneFile": "converted-sketch.scene.js"
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
"type": "text",
|
|
991
|
+
"markdown": "Key changes made:\n\n1. Added `// @renderer p5` at the top.\n2. Renamed `draw()` → `render(viji, p5)`, added `setup(viji, p5)`.\n3. Prefixed all P5 functions with `p5.`.\n4. Removed `createCanvas()`.\n5. Replaced hardcoded `400` and `120` with `viji.width`, `viji.height`, and proportional math.\n6. Replaced `frameCount * 0.02` with a `deltaTime`-based accumulator for frame-rate-independent animation.\n7. Extracted the hardcoded color and count into Viji parameters so they become live controls.\n\n## What Doesn't Work\n\nThese P5 features are unavailable in the worker environment:\n\n| Feature | Alternative |\n|---|---|\n| `p5.dom` (sliders, buttons) | Use Viji parameters (`viji.slider()`, `viji.toggle()`, etc.) |\n| `p5.sound` | Use Viji audio API (`viji.audio.*`) |\n| `loadImage()`, `loadFont()`, `loadJSON()` | `viji.image()` parameter or `fetch()` in `setup()` |\n| `save()`, `saveCanvas()`, `saveFrames()` | Host-side `core.captureFrame()` |\n| `createCapture()`, `createVideo()` | Use Viji video API (`viji.video.*`) |\n| `cursor()`, `noCursor()` | Not available in workers |\n| `fullscreen()` | Host-side concern |\n| `frameRate()` | Host-side `core.setFrameRate()` |\n| `mousePressed()`, `keyPressed()`, etc. | Check state in `render()` via Viji APIs |\n\n## Tips\n\n- **Start with `setup()` and `render()`.** Get the basic structure right first, then fix individual function calls.\n- **Search and replace `p5.` prefix.** Most editors support regex — replace `\\b(background|fill|stroke|rect|ellipse|circle|...)\\(` with `p5.$1(`.\n- **Use `viji.width` / `viji.height`** everywhere instead of hardcoded dimensions. This makes the scene resolution-agnostic.\n- **Convert animation timing.** Replace `frameCount`-based animation with `viji.time` or `viji.deltaTime` accumulators for frame-rate independence.\n- **Test incrementally.** Convert the structure first, then one feature at a time.\n\n## Related\n\n- [P5 Quick Start](/p5/quickstart) — building P5 scenes from scratch in Viji\n- [Drawing with P5](/p5/drawing) — P5 drawing functions in the Viji environment\n- [Parameters](/p5/parameters) — sliders, colors, toggles, images\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
992
|
+
}
|
|
993
|
+
]
|
|
994
|
+
},
|
|
995
|
+
"p5-parameters": {
|
|
996
|
+
"id": "p5-parameters",
|
|
997
|
+
"title": "Parameters",
|
|
998
|
+
"description": "The Viji parameter system in P5 scenes — sliders, colors, toggles, and more for artist-controllable inputs.",
|
|
999
|
+
"content": [
|
|
1000
|
+
{
|
|
1001
|
+
"type": "text",
|
|
1002
|
+
"markdown": "# Parameters\n\nParameters give users real-time control over your P5 scene. Define them at the top level, and Viji renders corresponding UI controls in the host application. Read `.value` inside `render()` to get the current state.\n\n## Parameter Types\n\n| Type | Function | Value | Use For |\n|---|---|---|---|\n| [Slider](slider/) | `viji.slider(default, config)` | `number` | Continuous numeric ranges (speed, size, opacity) |\n| [Number](number/) | `viji.number(default, config)` | `number` | Precise numeric input (counts, thresholds) |\n| [Color](color/) | `viji.color(default, config)` | `string` | Hex color values (`'#rrggbb'`) |\n| [Toggle](toggle/) | `viji.toggle(default, config)` | `boolean` | On/off switches (enable audio, show trail) |\n| [Select](select/) | `viji.select(default, config)` | `string \\| number` | Dropdown from predefined options (blend mode, shape type) |\n| [Text](text/) | `viji.text(default, config)` | `string` | Free-form text input (titles, labels) |\n| [Image](image/) | `viji.image(default, config)` | `ImageBitmap \\| null` | User-uploaded images and textures |\n\n## Basic Pattern\n\n```javascript\n// @renderer p5\n\n// 1. Define at top level — runs once\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed' });\nconst color = viji.color('#ff6600', { label: 'Color' });\nconst mirror = viji.toggle(false, { label: 'Mirror' });\n\n// 2. Read .value in render() — updates in real-time\nfunction render(viji, p5) {\n const r = parseInt(color.value.slice(1, 3), 16);\n const g = parseInt(color.value.slice(3, 5), 16);\n const b = parseInt(color.value.slice(5, 7), 16);\n p5.fill(r, g, b);\n // speed.value, mirror.value, etc.\n}\n```\n\n> [!WARNING]\n> Parameters must be declared at the **top level** of your scene, never inside `render()` or `setup()`. They are registered once during initialization. Declaring them inside `render()` would create duplicate parameters on every frame.\n\n## Image Parameters in P5\n\nWhen using `viji.image()` with P5 drawing functions, use the `.p5` property instead of `.value`:\n\n```javascript\nconst photo = viji.image(null, { label: 'Photo' });\n\nfunction render(viji, p5) {\n if (photo.value) {\n p5.image(photo.p5, 0, 0, viji.width, viji.height);\n }\n}\n```\n\nThe `.p5` property wraps the raw image data in a P5-compatible object. Use `.value` to check if an image is loaded, and `.p5` when passing to P5 drawing functions.\n\n## Common Config Keys\n\nAll parameter types share these optional configuration keys:\n\n| Key | Type | Default | Description |\n|---|---|---|---|\n| `label` | `string` | **(required)** | Display name shown in the parameter UI |\n| `description` | `string` | — | Tooltip or help text |\n| `group` | `string` | `'general'` | Group name for organizing parameters — see [Grouping](grouping/) |\n| `category` | `ParameterCategory` | `'general'` | Controls visibility based on capabilities — see [Categories](categories/) |\n\n## Organization\n\nAs scenes grow, you'll want to organize parameters into logical sections and control when they're visible:\n\n- **[Grouping](grouping/)** — Collect related parameters under named groups (e.g., \"animation\", \"shape\", \"audio\"). Parameters with the same `group` string appear together in the UI.\n- **[Categories](categories/)** — Tag parameters as `'general'`, `'audio'`, `'video'`, or `'interaction'` to automatically show/hide them based on what inputs are currently active.\n\n## Related\n\n- [Slider](slider/) — the most common parameter type\n- [Image](image/) — image parameters with the `.p5` property\n- [Grouping](grouping/) — organizing parameters into named groups\n- [Categories](categories/) — visibility based on capabilities\n- [Native Parameters](/native/parameters) — same system in the native renderer\n- [Shader Parameters](/shader/parameters) — comment-directive syntax for shaders\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
1003
|
+
}
|
|
1004
|
+
]
|
|
1005
|
+
},
|
|
1006
|
+
"p5-param-grouping": {
|
|
1007
|
+
"id": "p5-param-grouping",
|
|
1008
|
+
"title": "Grouping Parameters",
|
|
1009
|
+
"description": "Organize P5 scene parameters into named groups so related controls appear together in the UI.",
|
|
1010
|
+
"content": [
|
|
1011
|
+
{
|
|
1012
|
+
"type": "text",
|
|
1013
|
+
"markdown": "# Grouping Parameters\n\nAs your scene grows, the parameter list can become unwieldy. Groups let you collect related parameters under a shared heading in the host UI. Parameters with the same `group` string appear together, regardless of declaration order.\n\n## Usage\n\nSet the `group` config key on any parameter:\n\n```javascript\n// @renderer p5\n\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed', group: 'animation' });\nconst trail = viji.toggle(true, { label: 'Trail', group: 'animation' });\n\nconst size = viji.slider(0.05, { min: 0.01, max: 0.15, step: 0.01, label: 'Size', group: 'shape' });\nconst color = viji.color('#ff6600', { label: 'Color', group: 'shape' });\n```\n\nThe host application receives the group names and renders them as collapsible sections or visual separators.\n\n## How It Works\n\n- `group` is a **freeform string** — any name works. There is no fixed list.\n- If omitted, parameters default to the `'general'` group.\n- Parameters with the same `group` value are collected together when sent to the host UI.\n- Group names are displayed as-is, so use readable names like `'animation'`, `'visual style'`, `'audio settings'`.\n- Declaration order within a group is preserved.\n\n## Live Example\n\nThis scene uses two groups — \"animation\" (speed and trail) and \"shape\" (size and color):"
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
"type": "live-example",
|
|
1017
|
+
"title": "P5 Grouped Parameters",
|
|
1018
|
+
"sceneCode": "// @renderer p5\n\nconst speed = viji.slider(1, { min: 0.1, max: 5, label: 'Speed', group: 'animation' });\nconst trail = viji.toggle(true, { label: 'Trail', group: 'animation' });\n\nconst size = viji.slider(0.05, { min: 0.01, max: 0.15, step: 0.01, label: 'Size', group: 'shape' });\nconst color = viji.color('#ff6600', { label: 'Color', group: 'shape' });\n\nlet angle = 0;\n\nfunction render(viji, p5) {\n angle += speed.value * viji.deltaTime;\n\n if (trail.value) {\n p5.background(10, 10, 30, 25);\n } else {\n p5.background(10, 10, 30);\n }\n\n const cx = viji.width / 2 + p5.cos(angle) * viji.width * 0.3;\n const cy = viji.height / 2 + p5.sin(angle) * viji.height * 0.3;\n const r = Math.min(viji.width, viji.height) * size.value;\n\n const cr = parseInt(color.value.slice(1, 3), 16);\n const cg = parseInt(color.value.slice(3, 5), 16);\n const cb = parseInt(color.value.slice(5, 7), 16);\n\n p5.noStroke();\n p5.fill(cr, cg, cb);\n p5.circle(cx, cy, r * 2);\n}\n",
|
|
1019
|
+
"sceneFile": "grouping-demo.scene.js"
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
"type": "text",
|
|
1023
|
+
"markdown": "## Mixing Types\n\nGroups can contain any mix of parameter types — sliders, colors, toggles, selects, and more. There is no restriction on which types can share a group:\n\n```javascript\nconst brightness = viji.slider(1, { min: 0, max: 2, label: 'Brightness', group: 'visual' });\nconst tint = viji.color('#ffffff', { label: 'Tint', group: 'visual' });\nconst showGrid = viji.toggle(false, { label: 'Grid Overlay', group: 'visual' });\nconst blendMode = viji.select('normal', { options: ['normal', 'add', 'multiply'], label: 'Blend', group: 'visual' });\n```\n\n## Groups and Categories\n\n`group` and `category` are independent. A group organizes the visual layout; a category controls visibility based on active capabilities. You can combine them:\n\n```javascript\nconst volume = viji.slider(1, {\n label: 'Volume Scale',\n group: 'audio settings',\n category: 'audio'\n});\n```\n\nSee [Categories](../categories/) for details on how `category` controls parameter visibility.\n\n## Related\n\n- [Parameters Overview](../) — all parameter types and the basic pattern\n- [Categories](../categories/) — visibility based on active capabilities\n- [Native Grouping](/native/parameters/grouping) — same concept in the native renderer\n- [Shader Grouping](/shader/parameters/grouping) — same concept with `@viji-*` directives\n- [Slider](../slider/) — the most common parameter type"
|
|
1024
|
+
}
|
|
1025
|
+
]
|
|
1026
|
+
},
|
|
1027
|
+
"p5-param-categories": {
|
|
1028
|
+
"id": "p5-param-categories",
|
|
1029
|
+
"title": "Parameter Categories",
|
|
1030
|
+
"description": "Control P5 scene parameter visibility based on active capabilities like audio, video, and interaction.",
|
|
1031
|
+
"content": [
|
|
1032
|
+
{
|
|
1033
|
+
"type": "text",
|
|
1034
|
+
"markdown": "# Parameter Categories\n\nCategories let you tag parameters so they're only visible when the corresponding capability is active. An \"Audio Pulse\" slider is useless if no audio source is connected — with `category: 'audio'`, it automatically appears when audio is available and hides when it's not.\n\n## The Four Categories\n\n| Category | Visible When | Use For |\n|---|---|---|\n| `'general'` | Always | Colors, sizes, speeds, shapes — anything that works without external input |\n| `'audio'` | Audio source is connected | Volume scaling, beat reactivity, frequency controls |\n| `'video'` | Video/camera source is connected | Video opacity, CV sensitivity, segmentation controls |\n| `'interaction'` | User interaction is enabled | Mouse effects, keyboard bindings, touch sensitivity |\n\n## Usage\n\n```javascript\n// @renderer p5\n\nconst baseColor = viji.color('#4488ff', { label: 'Base Color', category: 'general' });\nconst pulseAmount = viji.slider(0.3, { min: 0, max: 1, label: 'Audio Pulse', category: 'audio' });\nconst showMouse = viji.toggle(true, { label: 'Mouse Dot', category: 'interaction' });\n```\n\n- `baseColor` is always visible.\n- `pulseAmount` only appears when audio is connected.\n- `showMouse` only appears when interaction is enabled.\n\nIf you omit `category`, it defaults to `'general'` (always visible).\n\n## How It Works\n\n1. The artist sets `category` on each parameter during scene initialization.\n2. When the host application requests parameters, Viji filters them based on the current `CoreCapabilities`:\n - `hasAudio` — is an audio stream connected?\n - `hasVideo` — is a video/camera stream connected?\n - `hasInteraction` — is user interaction enabled?\n - `hasGeneral` — always `true`.\n3. Only parameters matching active capabilities are sent to the UI.\n\n> [!NOTE]\n> Categories filter at both the **group level** and the **individual parameter level**. If a group's category doesn't match, the entire group is hidden. If individual parameters within a visible group have non-matching categories, those parameters are hidden while the group remains visible.\n\n## Live Example\n\nParameters in three categories — `general` (always visible), `audio` (needs audio), and `interaction` (needs mouse):"
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
"type": "live-example",
|
|
1038
|
+
"title": "P5 Parameter Categories",
|
|
1039
|
+
"sceneCode": "// @renderer p5\n\nconst baseColor = viji.color('#4488ff', { label: 'Base Color', category: 'general' });\nconst pulseAmount = viji.slider(0.3, { min: 0, max: 1, step: 0.01, label: 'Audio Pulse', category: 'audio' });\nconst showMouse = viji.toggle(true, { label: 'Mouse Dot', category: 'interaction' });\n\nlet angle = 0;\n\nfunction render(viji, p5) {\n p5.background(10, 10, 30, 40);\n\n angle += viji.deltaTime;\n\n const r = parseInt(baseColor.value.slice(1, 3), 16);\n const g = parseInt(baseColor.value.slice(3, 5), 16);\n const b = parseInt(baseColor.value.slice(5, 7), 16);\n\n let pulse = 0;\n if (viji.audio.isConnected) {\n pulse = viji.audio.volume.current * pulseAmount.value;\n }\n\n const baseR = Math.min(viji.width, viji.height) * (0.1 + pulse * 0.15);\n const cx = viji.width / 2 + p5.cos(angle) * viji.width * 0.2;\n const cy = viji.height / 2 + p5.sin(angle * 0.7) * viji.height * 0.2;\n\n p5.noStroke();\n p5.fill(r, g, b);\n p5.circle(cx, cy, baseR * 2);\n\n if (showMouse.value && viji.mouse.isInCanvas) {\n p5.fill(255, 255, 255, 200);\n p5.circle(viji.mouse.x, viji.mouse.y, Math.min(viji.width, viji.height) * 0.04);\n }\n}\n",
|
|
1040
|
+
"sceneFile": "categories-demo.scene.js"
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
"type": "text",
|
|
1044
|
+
"markdown": "## Design Guidelines\n\n- **Default to `'general'`** unless the parameter genuinely depends on an external input.\n- **Use `'audio'` for parameters that only make sense with sound** — beat sensitivity, frequency scaling, audio decay.\n- **Use `'video'` for parameters tied to camera/video** — segmentation threshold, face tracking sensitivity.\n- **Use `'interaction'` for parameters that need user input** — mouse effect radius, keyboard shortcut configuration.\n- **Don't use categories as a replacement for grouping.** Categories control *visibility*; groups control *layout*. Use both when appropriate.\n\n## Categories and Groups\n\n`category` and `group` are orthogonal. A parameter in group `'effects'` with category `'audio'` will appear under the \"effects\" group heading, but only when audio is connected:\n\n```javascript\nconst bassReact = viji.slider(0.5, {\n label: 'Bass Reactivity',\n group: 'effects',\n category: 'audio'\n});\n\nconst colorShift = viji.slider(0.2, {\n label: 'Color Shift',\n group: 'effects',\n category: 'general'\n});\n```\n\nBoth parameters appear in the \"effects\" group, but `bassReact` only shows when audio is active.\n\n## Related\n\n- [Parameters Overview](../) — all parameter types and the basic pattern\n- [Grouping](../grouping/) — organizing parameters into named groups\n- [Native Categories](/native/parameters/categories) — same concept in the native renderer\n- [Shader Categories](/shader/parameters/categories) — same concept with `@viji-*` directives\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
1045
|
+
}
|
|
1046
|
+
]
|
|
1047
|
+
},
|
|
1048
|
+
"shader-quickstart": {
|
|
1049
|
+
"id": "shader-quickstart",
|
|
1050
|
+
"title": "Shader Quick Start",
|
|
1051
|
+
"description": "Build your first Viji scene with GLSL fragment shaders running directly on the GPU.",
|
|
1052
|
+
"content": [
|
|
1053
|
+
{
|
|
1054
|
+
"type": "text",
|
|
1055
|
+
"markdown": "# Shader Quick Start\n\nThe shader renderer lets you write GLSL fragment shaders that run on the GPU. Viji handles the fullscreen quad, uniform injection, and parameter system — you write only the shader logic.\n\n> [!IMPORTANT]\n> P5 and shader scenes must declare their renderer type as the first comment:\n> ```\n> // @renderer shader\n> ```\n> Without this directive, the scene defaults to the native renderer.\n\n## Your First Shader"
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
"type": "live-example",
|
|
1059
|
+
"title": "Shader — Wave Pattern",
|
|
1060
|
+
"sceneCode": "// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0 step:0.1\n// @viji-slider:scale label:\"Scale\" default:8.0 min:1.0 max:20.0 step:0.5\n// @viji-color:tint label:\"Tint\" default:#00ffcc\n// @viji-accumulator:phase rate:speed\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n\n float pattern = sin(uv.x * scale + phase)\n * cos(uv.y * scale - phase * 0.7)\n * 0.5 + 0.5;\n\n vec3 color = tint * pattern;\n gl_FragColor = vec4(color, 1.0);\n}\n",
|
|
1061
|
+
"sceneFile": "quickstart-shader.scene.glsl"
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
"type": "text",
|
|
1065
|
+
"markdown": "### What's Happening\n\n**Comment directives — parsed before compilation:**\n\n- `// @renderer shader` tells Viji to use the shader renderer.\n- `// @viji-slider:speed ...` declares a parameter. Viji generates a `uniform float speed;` automatically.\n- `// @viji-color:tint ...` declares a color parameter. Viji generates a `uniform vec3 tint;`.\n- `// @viji-accumulator:phase rate:speed` creates a CPU-side accumulator that adds `speed × deltaTime` every frame. The result is a `uniform float phase;` that increases smoothly — no jumps when the slider changes.\n\n**`void main()` — runs for every pixel, every frame:**\n\n- `gl_FragCoord.xy / u_resolution` gives normalized UV coordinates (0–1).\n- `phase` is the accumulator — use it instead of `u_time * speed` for smooth, slider-driven animation.\n- `speed`, `scale`, `tint` are your parameter uniforms — updated live as the user adjusts controls.\n- `gl_FragColor` sets the output color for each pixel.\n\n> [!NOTE]\n> Parameter declarations use **single-line `//` comments only**. Block comments `/* */` are not parsed for `@viji-*` directives.\n\n## Scene Structure\n\nA shader scene is a GLSL fragment shader with comment directives:\n\n```glsl\n// @renderer shader\n// @viji-slider:brightness label:\"Brightness\" default:1.0 min:0.0 max:2.0\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n gl_FragColor = vec4(uv * brightness, 0.5, 1.0);\n}\n```\n\n- **No `precision` or `uniform` declarations needed.** Viji auto-injects `precision mediump float;` and all uniform declarations.\n- **No vertex shader.** Viji renders a fullscreen quad; your fragment shader defines the color of every pixel.\n- **Parameters become uniforms.** `// @viji-slider:name` becomes `uniform float name;` automatically.\n\n> [!NOTE]\n> The Viji shader renderer automatically injects `precision mediump float;` and all `uniform` declarations. Write only your helper functions and `void main() { ... }`. Do NOT redeclare `precision` or built-in uniforms — they will conflict. If you use `#version 300 es`, Viji will handle its placement automatically.\n\n## Parameter Types\n\nDeclare parameters with `// @viji-TYPE:uniformName key:value` syntax:\n\n| Type | Uniform | Example |\n|------|---------|---------|\n| `slider` | `float` | `// @viji-slider:speed label:\"Speed\" default:1.0 min:0.0 max:5.0` |\n| `number` | `float` | `// @viji-number:count label:\"Count\" default:10.0 min:1.0 max:100.0` |\n| `color` | `vec3` | `// @viji-color:tint label:\"Tint\" default:#ff6600` |\n| `toggle` | `bool` | `// @viji-toggle:invert label:\"Invert\" default:false` |\n| `select` | `int` | `// @viji-select:mode label:\"Mode\" default:0 options:[\"Wave\",\"Spiral\",\"Grid\"]` |\n| `image` | `sampler2D` | `// @viji-image:tex label:\"Texture\"` |\n| `accumulator` | `float` | `// @viji-accumulator:phase rate:speed` |\n\nConfig keys: `label`, `default`, `min`, `max`, `step`, `description`, `group`, `category`.\n\n### Accumulators\n\nAccumulators solve the \"jumping animation\" problem. When you write `u_time * speed`, changing the `speed` slider causes a visible jump because the entire phase is recalculated instantly. Accumulators integrate the rate over time on the CPU side:\n\n```glsl\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\n// @viji-accumulator:phase rate:speed\n```\n\n- `phase` increases by `speed × deltaTime` each frame — changing `speed` only affects future growth, never jumps backward.\n- `rate` can reference any declared parameter name or be a numeric constant (e.g., `rate:1.5`).\n- Accumulators have no UI control — they are internal uniform values.\n- Optional `default` sets the starting value (defaults to 0).\n\n> [!WARNING]\n> Do not use the `u_` prefix for your parameter uniform names — it is reserved for built-in Viji uniforms. Use descriptive names like `speed`, `colorMix`, `intensity` instead.\n\n## Built-in Uniforms\n\nThese are always available — no declaration needed:\n\n| Uniform | Type | Description |\n|---------|------|-------------|\n| `u_resolution` | `vec2` | Canvas width and height in pixels |\n| `u_time` | `float` | Elapsed seconds since scene start |\n| `u_deltaTime` | `float` | Seconds since last frame |\n| `u_frame` | `int` | Current frame number |\n| `u_mouse` | `vec2` | Mouse position in pixels |\n| `u_mousePressed` | `bool` | Any mouse button is pressed |\n| `u_audioVolume` | `float` | Overall audio volume (0–1) |\n| `u_audioLow` | `float` | Low frequency energy (0–1) |\n| `u_audioMid` | `float` | Mid frequency energy (0–1) |\n| `u_audioHigh` | `float` | High frequency energy (0–1) |\n| `u_audioKick` | `float` | Kick beat detection (0–1) |\n| `u_video` | `sampler2D` | Current video frame |\n\nSee [API Reference](/shader/api-reference) for the complete list of 100+ built-in uniforms.\n\n## Essential Patterns\n\n**Normalized coordinates:**\n\n```glsl\nvec2 uv = gl_FragCoord.xy / u_resolution; // 0..1\nvec2 centered = uv - 0.5; // -0.5..0.5\ncentered.x *= u_resolution.x / u_resolution.y; // aspect-corrected\n```\n\n**Distance fields:**\n\n```glsl\nfloat d = length(centered); // distance from center\nfloat circle = smoothstep(0.3, 0.29, d); // anti-aliased circle\n```\n\n> [!NOTE]\n> Always use `u_resolution` for positioning and sizing and `u_time` / `u_deltaTime` for animation. This keeps your shader resolution-agnostic and frame-rate-independent.\n\n## GLSL Version\n\nBy default, shaders use **GLSL ES 1.00** (WebGL 1). If you need WebGL 2 features, add `#version 300 es` as the first line:\n\n```glsl\n#version 300 es\n// @renderer shader\n\n// ES 3.00 requires explicit output declaration\nout vec4 fragColor;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n fragColor = vec4(uv, sin(u_time), 1.0);\n}\n```\n\nES 3.00 differences: `gl_FragColor` → `out vec4`, `texture2D()` → `texture()`. Use ES 1.00 for maximum compatibility.\n\n## Backbuffer (Previous Frame)\n\nViji gives you access to the previous frame as a texture — just reference `backbuffer` in your code and it's automatically enabled:\n\n```glsl\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec4 prev = texture2D(backbuffer, uv); // previous frame\n vec3 current = vec3(/* ... your effect ... */);\n gl_FragColor = vec4(mix(prev.rgb, current, 0.1), 1.0); // 90% trail\n}\n```\n\nThis enables feedback effects, trails, motion blur, and accumulation buffers. No setup needed — Viji detects the `backbuffer` reference and creates the ping-pong framebuffers automatically.\n\nSee [Backbuffer](/shader/backbuffer) for detailed patterns and techniques.\n\n## Shadertoy Compatibility\n\nIf you have existing Shadertoy shaders, see [Shadertoy Compatibility](/shader/shadertoy) for a mapping of Shadertoy uniforms to Viji equivalents.\n\n## Next Steps\n\n- [Shader Basics](/shader/basics) — uniforms, coordinate systems, techniques\n- [Parameters](/shader/parameters) — all parameter types for shaders\n- [Audio Uniforms](/shader/audio) — react to music in GLSL\n- [Backbuffer](/shader/backbuffer) — feedback effects using the previous frame\n- [API Reference](/shader/api-reference) — complete list of built-in uniforms\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
1066
|
+
}
|
|
1067
|
+
]
|
|
1068
|
+
},
|
|
1069
|
+
"shader-parameters": {
|
|
1070
|
+
"id": "shader-parameters",
|
|
1071
|
+
"title": "Parameters",
|
|
1072
|
+
"description": "Shader parameter system — declare controls with comment directives that become GLSL uniforms automatically.",
|
|
1073
|
+
"content": [
|
|
1074
|
+
{
|
|
1075
|
+
"type": "text",
|
|
1076
|
+
"markdown": "# Shader Parameters\n\nParameters are the primary way to give users control over your scene. You declare them at the top of your shader, and Viji renders corresponding UI controls (sliders, color pickers, toggles, etc.) in the host application. Values update in real-time as users interact with the controls.\n\nIn the shader renderer, parameters are declared using `// @viji-*` comment directives. Each directive creates a UI control and a corresponding GLSL uniform — no manual `uniform` declarations needed. Read the uniform directly in your shader code to get the current value.\n\n## Parameter Types\n\n| Directive | Uniform Type | Value | Use For |\n|---|---|---|---|\n| [`@viji-slider`](slider/) | `float` | Continuous range | Speed, intensity, size |\n| [`@viji-number`](number/) | `float` | Precise numeric | Count, threshold |\n| [`@viji-color`](color/) | `vec3` | RGB (0–1) | Tint, palette |\n| [`@viji-toggle`](toggle/) | `bool` | On/off | Invert, enable effect |\n| [`@viji-select`](select/) | `int` | Index (0-based) | Mode, pattern selection |\n| [`@viji-image`](image/) | `sampler2D` | Texture | Overlay, displacement map |\n| [`@viji-accumulator`](accumulator/) | `float` | CPU-side integration | Smooth animation phase |\n\n## Basic Pattern\n\n```glsl\n// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\n// @viji-color:tint label:\"Tint\" default:#ff6600\n// @viji-toggle:invert label:\"Invert\" default:false\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec3 col = tint * uv.x * speed;\n if (invert) col = 1.0 - col;\n gl_FragColor = vec4(col, 1.0);\n}\n```\n\nEach `@viji-*` directive auto-generates a `uniform` declaration with the name after the colon. `speed` becomes `uniform float speed;`, `tint` becomes `uniform vec3 tint;`, etc.\n\n> [!WARNING]\n> Do not use the `u_` prefix for parameter names — it is reserved for built-in Viji uniforms. Use descriptive names like `speed`, `tint`, `brightness`.\n\n> [!NOTE]\n> Parameter directives use **single-line `//` comments only**. Block comments `/* */` are not parsed for `@viji-*` directives.\n\n## Directive Syntax\n\n```\n// @viji-TYPE:uniformName key:value key:\"string value\" key:[array]\n```\n\n- **TYPE** — one of: `slider`, `number`, `color`, `toggle`, `select`, `image`, `accumulator`\n- **uniformName** — the GLSL uniform name (no `u_` prefix)\n- **key:value** pairs — configure the parameter\n\n## Common Config Keys\n\n| Key | Type | Description |\n|---|---|---|\n| `label` | `string` | **(required)** Display name in the UI |\n| `default` | varies | **(required for most types)** Initial value |\n| `min` | `number` | Minimum value (slider/number) |\n| `max` | `number` | Maximum value (slider/number) |\n| `step` | `number` | Step increment (slider/number) |\n| `description` | `string` | Help text |\n| `group` | `string` | Group name for organizing parameters — see [Grouping](grouping/) |\n| `category` | `string` | Visibility category — see [Categories](categories/) |\n| `options` | `array` | Options list (select only) |\n\n## Uniform Type Mapping\n\n| Parameter Type | GLSL Uniform | Notes |\n|---|---|---|\n| `slider` | `uniform float` | |\n| `number` | `uniform float` | Same as slider |\n| `color` | `uniform vec3` | RGB components, each 0–1 |\n| `toggle` | `uniform bool` | |\n| `select` | `uniform int` | 0-based index of selected option |\n| `image` | `uniform sampler2D` | |\n| `accumulator` | `uniform float` | CPU-side `value += rate × deltaTime` each frame |\n\n> [!WARNING]\n> All `@viji-*` directives must appear as top-level comments before `void main()`. They are parsed once during shader compilation. Viji's shader auto-injection places the generated `uniform` declarations before your code.\n\n## Organization\n\nAs scenes grow, you'll want to organize parameters into logical sections and control when they're visible:\n\n- **[Grouping](grouping/)** — Use the `group:` key to collect related parameters under a shared heading (e.g., `group:animation`). Parameters with the same `group` value appear together in the UI.\n- **[Categories](categories/)** — Use the `category:` key to tag parameters as `general`, `audio`, `video`, or `interaction` to automatically show/hide them based on what inputs are currently active.\n\n## Related\n\n- [Slider](slider/) — the most common parameter type\n- [Accumulator](accumulator/) — smooth animation without `u_time * speed` jumps\n- [Grouping](grouping/) — organizing parameters into named groups\n- [Categories](categories/) — visibility based on capabilities\n- [Native Parameters](/native/parameters) — equivalent JavaScript API\n- [P5 Parameters](/p5/parameters) — same system in the P5 renderer\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
1077
|
+
}
|
|
1078
|
+
]
|
|
1079
|
+
},
|
|
1080
|
+
"shader-param-accumulator": {
|
|
1081
|
+
"id": "shader-param-accumulator",
|
|
1082
|
+
"title": "Accumulator",
|
|
1083
|
+
"description": "Smooth, jump-free animation in shaders using CPU-side phase accumulation driven by parameters or constants.",
|
|
1084
|
+
"content": [
|
|
1085
|
+
{
|
|
1086
|
+
"type": "text",
|
|
1087
|
+
"markdown": "# @viji-accumulator\n\n```\n// @viji-accumulator:uniformName rate:source [default:value]\n```\n\nAn accumulator is a CPU-side float value that grows by `rate × deltaTime` every frame. It produces a `uniform float` in your shader — use it anywhere you need smooth, parameter-driven animation without jumps.\n\n## The Problem\n\nWhen you multiply `u_time` by a speed parameter, changing the slider recalculates the entire phase instantly and the animation jumps:\n\n```glsl\n// Bad — animation jumps when speed slider changes\nfloat wave = sin(u_time * speed);\n```\n\nThe accumulator solves this by integrating the rate over time. Changing the rate only affects future growth — it never jumps backward or forward:\n\n```glsl\n// Good — smooth at any slider value\n// @viji-accumulator:phase rate:speed\nfloat wave = sin(phase);\n```\n\n## Basic Usage"
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
"type": "live-example",
|
|
1091
|
+
"title": "Speed-Driven Accumulator",
|
|
1092
|
+
"sceneCode": "// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0 step:0.1\n// @viji-color:tint label:\"Tint\" default:#00ffcc\n// @viji-accumulator:phase rate:speed\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n float wave = sin(uv.x * 10.0 + phase) * 0.5 + 0.5;\n gl_FragColor = vec4(tint * wave, 1.0);\n}\n",
|
|
1093
|
+
"sceneFile": "acc-basic.scene.glsl"
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
"type": "text",
|
|
1097
|
+
"markdown": "The accumulator `phase` increases by `speed × deltaTime` each frame. Try moving the Speed slider — the wave changes pace smoothly, never jumps.\n\n## Config Keys\n\n| Key | Type | Required | Default | Description |\n|-----|------|----------|---------|-------------|\n| `rate` | `string` or `number` | Yes | — | Rate source: a declared parameter name or numeric constant |\n| `default` | `number` | No | `0` | Initial value of the accumulator |\n\n## Uniform Type\n\nAccumulators always produce a `uniform float`:\n\n```glsl\n// This directive:\n// @viji-accumulator:phase rate:speed\n\n// Generates this uniform:\nuniform float phase;\n```\n\n## No UI Control\n\nUnlike sliders, colors, or toggles, accumulators **do not appear** in the host parameter panel. They are internal values managed by the Viji runtime. Artists cannot directly manipulate them — they are driven entirely by their `rate` source.\n\n## Constant Rate\n\nThe `rate` can be a numeric constant instead of a parameter name. This is useful for steady background animation that doesn't need user control:\n\n```glsl\n// @viji-accumulator:drift rate:0.5\n```\n\nHere `drift` increases by `0.5` per second, unconditionally."
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
"type": "live-example",
|
|
1101
|
+
"title": "Constant-Rate Accumulator",
|
|
1102
|
+
"sceneCode": "// @renderer shader\n// @viji-slider:brightness label:\"Brightness\" default:1.0 min:0.1 max:2.0 step:0.1\n// @viji-accumulator:drift rate:0.5\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 center = uv - 0.5;\n center.x *= u_resolution.x / u_resolution.y;\n\n float d = length(center);\n float ring = smoothstep(0.02, 0.0, abs(d - mod(drift * 0.3, 0.5)));\n\n vec3 color = vec3(0.1, 0.05, 0.2) + ring * brightness * vec3(0.9, 0.4, 1.0);\n gl_FragColor = vec4(color, 1.0);\n}\n",
|
|
1103
|
+
"sceneFile": "acc-constant.scene.glsl"
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
"type": "text",
|
|
1107
|
+
"markdown": "## Multiple Accumulators\n\nYou can declare multiple accumulators with independent rates. This lets you animate different axes or properties at different speeds:\n\n```glsl\n// @viji-slider:speedX label:\"Horizontal Speed\" default:1.0 min:0.0 max:5.0\n// @viji-slider:speedY label:\"Vertical Speed\" default:0.7 min:0.0 max:5.0\n// @viji-accumulator:phaseX rate:speedX\n// @viji-accumulator:phaseY rate:speedY\n```\n\nEach accumulator tracks its own value independently. Changing `speedX` has no effect on `phaseY`."
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
"type": "live-example",
|
|
1111
|
+
"title": "Multi-Axis Accumulator",
|
|
1112
|
+
"sceneCode": "// @renderer shader\n// @viji-slider:speedX label:\"Horizontal Speed\" default:1.0 min:0.0 max:5.0 step:0.1\n// @viji-slider:speedY label:\"Vertical Speed\" default:0.7 min:0.0 max:5.0 step:0.1\n// @viji-slider:radius label:\"Radius\" default:0.3 min:0.05 max:0.5 step:0.01\n// @viji-color:dotColor label:\"Color\" default:#ff6600\n// @viji-accumulator:phaseX rate:speedX\n// @viji-accumulator:phaseY rate:speedY\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_resolution;\n vec2 center = uv - 0.5;\n center.x *= u_resolution.x / u_resolution.y;\n\n vec2 pos = vec2(cos(phaseX), sin(phaseY)) * 0.3;\n float d = length(center - pos);\n float circle = smoothstep(radius + 0.01, radius - 0.01, d);\n\n vec3 bg = vec3(0.05);\n vec3 color = mix(bg, dotColor, circle);\n gl_FragColor = vec4(color, 1.0);\n}\n",
|
|
1113
|
+
"sceneFile": "acc-multi.scene.glsl"
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
"type": "text",
|
|
1117
|
+
"markdown": "## Rules\n\n- **Only `//` comments.** Like all `@viji-*` directives, accumulators must use single-line `//` comments. Block comments `/* */` are not parsed.\n- **No `u_` prefix.** The `u_` prefix is reserved for built-in Viji uniforms. Name your accumulators descriptively: `phase`, `drift`, `rotation`.\n- **Rate must exist.** If `rate` references a parameter name that doesn't exist, Viji logs a warning and falls back to rate `0` (the accumulator stays at its default value).\n- **`label` is not required.** Since accumulators have no UI, the `label` config key is optional (unlike all other parameter types).\n- **Value grows unbounded.** The accumulator value increases indefinitely. If you need a bounded range, use `mod()` or `fract()` in your shader code:\n\n```glsl\n// @viji-accumulator:phase rate:speed\nfloat angle = mod(phase, 6.283185); // wrap to 0–2π\n```\n\n## Related\n\n- [Shader Quick Start](/shader/quickstart) — introduction to shader parameter system\n- [Slider](/shader/parameters/slider) — numeric slider parameter (common rate source)\n- [Best Practices](/getting-started/best-practices) — animation timing patterns\n- [Common Mistakes](/getting-started/common-mistakes) — why `u_time * speed` jumps"
|
|
1118
|
+
}
|
|
1119
|
+
]
|
|
1120
|
+
},
|
|
1121
|
+
"shader-param-grouping": {
|
|
1122
|
+
"id": "shader-param-grouping",
|
|
1123
|
+
"title": "Grouping Parameters",
|
|
1124
|
+
"description": "Organize shader parameters into named groups using the group config key.",
|
|
1125
|
+
"content": [
|
|
1126
|
+
{
|
|
1127
|
+
"type": "text",
|
|
1128
|
+
"markdown": "# Grouping Shader Parameters\n\nAs your shader grows, the parameter list can become unwieldy. Groups let you collect related parameters under a shared heading in the host UI. In shaders, set the `group:` key in any `@viji-*` directive.\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0 group:animation\n// @viji-toggle:invert label:\"Invert\" default:false group:animation\n// @viji-slider:rings label:\"Ring Count\" default:10.0 min:2.0 max:30.0 step:1.0 group:shape\n// @viji-color:tint label:\"Tint\" default:#ff6600 group:shape\n```\n\nParameters with the same `group` value appear together in the UI. The group name is a freeform string — any name works.\n\nThe host application receives the group names and renders them as collapsible sections or visual separators.\n\n## How It Works\n\n- The `group` key is a **freeform string** — any name works. There is no fixed list.\n- If you omit `group`, the parameter defaults to `'general'`.\n- Parameters with the same `group` value are collected together when sent to the host UI.\n- Group names are displayed as-is, so use readable names like `animation`, `visual style`, `audio settings`.\n- Declaration order within a group is preserved.\n- The `group:` key in `@viji-*` directives works identically to the `group` config in native/P5 JavaScript parameters.\n\n## Live Example\n\nThis shader uses two groups — \"animation\" (speed and invert) and \"shape\" (ring count and tint):"
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
"type": "live-example",
|
|
1132
|
+
"title": "Shader Grouped Parameters",
|
|
1133
|
+
"sceneCode": "// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0 group:animation\n// @viji-toggle:invert label:\"Invert\" default:false group:animation\n// @viji-slider:rings label:\"Ring Count\" default:10.0 min:2.0 max:30.0 step:1.0 group:shape\n// @viji-color:tint label:\"Tint\" default:#ff6600 group:shape\n// @viji-accumulator:phase rate:speed\n\nvoid main() {\n vec2 uv = (2.0 * gl_FragCoord.xy - u_resolution) / u_resolution.y;\n float d = length(uv);\n\n float wave = sin(d * rings - phase * 4.0) * 0.5 + 0.5;\n vec3 col = tint * wave;\n\n if (invert) col = 1.0 - col;\n\n col *= smoothstep(1.5, 0.5, d);\n gl_FragColor = vec4(col, 1.0);\n}\n",
|
|
1134
|
+
"sceneFile": "grouping-demo.scene.glsl"
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
"type": "text",
|
|
1138
|
+
"markdown": "## Mixing Types\n\nGroups can contain any mix of parameter types. There is no restriction on which types can share a group:\n\n```glsl\n// @viji-slider:brightness label:\"Brightness\" default:1.0 min:0.0 max:2.0 group:visual\n// @viji-color:tint label:\"Tint\" default:#ffffff group:visual\n// @viji-toggle:showGrid label:\"Grid Overlay\" default:false group:visual\n// @viji-select:blendMode label:\"Blend\" default:0.0 options:normal|add|multiply group:visual\n```\n\n## Groups and Categories\n\n`group` and `category` are independent. A group organizes the visual layout; a category controls visibility based on active capabilities. Combine them when a grouped parameter depends on an external input:\n\n```glsl\n// @viji-slider:bassReact label:\"Bass React\" default:0.5 min:0.0 max:1.0 group:effects category:audio\n// @viji-slider:colorShift label:\"Color Shift\" default:0.2 min:0.0 max:1.0 group:effects category:general\n```\n\nBoth appear in the \"effects\" group, but `bassReact` only shows when audio is connected.\n\nSee [Categories](../categories/) for details on how `category` controls parameter visibility.\n\n## Related\n\n- [Parameters Overview](../) — all shader parameter types\n- [Categories](../categories/) — visibility based on active capabilities\n- [Native Grouping](/native/parameters/grouping) — same concept in JavaScript\n- [P5 Grouping](/p5/parameters/grouping) — same concept in P5 renderer\n- [Slider](../slider/) — the most common parameter type"
|
|
1139
|
+
}
|
|
1140
|
+
]
|
|
1141
|
+
},
|
|
1142
|
+
"shader-param-categories": {
|
|
1143
|
+
"id": "shader-param-categories",
|
|
1144
|
+
"title": "Parameter Categories",
|
|
1145
|
+
"description": "Control shader parameter visibility based on active capabilities like audio, video, and interaction.",
|
|
1146
|
+
"content": [
|
|
1147
|
+
{
|
|
1148
|
+
"type": "text",
|
|
1149
|
+
"markdown": "# Shader Parameter Categories\n\nCategories let you tag parameters so they're only visible when the corresponding capability is active. An \"Audio Pulse\" slider is useless if no audio source is connected — with `category:audio`, it automatically appears when audio is available and hides when it's not. In shaders, set the `category:` key in any `@viji-*` directive.\n\n## The Four Categories\n\n| Category | Visible When | Use For |\n|---|---|---|\n| `general` | Always | Colors, sizes, speeds, shapes — anything that works without external input |\n| `audio` | Audio source is connected | Volume scaling, beat reactivity, frequency controls |\n| `video` | Video/camera source is connected | Video opacity, CV sensitivity, segmentation controls |\n| `interaction` | User interaction is enabled | Mouse effects, keyboard bindings, touch sensitivity |\n\n## Usage\n\n```glsl\n// @renderer shader\n// @viji-color:tint label:\"Color\" default:#4488ff category:general\n// @viji-slider:audioPulse label:\"Audio Pulse\" default:0.3 min:0.0 max:1.0 category:audio\n// @viji-slider:mouseSize label:\"Mouse Glow\" default:0.15 min:0.0 max:0.5 category:interaction\n```\n\n- `tint` is always visible.\n- `audioPulse` only appears when audio is connected.\n- `mouseSize` only appears when interaction is enabled.\n\nIf you omit `category`, it defaults to `general` (always visible).\n\n## How It Works\n\n1. The artist sets `category:` on each directive during scene declaration.\n2. The shader parameter parser extracts `category` along with other config keys.\n3. When the host application requests parameters, Viji filters them based on the current `CoreCapabilities`:\n - `hasAudio` — is an audio stream connected?\n - `hasVideo` — is a video/camera stream connected?\n - `hasInteraction` — is user interaction enabled?\n - `hasGeneral` — always `true`.\n4. Only parameters matching active capabilities are sent to the UI.\n\n> [!NOTE]\n> Categories filter at both the **group level** and the **individual parameter level**. If a group's category doesn't match, the entire group is hidden. If individual parameters within a visible group have non-matching categories, those parameters are hidden while the group remains visible.\n\n> [!NOTE]\n> In shader directives, category values are **unquoted strings**: `category:audio`, not `category:\"audio\"`. Both forms work, but the unquoted form is conventional.\n\n## Live Example\n\nParameters in three categories — `general` (always visible), `audio` (needs audio), and `interaction` (needs mouse):"
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
"type": "live-example",
|
|
1153
|
+
"title": "Shader Parameter Categories",
|
|
1154
|
+
"sceneCode": "// @renderer shader\n// @viji-color:tint label:\"Color\" default:#4488ff category:general\n// @viji-slider:audioPulse label:\"Audio Pulse\" default:0.3 min:0.0 max:1.0 category:audio\n// @viji-slider:mouseSize label:\"Mouse Glow\" default:0.15 min:0.0 max:0.5 category:interaction\n// @viji-accumulator:phase rate:1.0\n\nvoid main() {\n vec2 uv = (2.0 * gl_FragCoord.xy - u_resolution) / u_resolution.y;\n float d = length(uv);\n\n float pulse = u_audioVolume * audioPulse;\n float wave = sin(d * 15.0 - phase * 3.0) * 0.5 + 0.5;\n vec3 col = tint * wave * (1.0 + pulse);\n\n vec2 mouseUV = (2.0 * u_mouse - u_resolution) / u_resolution.y;\n float mouseDist = length(uv - mouseUV);\n float glow = mouseSize / (mouseDist + 0.05);\n col += vec3(glow * 0.3);\n\n col *= smoothstep(1.5, 0.3, d);\n gl_FragColor = vec4(col, 1.0);\n}\n",
|
|
1155
|
+
"sceneFile": "categories-demo.scene.glsl"
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
"type": "text",
|
|
1159
|
+
"markdown": "## Design Guidelines\n\n- **Default to `general`** unless the parameter genuinely depends on an external input.\n- **Use `audio` for parameters that only make sense with sound** — beat sensitivity, frequency multipliers, audio decay.\n- **Use `video` for parameters tied to camera/video** — segmentation threshold, face tracking sensitivity.\n- **Use `interaction` for parameters that need user input** — mouse effect radius, touch sensitivity.\n- **Don't use categories as a replacement for grouping.** Categories control *visibility*; groups control *layout*. Use both when appropriate.\n\n## Categories and Groups\n\n`category` and `group` are orthogonal. A parameter in group `effects` with category `audio` will appear under the \"effects\" group heading, but only when audio is connected:\n\n```glsl\n// @viji-slider:bassReact label:\"Bass Reactivity\" default:0.5 min:0.0 max:1.0 group:effects category:audio\n// @viji-slider:colorShift label:\"Color Shift\" default:0.2 min:0.0 max:1.0 group:effects category:general\n```\n\nBoth parameters appear in the \"effects\" group, but `bassReact` only shows when audio is active.\n\n## Related\n\n- [Parameters Overview](../) — all shader parameter types\n- [Grouping](../grouping/) — organizing parameters into named groups\n- [Native Categories](/native/parameters/categories) — same concept in JavaScript\n- [P5 Categories](/p5/parameters/categories) — same concept in P5 renderer\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
1160
|
+
}
|
|
1161
|
+
]
|
|
1162
|
+
},
|
|
1163
|
+
"shader-shadertoy": {
|
|
1164
|
+
"id": "shader-shadertoy",
|
|
1165
|
+
"title": "Shadertoy Compatibility",
|
|
1166
|
+
"description": "How to convert Shadertoy shaders for the Viji platform, with a full uniform mapping table and compatibility layer.",
|
|
1167
|
+
"content": [
|
|
1168
|
+
{
|
|
1169
|
+
"type": "text",
|
|
1170
|
+
"markdown": "# Shadertoy Compatibility\n\nViji can run most Shadertoy shaders with minor changes. This page provides a compatibility layer you can paste at the top of any Shadertoy shader, plus guidance for a full native conversion.\n\n> [!TIP]\n> Want an AI to convert it for you? See [Convert: Shadertoy](/ai-prompts/convert-shadertoy) for a ready-to-paste prompt that maps all Shadertoy uniforms to native Viji equivalents automatically.\n\n## Quick Conversion\n\nPaste this compatibility header above any Shadertoy code:\n\n```glsl\n// @renderer shader\n\n#define iTime u_time\n#define iTimeDelta u_deltaTime\n#define iFrame u_frame\n#define iResolution vec3(u_resolution, u_resolution.x / u_resolution.y)\n#define iMouse vec4(u_mouse, u_mouseLeft ? u_mouse : vec2(0.0))\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord);\nvoid main() { mainImage(gl_FragColor, gl_FragCoord.xy); }\n\n// ===== PASTE SHADERTOY CODE BELOW THIS LINE =====\n```\n\nThen paste the Shadertoy `mainImage` function below. That's it — the `#define` macros map Shadertoy names to Viji uniforms, and the `main()` wrapper bridges the function signature.\n\n## Uniform Mapping\n\n| Shadertoy | Viji Equivalent | Notes |\n|---|---|---|\n| `iResolution` (`vec3`) | `u_resolution` (`vec2`) | Shadertoy's `.z` is pixel aspect ratio. The `#define` above constructs the `vec3` from Viji's `vec2`. |\n| `iTime` (`float`) | `u_time` (`float`) | Direct match — elapsed seconds. |\n| `iTimeDelta` (`float`) | `u_deltaTime` (`float`) | Direct match — seconds since last frame. |\n| `iFrame` (`int`) | `u_frame` (`int`) | Direct match — current frame number. |\n| `iMouse` (`vec4`) | `u_mouse` (`vec2`) + `u_mouseLeft` (`bool`) | See [iMouse Limitations](#imouse-limitations) below. |\n| `iChannel0`–`iChannel3` | `@viji-image` parameters | Declare image parameters, then use their uniform names. See [Textures](#textures). |\n| `iChannelResolution[N]` | Not available | Track image dimensions manually if needed. |\n| `iDate` | Not available | No date uniform in Viji. Use `u_time` for elapsed time. |\n| `iSampleRate` | Not available | Not applicable — Viji's audio API provides processed data, not raw samples. |\n| `fragCoord` (`vec2`) | `gl_FragCoord.xy` | Same — pixel coordinates. The compatibility `main()` wrapper handles this. |\n| `fragColor` (`vec4`) | `gl_FragColor` | Same — output color. The wrapper handles this. |\n\n## iMouse Limitations\n\nShadertoy's `iMouse` is a `vec4`:\n\n- `.xy` — mouse position while button is held\n- `.z` — x coordinate where the button was first pressed (positive while held, negative on release)\n- `.w` — y coordinate where the button was first pressed\n\nViji provides `u_mouse` (current position as `vec2`) and `u_mouseLeft` / `u_mouseRight` / `u_mousePressed` (booleans). There is no click-origin tracking, so `.zw` is approximated.\n\n**In practice**, most Shadertoy shaders only use `iMouse.xy` for camera orbiting or UV distortion, and the compatibility define works well for those cases. If a shader relies heavily on `.zw` click-origin behavior, you may need to track the click position manually:\n\n```glsl\n// @viji-slider:dummy label:\"\" default:0.0 min:0.0 max:0.0\n// (no extra params needed — just use built-in uniforms)\n\nvec2 clickOrigin = vec2(0.0); // approximate — Viji doesn't track this\n```\n\n## Textures\n\nShadertoy uses `iChannel0`–`iChannel3` for texture inputs. In Viji, declare image parameters instead:\n\n```glsl\n// @renderer shader\n// @viji-image:tex0 label:\"Texture 1\"\n// @viji-image:tex1 label:\"Texture 2\"\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec4 color = texture2D(tex0, fragCoord / iResolution.xy);\n // ...\n}\n```\n\nEach `@viji-image` creates a `uniform sampler2D` with the name you specify. Replace `iChannel0` with your chosen name (e.g., `tex0`).\n\n> [!NOTE]\n> Shadertoy textures are often set to specific wrap and filter modes. Viji image parameters use default WebGL settings (clamp-to-edge, linear filtering). Some shaders that rely on `repeat` wrapping may need `fract(uv)` to simulate the behavior.\n\n## Adding Speed Control\n\nMany artists add a speed parameter when converting shaders. Use an [accumulator](/shader/parameters/accumulator) to avoid animation jumps when the slider changes:\n\n```glsl\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0\n// @viji-accumulator:phase rate:speed\n\n#define iTime phase\n```\n\nThis replaces `#define iTime u_time` — now `iTime` is driven by the accumulator, and changing the speed slider only affects future playback, never jumps.\n\n## Live Example — With Compatibility Layer\n\nThis original shader uses Shadertoy conventions (`mainImage`, `fragCoord`, `iResolution`, `iTime`) wrapped with the compatibility header:"
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
"type": "live-example",
|
|
1174
|
+
"title": "Shadertoy Style — Animated Rings",
|
|
1175
|
+
"sceneCode": "// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0 step:0.1\n// @viji-accumulator:phase rate:speed\n\n#define iTime phase\n#define iResolution vec3(u_resolution, u_resolution.x / u_resolution.y)\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord);\nvoid main() { mainImage(gl_FragColor, gl_FragCoord.xy); }\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;\n\n float d = length(uv);\n float rings = sin(d * 20.0 - iTime * 4.0) * 0.5 + 0.5;\n\n vec3 col = mix(\n vec3(0.1, 0.2, 0.4),\n vec3(1.0, 0.6, 0.2),\n rings\n );\n\n col *= smoothstep(1.8, 0.5, d);\n\n fragColor = vec4(col, 1.0);\n}\n",
|
|
1176
|
+
"sceneFile": "shadertoy-compat.scene.glsl"
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
"type": "text",
|
|
1180
|
+
"markdown": "## Fully Native Version\n\nThe same visual effect rewritten with Viji uniforms directly — no compatibility defines needed:"
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
"type": "live-example",
|
|
1184
|
+
"title": "Native Viji — Animated Rings",
|
|
1185
|
+
"sceneCode": "// @renderer shader\n// @viji-slider:speed label:\"Speed\" default:1.0 min:0.1 max:5.0 step:0.1\n// @viji-accumulator:phase rate:speed\n\nvoid main() {\n vec2 uv = (2.0 * gl_FragCoord.xy - u_resolution) / u_resolution.y;\n\n float d = length(uv);\n float rings = sin(d * 20.0 - phase * 4.0) * 0.5 + 0.5;\n\n vec3 col = mix(\n vec3(0.1, 0.2, 0.4),\n vec3(1.0, 0.6, 0.2),\n rings\n );\n\n col *= smoothstep(1.8, 0.5, d);\n\n gl_FragColor = vec4(col, 1.0);\n}\n",
|
|
1186
|
+
"sceneFile": "shadertoy-native.scene.glsl"
|
|
1187
|
+
},
|
|
1188
|
+
{
|
|
1189
|
+
"type": "text",
|
|
1190
|
+
"markdown": "The native version is simpler: no forward declarations, no `main()` wrapper, and direct use of `u_resolution`, `phase`, and `gl_FragCoord`.\n\n## GLSL Sandbox Compatibility\n\nGLSL Sandbox shaders use different variable names. The compatibility layer is simpler:\n\n```glsl\n// @renderer shader\n\n#define time u_time\n#define mouse (u_mouse / u_resolution)\n#define resolution u_resolution\n```\n\nGLSL Sandbox shaders use `void main()` and `gl_FragColor` directly, so no function wrapper is needed — just paste the shader below the defines.\n\n## Conversion Checklist\n\n1. Add `// @renderer shader` at the very top.\n2. Paste the compatibility defines (or convert to native Viji uniforms).\n3. If the shader uses `mainImage`, add the `main()` wrapper.\n4. Replace `iChannel0`–`iChannel3` with `@viji-image` parameters.\n5. Remove any `precision` statements — Viji auto-injects them.\n6. If using GLSL ES 3.00, add `#version 300 es` as the first line and replace `gl_FragColor` with `out vec4`, `texture2D` with `texture`.\n7. Test and adjust — most shaders work immediately; complex ones may need minor tweaks.\n\n## Related\n\n- [Shader Quick Start](/shader/quickstart) — build shaders from scratch in Viji\n- [Accumulator](/shader/parameters/accumulator) — smooth speed control for converted shaders\n- [Backbuffer](/shader/backbuffer) — feedback effects (replaces Shadertoy's Buffer A–D for simple cases)\n- [Built-in Uniforms](/shader/api-reference) — complete list of Viji uniforms\n- [Best Practices](/getting-started/best-practices) — essential patterns for all renderers"
|
|
1191
|
+
}
|
|
1192
|
+
]
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
};
|