@viji-dev/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +29 -0
- package/LICENSE +13 -0
- package/README.md +103 -0
- package/bin/viji.js +75 -0
- package/eslint.config.js +37 -0
- package/index.html +20 -0
- package/package.json +82 -0
- package/postcss.config.js +6 -0
- package/public/favicon.png +0 -0
- package/scenes/audio-visualizer/main.js +287 -0
- package/scenes/core-demo/main.js +532 -0
- package/scenes/demo-scene/main.js +619 -0
- package/scenes/global.d.ts +15 -0
- package/scenes/particle-system/main.js +349 -0
- package/scenes/tsconfig.json +12 -0
- package/scenes/video-mirror/main.ts +436 -0
- package/src/App.css +42 -0
- package/src/App.tsx +279 -0
- package/src/cli/commands/build.js +147 -0
- package/src/cli/commands/create.js +71 -0
- package/src/cli/commands/dev.js +108 -0
- package/src/cli/commands/init.js +262 -0
- package/src/cli/utils/cli-utils.js +208 -0
- package/src/cli/utils/scene-compiler.js +432 -0
- package/src/components/SDKPage.tsx +337 -0
- package/src/components/core/CoreContainer.tsx +126 -0
- package/src/components/ui/DeviceSelectionList.tsx +137 -0
- package/src/components/ui/FPSCounter.tsx +78 -0
- package/src/components/ui/FileDropzonePanel.tsx +120 -0
- package/src/components/ui/FileListPanel.tsx +285 -0
- package/src/components/ui/InputExpansionPanel.tsx +31 -0
- package/src/components/ui/MediaPlayerControls.tsx +191 -0
- package/src/components/ui/MenuContainer.tsx +71 -0
- package/src/components/ui/ParametersMenu.tsx +797 -0
- package/src/components/ui/ProjectSwitcherMenu.tsx +192 -0
- package/src/components/ui/QuickInputControls.tsx +542 -0
- package/src/components/ui/SDKMenuSystem.tsx +96 -0
- package/src/components/ui/SettingsMenu.tsx +346 -0
- package/src/components/ui/SimpleInputControls.tsx +137 -0
- package/src/index.css +68 -0
- package/src/main.tsx +10 -0
- package/src/scenes-hmr.ts +158 -0
- package/src/services/project-filesystem.ts +436 -0
- package/src/stores/scene-player/index.ts +3 -0
- package/src/stores/scene-player/input-manager.store.ts +1045 -0
- package/src/stores/scene-player/scene-session.store.ts +659 -0
- package/src/styles/globals.css +111 -0
- package/src/templates/minimal-template.js +11 -0
- package/src/utils/debounce.js +34 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +18 -0
- package/tsconfig.app.json +27 -0
- package/tsconfig.json +27 -0
- package/tsconfig.node.json +27 -0
- package/vite.config.ts +54 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
// Full Feature Demo Scene - Parameter Object API
|
|
2
|
+
console.log('🚀 DEMO SCENE LOADED');
|
|
3
|
+
|
|
4
|
+
// Define parameters using helper functions (returns parameter objects)
|
|
5
|
+
const primaryColor = viji.color('#ff6b6b', {
|
|
6
|
+
label: 'Primary Color',
|
|
7
|
+
description: 'Main color for the animated shapes',
|
|
8
|
+
group: 'colors'
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const secondaryColor = viji.color('#4ecdc4', {
|
|
12
|
+
label: 'Secondary Color',
|
|
13
|
+
description: 'Secondary color for gradient effects',
|
|
14
|
+
group: 'colors'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const backgroundColor = viji.color('#2c3e50', {
|
|
18
|
+
label: 'Background Color',
|
|
19
|
+
description: 'Scene background color',
|
|
20
|
+
group: 'colors'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const shapeSize = viji.slider(50, {
|
|
24
|
+
min: 10,
|
|
25
|
+
max: 150,
|
|
26
|
+
step: 5,
|
|
27
|
+
label: 'Shape Size',
|
|
28
|
+
description: 'Size of the animated shapes',
|
|
29
|
+
group: 'shape'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const shapeType = viji.select('circle', {
|
|
33
|
+
options: ['circle', 'square', 'triangle', 'hexagon'],
|
|
34
|
+
label: 'Shape Type',
|
|
35
|
+
description: 'Type of shape to render',
|
|
36
|
+
group: 'shape'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const particleCount = viji.slider(8, {
|
|
40
|
+
min: 1,
|
|
41
|
+
max: 20,
|
|
42
|
+
step: 1,
|
|
43
|
+
label: 'Particle Count',
|
|
44
|
+
description: 'Number of particles to render',
|
|
45
|
+
group: 'shape'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const animationSpeed = viji.slider(1.0, {
|
|
49
|
+
min: 0.1,
|
|
50
|
+
max: 3.0,
|
|
51
|
+
step: 0.1,
|
|
52
|
+
label: 'Animation Speed',
|
|
53
|
+
description: 'Speed of the animation loop',
|
|
54
|
+
group: 'animation'
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const rotationSpeed = viji.slider(0.5, {
|
|
58
|
+
min: -5,
|
|
59
|
+
max: 5,
|
|
60
|
+
step: 0.1,
|
|
61
|
+
label: 'Rotation Speed',
|
|
62
|
+
description: 'Rotation speed for shapes',
|
|
63
|
+
group: 'animation'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const useGradient = viji.toggle(true, {
|
|
67
|
+
label: 'Use Gradient',
|
|
68
|
+
description: 'Enable gradient fill for shapes',
|
|
69
|
+
group: 'visual'
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const opacity = viji.slider(0.8, {
|
|
73
|
+
min: 0.1,
|
|
74
|
+
max: 1.0,
|
|
75
|
+
step: 0.05,
|
|
76
|
+
label: 'Opacity',
|
|
77
|
+
description: 'Overall opacity of the scene',
|
|
78
|
+
group: 'visual'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const enableTrails = viji.toggle(false, {
|
|
82
|
+
label: 'Enable Trails',
|
|
83
|
+
description: 'Show trails behind moving shapes',
|
|
84
|
+
group: 'visual'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const glowIntensity = viji.slider(0.5, {
|
|
88
|
+
min: 0.0,
|
|
89
|
+
max: 2.0,
|
|
90
|
+
step: 0.1,
|
|
91
|
+
label: 'Glow Intensity',
|
|
92
|
+
description: 'Intensity of shape glow effect',
|
|
93
|
+
group: 'visual'
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const sceneTitle = viji.text('Viji SDK Demo', {
|
|
97
|
+
label: 'Scene Title',
|
|
98
|
+
description: 'Title displayed in the scene',
|
|
99
|
+
group: 'text'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const showDebugInfo = viji.toggle(true, {
|
|
103
|
+
label: 'Show Debug Info',
|
|
104
|
+
description: 'Display performance and parameter information',
|
|
105
|
+
group: 'text'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Interaction Parameters
|
|
109
|
+
const mouseAttraction = viji.toggle(true, {
|
|
110
|
+
label: 'Mouse Attraction',
|
|
111
|
+
description: 'Particles are attracted to mouse cursor',
|
|
112
|
+
group: 'interaction',
|
|
113
|
+
category: 'interaction'
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const attractionStrength = viji.slider(0.3, {
|
|
117
|
+
min: 0,
|
|
118
|
+
max: 1,
|
|
119
|
+
step: 0.1,
|
|
120
|
+
label: 'Attraction Strength',
|
|
121
|
+
description: 'How strongly particles are attracted to mouse',
|
|
122
|
+
group: 'interaction',
|
|
123
|
+
category: 'interaction'
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const keyboardControl = viji.toggle(true, {
|
|
127
|
+
label: 'Keyboard Control',
|
|
128
|
+
description: 'Use WASD keys to move particles',
|
|
129
|
+
group: 'interaction',
|
|
130
|
+
category: 'interaction'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const touchScale = viji.slider(1.0, {
|
|
134
|
+
min: 0.5,
|
|
135
|
+
max: 2.0,
|
|
136
|
+
step: 0.1,
|
|
137
|
+
label: 'Touch Scale Factor',
|
|
138
|
+
description: 'Scale factor for touch interactions',
|
|
139
|
+
group: 'interaction',
|
|
140
|
+
category: 'interaction'
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const showInteractionInfo = viji.toggle(true, {
|
|
144
|
+
label: 'Show Interaction Info',
|
|
145
|
+
description: 'Display current interaction state',
|
|
146
|
+
group: 'interaction',
|
|
147
|
+
category: 'interaction'
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Audio Parameters
|
|
151
|
+
const audioReactive = viji.toggle(false, {
|
|
152
|
+
label: 'Audio Reactive',
|
|
153
|
+
description: 'Make particles react to audio input',
|
|
154
|
+
group: 'audio',
|
|
155
|
+
category: 'audio'
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const volumeSensitivity = viji.slider(1.0, {
|
|
159
|
+
min: 0.1,
|
|
160
|
+
max: 5.0,
|
|
161
|
+
step: 0.1,
|
|
162
|
+
label: 'Volume Sensitivity',
|
|
163
|
+
description: 'How sensitive particles are to volume changes',
|
|
164
|
+
group: 'audio',
|
|
165
|
+
category: 'audio'
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const bassReactivity = viji.slider(1.0, {
|
|
169
|
+
min: 0,
|
|
170
|
+
max: 3.0,
|
|
171
|
+
step: 0.1,
|
|
172
|
+
label: 'Bass Reactivity',
|
|
173
|
+
description: 'How much particles react to bass frequencies',
|
|
174
|
+
group: 'audio',
|
|
175
|
+
category: 'audio'
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const trebleReactivity = viji.slider(0.5, {
|
|
179
|
+
min: 0,
|
|
180
|
+
max: 2.0,
|
|
181
|
+
step: 0.1,
|
|
182
|
+
label: 'Treble Reactivity',
|
|
183
|
+
description: 'How much particles react to treble frequencies',
|
|
184
|
+
group: 'audio',
|
|
185
|
+
category: 'audio'
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const showAudioViz = viji.toggle(true, {
|
|
189
|
+
label: 'Show Audio Visualization',
|
|
190
|
+
description: 'Display real-time audio analysis',
|
|
191
|
+
group: 'audio',
|
|
192
|
+
category: 'audio'
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Video Parameters
|
|
196
|
+
const videoReactive = viji.toggle(false, {
|
|
197
|
+
label: 'Video Reactive',
|
|
198
|
+
description: 'Make particles react to video input',
|
|
199
|
+
group: 'video',
|
|
200
|
+
category: 'video'
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const motionSensitivity = viji.slider(1.0, {
|
|
204
|
+
min: 0.1,
|
|
205
|
+
max: 3.0,
|
|
206
|
+
step: 0.1,
|
|
207
|
+
label: 'Motion Sensitivity',
|
|
208
|
+
description: 'How sensitive particles are to video motion',
|
|
209
|
+
group: 'video',
|
|
210
|
+
category: 'video'
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const brightnessReactivity = viji.slider(0.5, {
|
|
214
|
+
min: 0,
|
|
215
|
+
max: 2.0,
|
|
216
|
+
step: 0.1,
|
|
217
|
+
label: 'Brightness Reactivity',
|
|
218
|
+
description: 'How much particles react to video brightness',
|
|
219
|
+
group: 'video',
|
|
220
|
+
category: 'video'
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const showVideoOverlay = viji.toggle(true, {
|
|
224
|
+
label: 'Show Video Overlay',
|
|
225
|
+
description: 'Display current video frame on canvas',
|
|
226
|
+
group: 'video',
|
|
227
|
+
category: 'video'
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Render function using parameter object API
|
|
231
|
+
function render(viji) {
|
|
232
|
+
const ctx = viji.useContext('2d');
|
|
233
|
+
|
|
234
|
+
// Get interaction state
|
|
235
|
+
const mouse = viji.mouse;
|
|
236
|
+
const keyboard = viji.keyboard;
|
|
237
|
+
const touches = viji.touches;
|
|
238
|
+
|
|
239
|
+
// Get audio state
|
|
240
|
+
const audio = viji.audio;
|
|
241
|
+
|
|
242
|
+
// Get video state
|
|
243
|
+
const video = viji.video;
|
|
244
|
+
|
|
245
|
+
// Clear canvas with trails effect if enabled
|
|
246
|
+
if (enableTrails.value) {
|
|
247
|
+
const alpha = Math.floor(0.1 * opacity.value * 255).toString(16).padStart(2, '0');
|
|
248
|
+
ctx.fillStyle = backgroundColor.value + alpha;
|
|
249
|
+
ctx.fillRect(0, 0, viji.width, viji.height);
|
|
250
|
+
} else {
|
|
251
|
+
ctx.fillStyle = backgroundColor.value;
|
|
252
|
+
ctx.fillRect(0, 0, viji.width, viji.height);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Draw title
|
|
256
|
+
ctx.fillStyle = primaryColor.value;
|
|
257
|
+
ctx.font = 'bold 24px Arial';
|
|
258
|
+
ctx.textAlign = 'center';
|
|
259
|
+
ctx.fillText(sceneTitle.value, viji.width / 2, 40);
|
|
260
|
+
|
|
261
|
+
// Keyboard offset for particle center
|
|
262
|
+
let keyboardOffsetX = 0;
|
|
263
|
+
let keyboardOffsetY = 0;
|
|
264
|
+
|
|
265
|
+
if (keyboardControl.value && keyboard) {
|
|
266
|
+
const keySpeed = 3;
|
|
267
|
+
const isPressed = (k) => (keyboard && typeof keyboard.isPressed === 'function' ? keyboard.isPressed(k) : false);
|
|
268
|
+
if (isPressed('w') || isPressed('W')) keyboardOffsetY -= keySpeed;
|
|
269
|
+
if (isPressed('s') || isPressed('S')) keyboardOffsetY += keySpeed;
|
|
270
|
+
if (isPressed('a') || isPressed('A')) keyboardOffsetX -= keySpeed;
|
|
271
|
+
if (isPressed('d') || isPressed('D')) keyboardOffsetX += keySpeed;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Animation calculations
|
|
275
|
+
let centerX = viji.width / 2 + keyboardOffsetX;
|
|
276
|
+
let centerY = viji.height / 2 + keyboardOffsetY;
|
|
277
|
+
const animTime = viji.time * animationSpeed.value;
|
|
278
|
+
|
|
279
|
+
// Audio reactive calculations
|
|
280
|
+
let audioScale = 1;
|
|
281
|
+
let audioColorShift = 0;
|
|
282
|
+
if (audioReactive.value && audio && audio.isConnected) {
|
|
283
|
+
audioScale = 1 + ((((audio.volume && audio.volume.rms) || 0)) * volumeSensitivity.value);
|
|
284
|
+
|
|
285
|
+
const bassEnergy = ((((audio && audio.bands && audio.bands.bass) || 0) + ((audio && audio.bands && audio.bands.subBass) || 0)) / 2);
|
|
286
|
+
const trebleEnergy = ((((audio && audio.bands && audio.bands.treble) || 0) + ((audio && audio.bands && audio.bands.presence) || 0)) / 2);
|
|
287
|
+
audioColorShift = (bassEnergy * bassReactivity.value) - (trebleEnergy * trebleReactivity.value);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Video reactive calculations
|
|
291
|
+
let videoScale = 1;
|
|
292
|
+
if (videoReactive.value && video && video.isConnected) {
|
|
293
|
+
const motionEnergy = Math.sin(animTime * 2) * 0.5 + 0.5; // Simulate motion
|
|
294
|
+
videoScale = 1 + (motionEnergy * motionSensitivity.value * 0.3);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Touch interaction - use primary touch for center offset
|
|
298
|
+
if (touches && touches.points && touches.points.length > 0) {
|
|
299
|
+
const primaryTouch = touches.points[0];
|
|
300
|
+
centerX = primaryTouch.x * touchScale.value;
|
|
301
|
+
centerY = primaryTouch.y * touchScale.value;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Draw particles
|
|
305
|
+
for (let i = 0; i < particleCount.value; i++) {
|
|
306
|
+
const angle = (i / particleCount.value) * Math.PI * 2 + animTime;
|
|
307
|
+
const radius = Math.min(viji.width, viji.height) * 0.25;
|
|
308
|
+
|
|
309
|
+
let x = centerX + Math.cos(angle) * radius;
|
|
310
|
+
let y = centerY + Math.sin(angle) * radius;
|
|
311
|
+
|
|
312
|
+
// Mouse attraction
|
|
313
|
+
if (mouseAttraction.value && mouse && mouse.isInCanvas) {
|
|
314
|
+
const mouseX = mouse.x;
|
|
315
|
+
const mouseY = mouse.y;
|
|
316
|
+
const distToMouse = Math.sqrt((x - mouseX) ** 2 + (y - mouseY) ** 2);
|
|
317
|
+
|
|
318
|
+
if (distToMouse > 0) {
|
|
319
|
+
const attraction = attractionStrength.value * (1 / (distToMouse * 0.01 + 1));
|
|
320
|
+
x += (mouseX - x) * attraction;
|
|
321
|
+
y += (mouseY - y) * attraction;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Apply rotation
|
|
326
|
+
const rotation = animTime * rotationSpeed.value;
|
|
327
|
+
|
|
328
|
+
ctx.save();
|
|
329
|
+
ctx.translate(x, y);
|
|
330
|
+
ctx.rotate(rotation);
|
|
331
|
+
|
|
332
|
+
// Set opacity
|
|
333
|
+
ctx.globalAlpha = opacity.value;
|
|
334
|
+
|
|
335
|
+
let scaledSize = shapeSize.value * Math.min(viji.width, viji.height) / 1000;
|
|
336
|
+
|
|
337
|
+
// Apply audio scaling
|
|
338
|
+
if (audioReactive.value && audio && audio.isConnected) {
|
|
339
|
+
scaledSize *= audioScale;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Apply video scaling
|
|
343
|
+
if (videoReactive.value && video && video.isConnected) {
|
|
344
|
+
scaledSize *= videoScale;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Glow effect
|
|
348
|
+
if (glowIntensity.value > 0) {
|
|
349
|
+
const glowSize = scaledSize * (2 + glowIntensity.value);
|
|
350
|
+
const gradient = ctx.createRadialGradient(0, 0, scaledSize / 2, 0, 0, glowSize / 2);
|
|
351
|
+
gradient.addColorStop(0, primaryColor.value);
|
|
352
|
+
gradient.addColorStop(1, primaryColor.value + '00');
|
|
353
|
+
|
|
354
|
+
ctx.fillStyle = gradient;
|
|
355
|
+
ctx.beginPath();
|
|
356
|
+
ctx.arc(0, 0, glowSize / 2, 0, Math.PI * 2);
|
|
357
|
+
ctx.fill();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Create gradient or solid fill
|
|
361
|
+
if (useGradient.value) {
|
|
362
|
+
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, scaledSize / 2);
|
|
363
|
+
|
|
364
|
+
// Apply audio color shifting
|
|
365
|
+
let color1 = primaryColor.value;
|
|
366
|
+
let color2 = secondaryColor.value;
|
|
367
|
+
|
|
368
|
+
if (audioReactive.value && audio && audio.isConnected && audioColorShift !== 0) {
|
|
369
|
+
// Simple color shifting effect
|
|
370
|
+
const audioHueShift = audioColorShift * 60;
|
|
371
|
+
color1 = adjustHue(primaryColor.value, audioHueShift);
|
|
372
|
+
color2 = adjustHue(secondaryColor.value, audioHueShift);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
gradient.addColorStop(0, color1);
|
|
376
|
+
gradient.addColorStop(1, color2);
|
|
377
|
+
ctx.fillStyle = gradient;
|
|
378
|
+
} else {
|
|
379
|
+
ctx.fillStyle = primaryColor.value;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Draw shape based on type
|
|
383
|
+
ctx.beginPath();
|
|
384
|
+
switch (shapeType.value) {
|
|
385
|
+
case 'circle':
|
|
386
|
+
ctx.arc(0, 0, scaledSize / 2, 0, Math.PI * 2);
|
|
387
|
+
break;
|
|
388
|
+
|
|
389
|
+
case 'square':
|
|
390
|
+
const half = scaledSize / 2;
|
|
391
|
+
ctx.rect(-half, -half, scaledSize, scaledSize);
|
|
392
|
+
break;
|
|
393
|
+
|
|
394
|
+
case 'triangle':
|
|
395
|
+
const size = scaledSize;
|
|
396
|
+
ctx.moveTo(0, -size / 2);
|
|
397
|
+
ctx.lineTo(-size / 2, size / 2);
|
|
398
|
+
ctx.lineTo(size / 2, size / 2);
|
|
399
|
+
ctx.closePath();
|
|
400
|
+
break;
|
|
401
|
+
|
|
402
|
+
case 'hexagon':
|
|
403
|
+
const hexRadius = scaledSize / 2;
|
|
404
|
+
for (let j = 0; j < 6; j++) {
|
|
405
|
+
const hexAngle = (j / 6) * Math.PI * 2;
|
|
406
|
+
const hexX = Math.cos(hexAngle) * hexRadius;
|
|
407
|
+
const hexY = Math.sin(hexAngle) * hexRadius;
|
|
408
|
+
if (j === 0) ctx.moveTo(hexX, hexY);
|
|
409
|
+
else ctx.lineTo(hexX, hexY);
|
|
410
|
+
}
|
|
411
|
+
ctx.closePath();
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
ctx.fill();
|
|
415
|
+
|
|
416
|
+
ctx.restore();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Draw mouse cursor indicator
|
|
420
|
+
if (mouseAttraction.value && mouse && mouse.isInCanvas) {
|
|
421
|
+
ctx.strokeStyle = primaryColor.value + '80';
|
|
422
|
+
ctx.lineWidth = 2;
|
|
423
|
+
ctx.setLineDash([5, 5]);
|
|
424
|
+
ctx.beginPath();
|
|
425
|
+
ctx.arc(mouse.x, mouse.y, 25, 0, Math.PI * 2);
|
|
426
|
+
ctx.stroke();
|
|
427
|
+
ctx.setLineDash([]);
|
|
428
|
+
|
|
429
|
+
// Draw crosshair
|
|
430
|
+
ctx.strokeStyle = primaryColor.value + 'CC';
|
|
431
|
+
ctx.lineWidth = 1;
|
|
432
|
+
ctx.beginPath();
|
|
433
|
+
ctx.moveTo(mouse.x - 10, mouse.y);
|
|
434
|
+
ctx.lineTo(mouse.x + 10, mouse.y);
|
|
435
|
+
ctx.moveTo(mouse.x, mouse.y - 10);
|
|
436
|
+
ctx.lineTo(mouse.x, mouse.y + 10);
|
|
437
|
+
ctx.stroke();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Draw debug info
|
|
441
|
+
if (showDebugInfo.value) {
|
|
442
|
+
ctx.fillStyle = secondaryColor.value;
|
|
443
|
+
ctx.font = '12px monospace';
|
|
444
|
+
ctx.textAlign = 'left';
|
|
445
|
+
|
|
446
|
+
let debugY = viji.height - 120;
|
|
447
|
+
ctx.fillText(`Frame: ${viji.frameCount}`, 10, debugY);
|
|
448
|
+
debugY += 15;
|
|
449
|
+
ctx.fillText(`Time: ${viji.time.toFixed(2)}s`, 10, debugY);
|
|
450
|
+
debugY += 15;
|
|
451
|
+
ctx.fillText(`Particles: ${particleCount.value}`, 10, debugY);
|
|
452
|
+
debugY += 15;
|
|
453
|
+
ctx.fillText(`Shape: ${shapeType.value} (${shapeSize.value}px)`, 10, debugY);
|
|
454
|
+
debugY += 15;
|
|
455
|
+
ctx.fillText(`Animation: ${animationSpeed.value.toFixed(1)}x`, 10, debugY);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Draw interaction info
|
|
459
|
+
if (showInteractionInfo.value) {
|
|
460
|
+
ctx.fillStyle = primaryColor.value + 'CC';
|
|
461
|
+
ctx.font = '12px monospace';
|
|
462
|
+
ctx.textAlign = 'right';
|
|
463
|
+
|
|
464
|
+
let infoY = viji.height - 140;
|
|
465
|
+
|
|
466
|
+
ctx.fillText('INTERACTIONS:', viji.width - 10, infoY);
|
|
467
|
+
infoY += 15;
|
|
468
|
+
|
|
469
|
+
if (mouse && mouse.isInCanvas) {
|
|
470
|
+
ctx.fillText(`Mouse: (${Math.round(mouse.x)}, ${Math.round(mouse.y)})`, viji.width - 10, infoY);
|
|
471
|
+
infoY += 15;
|
|
472
|
+
const buttons = (mouse.leftButton ? 'L' : '-') + (mouse.rightButton ? 'R' : '-') + (mouse.middleButton ? 'M' : '-');
|
|
473
|
+
ctx.fillText(`Buttons: ${buttons}`, viji.width - 10, infoY);
|
|
474
|
+
infoY += 15;
|
|
475
|
+
} else {
|
|
476
|
+
ctx.fillText('Mouse: outside canvas', viji.width - 10, infoY);
|
|
477
|
+
infoY += 15;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (keyboardControl.value && keyboard) {
|
|
481
|
+
const activeKeys = Array.from(keyboard.activeKeys || []);
|
|
482
|
+
ctx.fillText(`Keys: ${activeKeys.length > 0 ? activeKeys.slice(0, 3).join(', ') : 'none'}`, viji.width - 10, infoY);
|
|
483
|
+
infoY += 15;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
ctx.fillText(`Touches: ${(touches && touches.points ? touches.points.length : 0)}`, viji.width - 10, infoY);
|
|
487
|
+
infoY += 15;
|
|
488
|
+
|
|
489
|
+
if (touches && touches.points && touches.points.length > 0) {
|
|
490
|
+
const touch0 = touches.points[0];
|
|
491
|
+
ctx.fillText(`Touch: (${Math.round(touch0.x)}, ${Math.round(touch0.y)})`, viji.width - 10, infoY);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Draw audio visualization
|
|
496
|
+
if (showAudioViz.value && audio && audio.isConnected) {
|
|
497
|
+
ctx.fillStyle = 'rgba(0, 255, 255, 0.9)';
|
|
498
|
+
ctx.font = '12px monospace';
|
|
499
|
+
ctx.textAlign = 'left';
|
|
500
|
+
|
|
501
|
+
let audioY = 60;
|
|
502
|
+
|
|
503
|
+
ctx.fillText('🎵 AUDIO ANALYSIS:', 10, audioY);
|
|
504
|
+
audioY += 15;
|
|
505
|
+
ctx.fillText(`Volume: ${((audio.volume?.rms || 0) * 100).toFixed(1)}%`, 10, audioY);
|
|
506
|
+
audioY += 15;
|
|
507
|
+
ctx.fillText(`Peak: ${((audio.volume?.peak || 0) * 100).toFixed(1)}%`, 10, audioY);
|
|
508
|
+
audioY += 15;
|
|
509
|
+
|
|
510
|
+
// Frequency bands
|
|
511
|
+
const bands = ['bass', 'mid', 'treble'];
|
|
512
|
+
for (const band of bands) {
|
|
513
|
+
const value = (audio && audio.bands ? (audio.bands[band] || 0) : 0);
|
|
514
|
+
ctx.fillText(`${band}: ${(value * 100).toFixed(1)}%`, 10, audioY);
|
|
515
|
+
audioY += 15;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Visual frequency bars
|
|
519
|
+
const barWidth = 4;
|
|
520
|
+
const barSpacing = 6;
|
|
521
|
+
const barHeight = 40;
|
|
522
|
+
const barStartX = 10;
|
|
523
|
+
const barStartY = audioY + 5;
|
|
524
|
+
|
|
525
|
+
const allBands = ['subBass', 'bass', 'lowMid', 'mid', 'highMid', 'presence', 'brilliance', 'treble'];
|
|
526
|
+
|
|
527
|
+
for (let i = 0; i < allBands.length; i++) {
|
|
528
|
+
const bandValue = (audio && audio.bands ? (audio.bands[allBands[i]] || 0) : 0);
|
|
529
|
+
const x = barStartX + i * barSpacing;
|
|
530
|
+
const barHeightValue = bandValue * barHeight;
|
|
531
|
+
|
|
532
|
+
// Background bar
|
|
533
|
+
ctx.fillStyle = 'rgba(100, 100, 100, 0.3)';
|
|
534
|
+
ctx.fillRect(x, barStartY, barWidth, barHeight);
|
|
535
|
+
|
|
536
|
+
// Value bar with color
|
|
537
|
+
const barHue = (i / allBands.length) * 300;
|
|
538
|
+
ctx.fillStyle = `hsla(${barHue}, 70%, 60%, 0.8)`;
|
|
539
|
+
ctx.fillRect(x, barStartY + barHeight - barHeightValue, barWidth, barHeightValue);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Draw video overlay
|
|
544
|
+
if (showVideoOverlay.value && video && video.isConnected && video.currentFrame) {
|
|
545
|
+
ctx.save();
|
|
546
|
+
ctx.globalAlpha = 0.3;
|
|
547
|
+
|
|
548
|
+
// Calculate video display size
|
|
549
|
+
const videoWidth = video.frameWidth || 320;
|
|
550
|
+
const videoHeight = video.frameHeight || 240;
|
|
551
|
+
const displayWidth = 160;
|
|
552
|
+
const displayHeight = (videoHeight / videoWidth) * displayWidth;
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
ctx.drawImage(
|
|
556
|
+
video.currentFrame,
|
|
557
|
+
0, 0, videoWidth, videoHeight,
|
|
558
|
+
viji.width - displayWidth - 10, 80, displayWidth, displayHeight
|
|
559
|
+
);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
// Fallback
|
|
562
|
+
ctx.fillStyle = 'rgba(255, 100, 100, 0.5)';
|
|
563
|
+
ctx.fillRect(viji.width - displayWidth - 10, 80, displayWidth, displayHeight);
|
|
564
|
+
ctx.fillStyle = 'white';
|
|
565
|
+
ctx.font = '10px monospace';
|
|
566
|
+
ctx.textAlign = 'center';
|
|
567
|
+
ctx.fillText('Video', viji.width - displayWidth/2 - 10, 95);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
ctx.restore();
|
|
571
|
+
|
|
572
|
+
// Video info
|
|
573
|
+
ctx.fillStyle = 'rgba(255, 255, 0, 0.9)';
|
|
574
|
+
ctx.font = '10px monospace';
|
|
575
|
+
ctx.textAlign = 'right';
|
|
576
|
+
|
|
577
|
+
let videoInfoY = 250;
|
|
578
|
+
ctx.fillText('📹 VIDEO:', viji.width - 10, videoInfoY);
|
|
579
|
+
videoInfoY += 12;
|
|
580
|
+
ctx.fillText(`${videoWidth}x${videoHeight}`, viji.width - 10, videoInfoY);
|
|
581
|
+
videoInfoY += 12;
|
|
582
|
+
ctx.fillText(`${(video.frameRate || 30).toFixed(1)} FPS`, viji.width - 10, videoInfoY);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Helper function for color hue adjustment
|
|
587
|
+
function adjustHue(hexColor, degrees) {
|
|
588
|
+
// Simple hue adjustment - in a real implementation this would be more sophisticated
|
|
589
|
+
const hex = hexColor.replace('#', '');
|
|
590
|
+
const r = parseInt(hex.substr(0, 2), 16) / 255;
|
|
591
|
+
const g = parseInt(hex.substr(2, 2), 16) / 255;
|
|
592
|
+
const b = parseInt(hex.substr(4, 2), 16) / 255;
|
|
593
|
+
|
|
594
|
+
// Convert to HSL, adjust hue, convert back to RGB
|
|
595
|
+
const max = Math.max(r, g, b);
|
|
596
|
+
const min = Math.min(r, g, b);
|
|
597
|
+
let h = 0;
|
|
598
|
+
|
|
599
|
+
if (max !== min) {
|
|
600
|
+
const d = max - min;
|
|
601
|
+
switch (max) {
|
|
602
|
+
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
|
603
|
+
case g: h = (b - r) / d + 2; break;
|
|
604
|
+
case b: h = (r - g) / d + 4; break;
|
|
605
|
+
}
|
|
606
|
+
h /= 6;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Adjust hue
|
|
610
|
+
h = (h + degrees / 360) % 1;
|
|
611
|
+
if (h < 0) h += 1;
|
|
612
|
+
|
|
613
|
+
// Convert back to RGB (simplified)
|
|
614
|
+
const newR = Math.round(Math.max(0, Math.min(255, r * 255 + Math.sin(h * Math.PI * 2) * 50)));
|
|
615
|
+
const newG = Math.round(Math.max(0, Math.min(255, g * 255 + Math.cos(h * Math.PI * 2) * 50)));
|
|
616
|
+
const newB = Math.round(Math.max(0, Math.min(255, b * 255 + Math.sin(h * Math.PI * 4) * 50)));
|
|
617
|
+
|
|
618
|
+
return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`;
|
|
619
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Ambient types for Viji scenes in this folder only.
|
|
2
|
+
// This file is editor-only; it is not bundled into artist code.
|
|
3
|
+
/// <reference types="@viji-dev/core/artist-global" />
|
|
4
|
+
|
|
5
|
+
// Some older bundles referenced ArtistAPI; provide a compatibility alias.
|
|
6
|
+
declare namespace VijiCore {
|
|
7
|
+
type ArtistAPI = VijiAPI;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Tell the type-checker that a global render function is observed externally by runtime
|
|
11
|
+
declare function render(viji: any): void;
|
|
12
|
+
// This prevents editor "unused" noise without requiring artists to add code
|
|
13
|
+
// (Implementations defined per scene are consumed by Viji Core worker)
|
|
14
|
+
|
|
15
|
+
|