diff options
Diffstat (limited to 'src/demos.js')
| -rw-r--r-- | src/demos.js | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/src/demos.js b/src/demos.js new file mode 100644 index 0000000..1dd6785 --- /dev/null +++ b/src/demos.js @@ -0,0 +1,697 @@ +/** + * Preloaded Graphics Demos + * + * Each demo exports: + * - name: Display name + * - description: Short description + * - setup(app, state): Called once to create objects + * - update(app, state, objects): Called every frame + * - cleanup(app, objects): Called when switching demos + */ + +// ============================================================================ +// DEMO 1: BOUNCING PARTICLES +// ============================================================================ + +export const bouncingParticles = { + name: "Bouncing Particles", + description: "Colorful particles bouncing around the screen", + + setup(app, state) { + const particles = []; + const colors = [0xff6b6b, 0x4ecdc4, 0x45b7d1, 0xf9ca24, 0x6c5ce7]; + + for (let i = 0; i < 50; i++) { + const particle = new PIXI.Graphics(); + const size = 5 + Math.random() * 10; + particle.circle(0, 0, size); + particle.fill(colors[Math.floor(Math.random() * colors.length)]); + + particle.x = Math.random() * app.screen.width; + particle.y = Math.random() * app.screen.height; + particle.vx = (Math.random() - 0.5) * 8; + particle.vy = (Math.random() - 0.5) * 8; + particle.size = size; + + app.stage.addChild(particle); + particles.push(particle); + } + + return { particles }; + }, + + update(app, state, objects) { + objects.particles.forEach(p => { + p.x += p.vx; + p.y += p.vy; + + // Bounce off edges + if (p.x < p.size || p.x > app.screen.width - p.size) p.vx *= -1; + if (p.y < p.size || p.y > app.screen.height - p.size) p.vy *= -1; + + // Clamp to screen + p.x = Math.max(p.size, Math.min(app.screen.width - p.size, p.x)); + p.y = Math.max(p.size, Math.min(app.screen.height - p.size, p.y)); + }); + }, + + cleanup(app, objects) { + objects.particles.forEach(p => p.destroy()); + } +}; + +// ============================================================================ +// DEMO 2: SPIROGRAPH +// ============================================================================ + +export const spirograph = { + name: "Spirograph", + description: "Mesmerizing geometric spiral patterns", + + setup(app, state) { + const graphics = new PIXI.Graphics(); + app.stage.addChild(graphics); + + return { + graphics, + angle: 0, + points: [] + }; + }, + + update(app, state, objects) { + const cx = app.screen.width / 2; + const cy = app.screen.height / 2; + const t = state.state.time.current; + + // Generate new point + const r1 = 150; + const r2 = 50; + const r3 = 30; + + const x = cx + Math.cos(t * 0.5) * r1 + Math.cos(t * 2) * r2 + Math.cos(t * 5) * r3; + const y = cy + Math.sin(t * 0.5) * r1 + Math.sin(t * 2) * r2 + Math.sin(t * 5) * r3; + + objects.points.push({ x, y }); + + // Keep only last 500 points + if (objects.points.length > 500) { + objects.points.shift(); + } + + // Draw trail + objects.graphics.clear(); + if (objects.points.length > 1) { + for (let i = 1; i < objects.points.length; i++) { + const alpha = i / objects.points.length; + const hue = (i / objects.points.length) * 360; + objects.graphics.moveTo(objects.points[i-1].x, objects.points[i-1].y); + objects.graphics.lineTo(objects.points[i].x, objects.points[i].y); + objects.graphics.stroke({ width: 2, color: hslToHex(hue, 100, 60), alpha }); + } + } + }, + + cleanup(app, objects) { + objects.graphics.destroy(); + } +}; + +// ============================================================================ +// DEMO 3: STARFIELD +// ============================================================================ + +export const starfield = { + name: "Starfield", + description: "Flying through space at warp speed", + + setup(app, state) { + const stars = []; + + for (let i = 0; i < 200; i++) { + const star = new PIXI.Graphics(); + star.circle(0, 0, 2); + star.fill(0xffffff); + + star.x = (Math.random() - 0.5) * app.screen.width * 2; + star.y = (Math.random() - 0.5) * app.screen.height * 2; + star.z = Math.random() * 1000; + + app.stage.addChild(star); + stars.push(star); + } + + return { stars }; + }, + + update(app, state, objects) { + const cx = app.screen.width / 2; + const cy = app.screen.height / 2; + const speed = 5; + + objects.stars.forEach(star => { + star.z -= speed; + + if (star.z <= 0) { + star.z = 1000; + star.x = (Math.random() - 0.5) * app.screen.width * 2; + star.y = (Math.random() - 0.5) * app.screen.height * 2; + } + + const screenX = cx + (star.x / star.z) * 200; + const screenY = cy + (star.y / star.z) * 200; + const size = (1 - star.z / 1000) * 4 + 1; + + star.x = star.x; + star.y = star.y; + star.position.set(screenX, screenY); + star.scale.set(size); + star.alpha = 1 - star.z / 1000; + }); + }, + + cleanup(app, objects) { + objects.stars.forEach(s => s.destroy()); + } +}; + +// ============================================================================ +// DEMO 4: WAVE INTERFERENCE +// ============================================================================ + +export const waveInterference = { + name: "Wave Interference", + description: "Rippling wave patterns", + + setup(app, state) { + const gridSize = 20; + const cols = Math.floor(app.screen.width / gridSize); + const rows = Math.floor(app.screen.height / gridSize); + const circles = []; + + for (let i = 0; i < cols; i++) { + for (let j = 0; j < rows; j++) { + const circle = new PIXI.Graphics(); + circle.circle(0, 0, 4); + circle.fill(0x4ecdc4); + circle.x = i * gridSize + gridSize / 2; + circle.y = j * gridSize + gridSize / 2; + circle.baseX = circle.x; + circle.baseY = circle.y; + + app.stage.addChild(circle); + circles.push(circle); + } + } + + return { circles, sources: [ + { x: app.screen.width * 0.3, y: app.screen.height * 0.5 }, + { x: app.screen.width * 0.7, y: app.screen.height * 0.5 } + ]}; + }, + + update(app, state, objects) { + const t = state.state.time.current; + + objects.circles.forEach(c => { + let totalOffset = 0; + + objects.sources.forEach(source => { + const dx = c.baseX - source.x; + const dy = c.baseY - source.y; + const dist = Math.sqrt(dx * dx + dy * dy); + totalOffset += Math.sin(dist * 0.05 - t * 3) * 10; + }); + + c.y = c.baseY + totalOffset; + c.alpha = 0.3 + (Math.sin(totalOffset * 0.1) + 1) * 0.35; + }); + }, + + cleanup(app, objects) { + objects.circles.forEach(c => c.destroy()); + } +}; + +// ============================================================================ +// DEMO 5: CIRCLE PACKING +// ============================================================================ + +export const circlePacking = { + name: "Circle Packing", + description: "Organic growth simulation", + + setup(app, state) { + const circles = []; + return { circles, attempts: 0 }; + }, + + update(app, state, objects) { + // Try to add a new circle each frame + const maxAttempts = 100; + const maxCircles = 150; + + if (objects.circles.length >= maxCircles) return; + + for (let i = 0; i < 10; i++) { + const x = Math.random() * app.screen.width; + const y = Math.random() * app.screen.height; + const minRadius = 5; + const maxRadius = 60; + + let valid = true; + let radius = minRadius; + + // Find largest radius that doesn't overlap + for (let r = minRadius; r < maxRadius; r++) { + let overlaps = false; + + for (const other of objects.circles) { + const dx = x - other.x; + const dy = y - other.y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist < r + other.radius + 2) { + overlaps = true; + break; + } + } + + if (overlaps) { + break; + } + radius = r; + } + + if (radius > minRadius) { + const circle = new PIXI.Graphics(); + circle.circle(0, 0, radius); + const hue = (objects.circles.length * 137.5) % 360; + circle.fill(hslToHex(hue, 70, 60)); + circle.x = x; + circle.y = y; + circle.radius = radius; + + app.stage.addChild(circle); + objects.circles.push(circle); + break; + } + } + }, + + cleanup(app, objects) { + objects.circles.forEach(c => c.destroy()); + } +}; + +// ============================================================================ +// DEMO 6: PERLIN FLOW FIELD +// ============================================================================ + +export const flowField = { + name: "Flow Field", + description: "Particles following a noise field", + + setup(app, state) { + const particles = []; + const colors = [0xff6b6b, 0x4ecdc4, 0x45b7d1, 0xf9ca24, 0x6c5ce7, 0xfeca57]; + + for (let i = 0; i < 300; i++) { + const particle = new PIXI.Graphics(); + particle.circle(0, 0, 2); + particle.fill(colors[Math.floor(Math.random() * colors.length)]); + particle.alpha = 0.6; + + particle.x = Math.random() * app.screen.width; + particle.y = Math.random() * app.screen.height; + particle.vx = 0; + particle.vy = 0; + particle.color = colors[Math.floor(Math.random() * colors.length)]; + + app.stage.addChild(particle); + particles.push(particle); + } + + return { particles }; + }, + + update(app, state, objects) { + const t = state.state.time.current; + + objects.particles.forEach(p => { + // Simple noise-like function using sin/cos + const angle = noise(p.x * 0.005, p.y * 0.005, t * 0.3) * Math.PI * 2; + + p.vx += Math.cos(angle) * 0.3; + p.vy += Math.sin(angle) * 0.3; + + // Damping + p.vx *= 0.95; + p.vy *= 0.95; + + p.x += p.vx; + p.y += p.vy; + + // Wrap around screen + if (p.x < 0) p.x = app.screen.width; + if (p.x > app.screen.width) p.x = 0; + if (p.y < 0) p.y = app.screen.height; + if (p.y > app.screen.height) p.y = 0; + }); + }, + + cleanup(app, objects) { + objects.particles.forEach(p => p.destroy()); + } +}; + +// ============================================================================ +// DEMO 7: DNA HELIX +// ============================================================================ + +export const dnaHelix = { + name: "DNA Helix", + description: "Rotating double helix structure", + + setup(app, state) { + const helix1 = []; + const helix2 = []; + const connectors = []; + const segments = 40; + + for (let i = 0; i < segments; i++) { + const sphere1 = new PIXI.Graphics(); + sphere1.circle(0, 0, 8); + sphere1.fill(0x4ecdc4); + app.stage.addChild(sphere1); + helix1.push(sphere1); + + const sphere2 = new PIXI.Graphics(); + sphere2.circle(0, 0, 8); + sphere2.fill(0xff6b6b); + app.stage.addChild(sphere2); + helix2.push(sphere2); + + const connector = new PIXI.Graphics(); + app.stage.addChild(connector); + connectors.push(connector); + } + + return { helix1, helix2, connectors }; + }, + + update(app, state, objects) { + const t = state.state.time.current; + const cx = app.screen.width / 2; + const cy = app.screen.height / 2; + const radius = 100; + const height = app.screen.height * 0.8; + const spacing = height / objects.helix1.length; + + objects.helix1.forEach((sphere, i) => { + const y = i * spacing - height / 2 + cy; + const angle = t + i * 0.3; + const x = cx + Math.cos(angle) * radius; + const z = Math.sin(angle) * radius; + + sphere.x = x; + sphere.y = y; + sphere.scale.set(1 + z / 200); + sphere.alpha = 0.5 + z / 400; + }); + + objects.helix2.forEach((sphere, i) => { + const y = i * spacing - height / 2 + cy; + const angle = t + i * 0.3 + Math.PI; + const x = cx + Math.cos(angle) * radius; + const z = Math.sin(angle) * radius; + + sphere.x = x; + sphere.y = y; + sphere.scale.set(1 + z / 200); + sphere.alpha = 0.5 + z / 400; + }); + + // Draw connectors + objects.connectors.forEach((connector, i) => { + connector.clear(); + connector.moveTo(objects.helix1[i].x, objects.helix1[i].y); + connector.lineTo(objects.helix2[i].x, objects.helix2[i].y); + connector.stroke({ width: 2, color: 0x666666, alpha: 0.3 }); + }); + }, + + cleanup(app, objects) { + objects.helix1.forEach(s => s.destroy()); + objects.helix2.forEach(s => s.destroy()); + objects.connectors.forEach(c => c.destroy()); + } +}; + +// ============================================================================ +// DEMO 8: FIREWORKS +// ============================================================================ + +export const fireworks = { + name: "Fireworks", + description: "Explosive particle celebration", + + setup(app, state) { + return { + explosions: [], + nextExplosion: 0 + }; + }, + + update(app, state, objects) { + const t = state.state.time.current; + + // Create new explosion every second + if (t > objects.nextExplosion) { + objects.nextExplosion = t + 0.5 + Math.random(); + + const explosion = { + x: Math.random() * app.screen.width, + y: Math.random() * app.screen.height * 0.7, + particles: [], + color: Math.random() * 0xffffff, + born: t + }; + + // Create particles + for (let i = 0; i < 50; i++) { + const angle = (i / 50) * Math.PI * 2; + const speed = 2 + Math.random() * 4; + const particle = new PIXI.Graphics(); + particle.circle(0, 0, 3); + particle.fill(explosion.color); + particle.x = explosion.x; + particle.y = explosion.y; + particle.vx = Math.cos(angle) * speed; + particle.vy = Math.sin(angle) * speed; + + app.stage.addChild(particle); + explosion.particles.push(particle); + } + + objects.explosions.push(explosion); + } + + // Update explosions + objects.explosions = objects.explosions.filter(explosion => { + const age = t - explosion.born; + + if (age > 3) { + explosion.particles.forEach(p => p.destroy()); + return false; + } + + explosion.particles.forEach(p => { + p.vx *= 0.98; + p.vy += 0.1; // Gravity + p.x += p.vx; + p.y += p.vy; + p.alpha = 1 - age / 3; + }); + + return true; + }); + }, + + cleanup(app, objects) { + objects.explosions.forEach(explosion => { + explosion.particles.forEach(p => p.destroy()); + }); + } +}; + +// ============================================================================ +// DEMO 9: MATRIX RAIN +// ============================================================================ + +export const matrixRain = { + name: "Matrix Rain", + description: "Falling digital rain effect", + + setup(app, state) { + const fontSize = 16; + const columns = Math.floor(app.screen.width / fontSize); + const drops = []; + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*"; + + for (let i = 0; i < columns; i++) { + const text = new PIXI.Text('', { + fontFamily: 'monospace', + fontSize: fontSize, + fill: 0x00ff00 + }); + text.x = i * fontSize; + text.y = -Math.random() * app.screen.height; + + app.stage.addChild(text); + drops.push({ + text, + speed: 1 + Math.random() * 3, + chars: chars + }); + } + + return { drops }; + }, + + update(app, state, objects) { + objects.drops.forEach(drop => { + drop.y = (drop.y || drop.text.y) + drop.speed; + drop.text.y = drop.y; + + // Random character + if (Math.random() > 0.95) { + drop.text.text = drop.chars[Math.floor(Math.random() * drop.chars.length)]; + } + + // Reset to top + if (drop.y > app.screen.height) { + drop.y = -20; + drop.text.alpha = 1; + } + + // Fade trail + drop.text.alpha = Math.max(0.1, drop.text.alpha - 0.01); + }); + }, + + cleanup(app, objects) { + objects.drops.forEach(d => d.text.destroy()); + } +}; + +// ============================================================================ +// DEMO 10: SOLAR SYSTEM +// ============================================================================ + +export const solarSystem = { + name: "Solar System", + description: "Orbiting planets around a star", + + setup(app, state) { + const cx = app.screen.width / 2; + const cy = app.screen.height / 2; + + // Sun + const sun = new PIXI.Graphics(); + sun.circle(0, 0, 30); + sun.fill(0xffd700); + sun.x = cx; + sun.y = cy; + app.stage.addChild(sun); + + // Planets + const planets = [ + { radius: 60, size: 6, speed: 2.0, color: 0x8b7355 }, + { radius: 100, size: 10, speed: 1.5, color: 0xff6347 }, + { radius: 150, size: 12, speed: 1.0, color: 0x4169e1 }, + { radius: 200, size: 8, speed: 0.7, color: 0xff4500 }, + { radius: 260, size: 18, speed: 0.4, color: 0xdaa520 }, + ]; + + const planetObjects = planets.map(config => { + const planet = new PIXI.Graphics(); + planet.circle(0, 0, config.size); + planet.fill(config.color); + planet.config = config; + app.stage.addChild(planet); + return planet; + }); + + return { sun, planets: planetObjects, cx, cy }; + }, + + update(app, state, objects) { + const t = state.state.time.current; + + objects.planets.forEach((planet, i) => { + const angle = t * planet.config.speed; + planet.x = objects.cx + Math.cos(angle) * planet.config.radius; + planet.y = objects.cy + Math.sin(angle) * planet.config.radius; + }); + }, + + cleanup(app, objects) { + objects.sun.destroy(); + objects.planets.forEach(p => p.destroy()); + } +}; + +// ============================================================================ +// UTILITIES +// ============================================================================ + +function hslToHex(h, s, l) { + s /= 100; + l /= 100; + const c = (1 - Math.abs(2 * l - 1)) * s; + const x = c * (1 - Math.abs((h / 60) % 2 - 1)); + const m = l - c/2; + let r = 0, g = 0, b = 0; + + if (0 <= h && h < 60) { + r = c; g = x; b = 0; + } else if (60 <= h && h < 120) { + r = x; g = c; b = 0; + } else if (120 <= h && h < 180) { + r = 0; g = c; b = x; + } else if (180 <= h && h < 240) { + r = 0; g = x; b = c; + } else if (240 <= h && h < 300) { + r = x; g = 0; b = c; + } else if (300 <= h && h < 360) { + r = c; g = 0; b = x; + } + + r = Math.round((r + m) * 255); + g = Math.round((g + m) * 255); + b = Math.round((b + m) * 255); + + return (r << 16) | (g << 8) | b; +} + +function noise(x, y, z) { + return Math.sin(x + Math.cos(y)) * Math.cos(y + Math.sin(z)) * Math.sin(z + Math.cos(x)); +} + +// ============================================================================ +// EXPORT ALL DEMOS +// ============================================================================ + +export const allDemos = [ + bouncingParticles, + spirograph, + starfield, + waveInterference, + circlePacking, + flowField, + dnaHelix, + fireworks, + matrixRain, + solarSystem +]; |
