@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,349 @@
|
|
|
1
|
+
// Interactive Particle System Scene - Parameter Object API
|
|
2
|
+
console.log('⚡ PARTICLE SYSTEM SCENE LOADED');
|
|
3
|
+
|
|
4
|
+
// Define parameters using helper functions (returns parameter objects)
|
|
5
|
+
const particleCount = viji.slider(150, {
|
|
6
|
+
min: 10,
|
|
7
|
+
max: 500,
|
|
8
|
+
step: 10,
|
|
9
|
+
label: 'Particle Count',
|
|
10
|
+
description: 'Number of particles in the system',
|
|
11
|
+
group: 'particles'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const particleSize = viji.slider(3, {
|
|
15
|
+
min: 1,
|
|
16
|
+
max: 10,
|
|
17
|
+
step: 0.5,
|
|
18
|
+
label: 'Particle Size',
|
|
19
|
+
description: 'Base size of particles',
|
|
20
|
+
group: 'particles'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const particleLifetime = viji.slider(5.0, {
|
|
24
|
+
min: 1.0,
|
|
25
|
+
max: 20.0,
|
|
26
|
+
step: 0.5,
|
|
27
|
+
label: 'Particle Lifetime',
|
|
28
|
+
description: 'How long particles live (seconds)',
|
|
29
|
+
group: 'particles'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const gravity = viji.slider(0.1, {
|
|
33
|
+
min: -1.0,
|
|
34
|
+
max: 2.0,
|
|
35
|
+
step: 0.05,
|
|
36
|
+
label: 'Gravity',
|
|
37
|
+
description: 'Downward force on particles',
|
|
38
|
+
group: 'physics'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const airResistance = viji.slider(0.98, {
|
|
42
|
+
min: 0.9,
|
|
43
|
+
max: 1.0,
|
|
44
|
+
step: 0.005,
|
|
45
|
+
label: 'Air Resistance',
|
|
46
|
+
description: 'Velocity dampening factor',
|
|
47
|
+
group: 'physics'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const bounce = viji.slider(0.7, {
|
|
51
|
+
min: 0.0,
|
|
52
|
+
max: 1.0,
|
|
53
|
+
step: 0.05,
|
|
54
|
+
label: 'Bounce',
|
|
55
|
+
description: 'Energy retained on collision',
|
|
56
|
+
group: 'physics'
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const friction = viji.slider(0.9, {
|
|
60
|
+
min: 0.5,
|
|
61
|
+
max: 1.0,
|
|
62
|
+
step: 0.05,
|
|
63
|
+
label: 'Friction',
|
|
64
|
+
description: 'Friction when particles hit ground',
|
|
65
|
+
group: 'physics'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const mouseInteraction = viji.toggle(true, {
|
|
69
|
+
label: 'Mouse Interaction',
|
|
70
|
+
description: 'Particles react to mouse movement',
|
|
71
|
+
group: 'interaction',
|
|
72
|
+
category: 'interaction'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const mouseForce = viji.slider(5.0, {
|
|
76
|
+
min: 0.1,
|
|
77
|
+
max: 20.0,
|
|
78
|
+
step: 0.5,
|
|
79
|
+
label: 'Mouse Force',
|
|
80
|
+
description: 'Strength of mouse attraction/repulsion',
|
|
81
|
+
group: 'interaction',
|
|
82
|
+
category: 'interaction'
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const mouseRadius = viji.slider(100, {
|
|
86
|
+
min: 20,
|
|
87
|
+
max: 300,
|
|
88
|
+
step: 10,
|
|
89
|
+
label: 'Mouse Radius',
|
|
90
|
+
description: 'Range of mouse influence',
|
|
91
|
+
group: 'interaction',
|
|
92
|
+
category: 'interaction'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const attractMode = viji.toggle(false, {
|
|
96
|
+
label: 'Attract Mode',
|
|
97
|
+
description: 'Attract particles to mouse (vs repel)',
|
|
98
|
+
group: 'interaction',
|
|
99
|
+
category: 'interaction'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const colorMode = viji.select('velocity', {
|
|
103
|
+
options: ['velocity', 'age', 'position', 'static'],
|
|
104
|
+
label: 'Color Mode',
|
|
105
|
+
description: 'How particles are colored',
|
|
106
|
+
group: 'colors'
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const staticColor = viji.color('#ff6b6b', {
|
|
110
|
+
label: 'Static Color',
|
|
111
|
+
description: 'Color when using static color mode',
|
|
112
|
+
group: 'colors'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const backgroundColor = viji.color('#0a0a0a', {
|
|
116
|
+
label: 'Background Color',
|
|
117
|
+
description: 'Scene background color',
|
|
118
|
+
group: 'colors'
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Scene state
|
|
122
|
+
let particles = [];
|
|
123
|
+
let frameTime = 0;
|
|
124
|
+
|
|
125
|
+
class Particle {
|
|
126
|
+
constructor(x, y) {
|
|
127
|
+
this.x = x;
|
|
128
|
+
this.y = y;
|
|
129
|
+
this.vx = (Math.random() - 0.5) * 4;
|
|
130
|
+
this.vy = (Math.random() - 0.5) * 4;
|
|
131
|
+
this.age = 0;
|
|
132
|
+
this.maxAge = particleLifetime.value + (Math.random() - 0.5) * 2;
|
|
133
|
+
this.size = particleSize.value + (Math.random() - 0.5) * 2;
|
|
134
|
+
this.id = Math.random();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
update(deltaTime, mouse) {
|
|
138
|
+
const dt = deltaTime / 16.67; // Normalize to ~60fps
|
|
139
|
+
|
|
140
|
+
// Age the particle
|
|
141
|
+
this.age += dt;
|
|
142
|
+
|
|
143
|
+
// Apply physics
|
|
144
|
+
this.vy += gravity.value * dt;
|
|
145
|
+
this.vx *= Math.pow(airResistance.value, dt);
|
|
146
|
+
this.vy *= Math.pow(airResistance.value, dt);
|
|
147
|
+
|
|
148
|
+
// Mouse interaction
|
|
149
|
+
if (mouseInteraction.value && mouse && mouse.isInCanvas) {
|
|
150
|
+
const dx = mouse.x - this.x;
|
|
151
|
+
const dy = mouse.y - this.y;
|
|
152
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
153
|
+
|
|
154
|
+
if (dist < mouseRadius.value && dist > 0) {
|
|
155
|
+
const force = mouseForce.value * (1 - dist / mouseRadius.value);
|
|
156
|
+
const angle = Math.atan2(dy, dx);
|
|
157
|
+
|
|
158
|
+
if (attractMode.value) {
|
|
159
|
+
// Attract to mouse
|
|
160
|
+
this.vx += Math.cos(angle) * force * dt;
|
|
161
|
+
this.vy += Math.sin(angle) * force * dt;
|
|
162
|
+
} else {
|
|
163
|
+
// Repel from mouse
|
|
164
|
+
this.vx -= Math.cos(angle) * force * dt;
|
|
165
|
+
this.vy -= Math.sin(angle) * force * dt;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Update position
|
|
171
|
+
this.x += this.vx * dt;
|
|
172
|
+
this.y += this.vy * dt;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
checkCollisions(width, height) {
|
|
176
|
+
// Floor collision
|
|
177
|
+
if (this.y + this.size > height) {
|
|
178
|
+
this.y = height - this.size;
|
|
179
|
+
this.vy *= -bounce.value;
|
|
180
|
+
this.vx *= friction.value;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Ceiling collision
|
|
184
|
+
if (this.y - this.size < 0) {
|
|
185
|
+
this.y = this.size;
|
|
186
|
+
this.vy *= -bounce.value;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Wall collisions
|
|
190
|
+
if (this.x + this.size > width) {
|
|
191
|
+
this.x = width - this.size;
|
|
192
|
+
this.vx *= -bounce.value;
|
|
193
|
+
}
|
|
194
|
+
if (this.x - this.size < 0) {
|
|
195
|
+
this.x = this.size;
|
|
196
|
+
this.vx *= -bounce.value;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
isDead() {
|
|
201
|
+
return this.age >= this.maxAge;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
getColor() {
|
|
205
|
+
switch (colorMode.value) {
|
|
206
|
+
case 'velocity':
|
|
207
|
+
const speed = Math.sqrt(this.vx * this.vx + this.vy * this.vy);
|
|
208
|
+
const hue = Math.min(speed * 20, 360);
|
|
209
|
+
return `hsl(${hue}, 80%, 60%)`;
|
|
210
|
+
|
|
211
|
+
case 'age':
|
|
212
|
+
const ageRatio = this.age / this.maxAge;
|
|
213
|
+
const ageBrightness = Math.max(0, 1 - ageRatio);
|
|
214
|
+
return `hsl(30, 80%, ${ageBrightness * 60 + 20}%)`;
|
|
215
|
+
|
|
216
|
+
case 'position':
|
|
217
|
+
const positionHue = (this.x / 800) * 360; // Assumes ~800px width
|
|
218
|
+
return `hsl(${positionHue}, 70%, 50%)`;
|
|
219
|
+
|
|
220
|
+
case 'static':
|
|
221
|
+
default:
|
|
222
|
+
return staticColor.value;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function spawnParticle(x, y) {
|
|
228
|
+
if (particles.length < particleCount.value) {
|
|
229
|
+
particles.push(new Particle(x, y));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function spawnParticles(count, width, height) {
|
|
234
|
+
for (let i = 0; i < count; i++) {
|
|
235
|
+
if (particles.length < particleCount.value) {
|
|
236
|
+
const x = Math.random() * width;
|
|
237
|
+
const y = Math.random() * height * 0.5; // Spawn in upper half
|
|
238
|
+
spawnParticle(x, y);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Render function using parameter object API
|
|
244
|
+
function render(viji) {
|
|
245
|
+
const ctx = viji.useContext('2d');
|
|
246
|
+
const mouse = viji.mouse;
|
|
247
|
+
|
|
248
|
+
frameTime += viji.deltaTime;
|
|
249
|
+
|
|
250
|
+
// Clear background
|
|
251
|
+
ctx.fillStyle = backgroundColor.value;
|
|
252
|
+
ctx.fillRect(0, 0, viji.width, viji.height);
|
|
253
|
+
|
|
254
|
+
// Maintain particle count
|
|
255
|
+
const targetCount = particleCount.value;
|
|
256
|
+
if (particles.length < targetCount) {
|
|
257
|
+
const needed = Math.min(5, targetCount - particles.length); // Spawn gradually
|
|
258
|
+
spawnParticles(needed, viji.width, viji.height);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Update particles
|
|
262
|
+
for (let i = particles.length - 1; i >= 0; i--) {
|
|
263
|
+
const particle = particles[i];
|
|
264
|
+
|
|
265
|
+
particle.update(viji.deltaTime, mouse);
|
|
266
|
+
particle.checkCollisions(viji.width, viji.height);
|
|
267
|
+
|
|
268
|
+
// Remove dead particles
|
|
269
|
+
if (particle.isDead() || particles.length > targetCount) {
|
|
270
|
+
particles.splice(i, 1);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Draw particles
|
|
276
|
+
particles.forEach(particle => {
|
|
277
|
+
ctx.fillStyle = particle.getColor();
|
|
278
|
+
ctx.beginPath();
|
|
279
|
+
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
|
280
|
+
ctx.fill();
|
|
281
|
+
|
|
282
|
+
// Add slight glow effect
|
|
283
|
+
const glowSize = particle.size * 2;
|
|
284
|
+
const gradient = ctx.createRadialGradient(
|
|
285
|
+
particle.x, particle.y, particle.size,
|
|
286
|
+
particle.x, particle.y, glowSize
|
|
287
|
+
);
|
|
288
|
+
// Convert color to rgba format with alpha transparency
|
|
289
|
+
const baseColor = particle.getColor();
|
|
290
|
+
let colorWithAlpha;
|
|
291
|
+
if (baseColor.startsWith('hsl(')) {
|
|
292
|
+
// Convert hsl(h, s, l) to hsla(h, s, l, a)
|
|
293
|
+
colorWithAlpha = baseColor.replace('hsl(', 'hsla(').replace(')', ', 0.25)');
|
|
294
|
+
} else if (baseColor.startsWith('#')) {
|
|
295
|
+
// Convert hex to rgba
|
|
296
|
+
const hex = baseColor.slice(1);
|
|
297
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
298
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
299
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
300
|
+
colorWithAlpha = `rgba(${r}, ${g}, ${b}, 0.25)`;
|
|
301
|
+
} else {
|
|
302
|
+
// Fallback for other color formats
|
|
303
|
+
colorWithAlpha = baseColor;
|
|
304
|
+
}
|
|
305
|
+
gradient.addColorStop(0, colorWithAlpha);
|
|
306
|
+
gradient.addColorStop(1, 'transparent');
|
|
307
|
+
ctx.fillStyle = gradient;
|
|
308
|
+
ctx.beginPath();
|
|
309
|
+
ctx.arc(particle.x, particle.y, glowSize, 0, Math.PI * 2);
|
|
310
|
+
ctx.fill();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Draw mouse interaction indicator
|
|
314
|
+
if (mouseInteraction.value && mouse && mouse.isInCanvas) {
|
|
315
|
+
ctx.strokeStyle = attractMode.value ? '#00ff00' : '#ff0000';
|
|
316
|
+
ctx.lineWidth = 2;
|
|
317
|
+
ctx.setLineDash([5, 5]);
|
|
318
|
+
ctx.beginPath();
|
|
319
|
+
ctx.arc(mouse.x, mouse.y, mouseRadius.value, 0, Math.PI * 2);
|
|
320
|
+
ctx.stroke();
|
|
321
|
+
ctx.setLineDash([]);
|
|
322
|
+
|
|
323
|
+
// Draw force indicator
|
|
324
|
+
ctx.fillStyle = attractMode.value ? 'rgba(0, 255, 0, 0.1)' : 'rgba(255, 0, 0, 0.1)';
|
|
325
|
+
ctx.beginPath();
|
|
326
|
+
ctx.arc(mouse.x, mouse.y, mouseRadius.value, 0, Math.PI * 2);
|
|
327
|
+
ctx.fill();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Draw info
|
|
331
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
|
332
|
+
ctx.font = '16px monospace';
|
|
333
|
+
ctx.textAlign = 'left';
|
|
334
|
+
ctx.fillText(`🎯 PARTICLE SYSTEM`, 20, 30);
|
|
335
|
+
|
|
336
|
+
ctx.font = '12px monospace';
|
|
337
|
+
ctx.fillText(`Particles: ${particles.length} / ${particleCount.value}`, 20, 55);
|
|
338
|
+
ctx.fillText(`Physics: G=${gravity.value} AR=${airResistance.value}`, 20, 75);
|
|
339
|
+
ctx.fillText(`Color Mode: ${colorMode.value}`, 20, 95);
|
|
340
|
+
|
|
341
|
+
if (mouseInteraction.value && mouse && mouse.isInCanvas) {
|
|
342
|
+
ctx.fillText(`Mouse: ${attractMode.value ? 'ATTRACT' : 'REPEL'} (Force: ${mouseForce.value})`, 20, 115);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Draw frame info
|
|
346
|
+
ctx.textAlign = 'right';
|
|
347
|
+
ctx.fillText(`Frame: ${viji.frameCount}`, viji.width - 20, viji.height - 25);
|
|
348
|
+
ctx.fillText(`Time: ${viji.time.toFixed(1)}s`, viji.width - 20, viji.height - 10);
|
|
349
|
+
}
|