Double pendulum · ling-2.6-flash
Simulate a chaotic double pendulum on a canvas using the correct equations of motion, drawing the arms and a colorful fading trail of the tip.
This one-shot run produced no runnable artifact.
agent produced no index.html
The transcript below shows what the model did — read it to see why the one shot fell short.
transcript · 2 messages
Simulate a chaotic double pendulum on a canvas using the correct equations of motion, drawing the arms and a colorful fading trail of the tip.
I'll create a self-contained HTML file that simulates a chaotic double pendulum with correct physics equations and a colorful fading trail effect. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-width=device-width, initial-scale=1.0"> <title>Chaotic Double Pendulum</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #000; overflow: hidden; display: flex; justify-content: center; align-items: center; height: 100vh; } canvas { display: block; cursor: crosshair; } </style> </head> <body> <canvas id="canvas"></canvas> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); // Set canvas to full window size function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resize(); window.addEventListener('resize', resize); // Double pendulum parameters const params = { g: 9.81, // gravity m1: 1.0, // mass 1 m2: 1.0, // mass 2 l1: 200, // length 1 l2: 150, // length 2 damping: 0.99998, // slight energy damping for long-term stability }; // State variables [theta1, omega1, theta2, omega2] let state = { theta1: Math.PI * 0.7, omega1: 0, theta2: Math.PI * 0.7, omega2: 0, }; // Trail storage - stores positions with fading alpha let trail = []; const MAX_TRAIL = 3000; // Convert polar to cartesian for pendulum joint function getJoint1() { return { x: canvas.width / 2, y: 80 }; } function getJoint2(theta1, theta2) { const x1 = canvas.width / 2 + params.l1 * Math.sin(theta1); const y1 = 80 + params.l1 * Math.cos(theta1); const x2 = x1 + params.l2 * Math.sin(theta1 + theta2); const y2 = y1 + params.l2 * Math.cos(theta1 + theta2); return { x1, y1, x2, y2 }; } // RK4 integration of the equations of motion function derivatives(s, t) { const { theta1, omega1, theta2, omega2 } = s; const g = params.g; const m1 = params.m1; const m2 = params.m2; const l1 = params.l1; const l2 = params.l2; const damping = params.damping; const dtheta1 = omega1; const dtheta2 = omega2; const delta = theta2 - theta1; const den1 = (m1 + m2) * l1 - m2 * l1 * Math.cos(delta) * Math.cos(delta); const den2 = (l2 / l1) * den1; const num1 = m2 * l1 * omega1 * omega1 * Math.sin(delta) * Math.cos(delta) + m2 * g * Math.sin(theta2) * Math.cos(delta) + m2 * l2 * omega2 * omega2 * Math.sin(delta) - (m1 + m2) * g * Math.sin(theta1); const a1 = -num1 / (den1 * damping); const num2 = -m2 * l2 * omega2 * omega2 * Math.sin(delta) * Math.cos(delta) + (m1 + m2) * (g * Math.sin(theta1) * Math.cos(delta) - l1 * omega1 * omega1 * Math.sin(delta) - g * Math.sin(theta2)); const a2 = num2 / (den2 * damping); return [dtheta1, a1, dtheta2, a2]; } function rk4Step(s, t, dt) { const k1 = derivatives(s, t); const s2 = { theta1: s.theta1 + 0.5 * dt * k1[0], omega1: s.omega1 + 0.5 * dt * k1[1], theta2: s.theta2 + 0.5 * dt * k1[2], omega2: s.omega2 + 0.5 * dt * k1[3], }; const k2 = derivatives(s2, t + 0.5 * dt); const s3 = { theta1: s.theta1 + 0.5 * dt * k2[0], omega1: s.omega1 + 0.5 * dt * k2[1], theta2: s.theta2 + 0.5 * dt * k2[2], omega2: s.omega2 + 0.5 * dt * k2[3], }; const k3 = derivatives(s3, t + 0.5 * dt); const s4 = { theta1: s.theta1 + dt * k3[0], omega1: s.omega1 + dt * k3[1], theta2: s.theta2 + dt * k3[2], omega2: s.omega2 + dt * k3[3], }; const k4 = derivatives(s4, t + dt); return { theta1: s.theta1 + (dt / 6) * (k1[0] + 2*k2[0] + 2*k3[0] + k4[0]), omega1: s.omega1 + (dt / 6) * (k1[1] + 2*k2[1] + 2*k3[1] + k4[1]), theta2: s.theta2 + (dt / 6) * (k1[2] + 2*k2[2] + 2*k3[2] + k4[2]), omega2: s.omega2 + (dt / 6) * (k1[3] + 2*k2[3] + 2*k3[3] + k4[3]), }; } // Add position to trail function addTrail(x, y) { trail.push({ x, y, life: 1.0 }); if (trail.length > MAX_TRAIL) { trail.shift(); } } // Update trail life for fading function updateTrail() { for (let i = 0; i < trail.length; i++) { trail[i].life -= 0.008; } // Remove dead trail segments while (trail.length > 0 && trail[0].life <= 0) { trail.shift(); } } // Draw the pendulum function drawPendulum(x1, y1, x2, y2) { // Arm 1 ctx.beginPath(); ctx.moveTo(canvas.width / 2, 80); ctx.lineTo(x1, y1); ctx.strokeStyle = 'rgba(100, 100, 120, 0.8)'; ctx.lineWidth = 3; ctx.stroke(); // Arm 2 ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.strokeStyle = 'rgba(120, 100, 100, 0.8)'; ctx.lineWidth = 2.5; ctx.stroke(); // Joint 1 (fixed pivot) ctx.beginPath(); ctx.arc(canvas.width / 2, 80, 8, 0, Math.PI * 2); ctx.fillStyle = '#aaa'; ctx.fill(); // Joint 2 (moving pivot) ctx.beginPath(); ctx.arc(x1, y1, 6, 0, Math.PI * 2); ctx.fillStyle = '#ccc'; ctx.fill(); // Bob 1 ctx.beginPath(); ctx.arc(x2, y2, 12, 0, Math.PI * 2); const hue1 = (x2 * 0.5 + y2 * 0.3) % 360; ctx.fillStyle = `hsla(${hue1}, 80%, 60%, 0.9)`; ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.lineWidth = 1; ctx.stroke(); // Bob 2 (tip - colorful) ctx.beginPath(); ctx.arc(x2, y2, 14, 0, Math.PI * 2); const hue2 = (t * 50 + x2 * 0.3) % 360; ctx.fillStyle = `hsla(${hue2}, 90%, 55%, 0.95)`; ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.5)'; ctx.lineWidth = 1; ctx.stroke(); // Glow at tip ctx.beginPath(); ctx.arc(x2, y2, 20, 0, Math.PI * 2); const gradient = ctx.createRadialGradient(x2, y2, 0, x2, y2, 20); gradient.addColorStop(0, `hsla(${(t * 50) % 360}, 100%, 70%, 0.3)`); gradient.addColorStop(1, 'rgba(0,0,0,0)'); ctx.fillStyle = gradient; ctx.fill(); } // Draw the trail behind the tip function drawTrail() { if (trail.length < 2) return; // Draw thick fading trail for (let i = 1; i < trail.length; i++) { const t1 = trail[i - 1]; const t2 = trail[i]; const alpha = (t1.life + trail[trail.length - 1].life) / 2 * 0.6; const segmentLength = Math.sqrt(Math.pow(t2.x - t1.x, 2) + Math.pow(t2.y - t1.y, 2)); const hue = (i * 0.5 + t * 30) % 360; ctx.beginPath(); ctx.moveTo(t1.x, t1.y); ctx.lineTo(t2.x, t2.y); ctx.strokeStyle = `hsla(${hue}, 90%, 60%, ${Math.max(0, alpha)})`; ctx.lineWidth = Math.max(1, segmentLength * 0.5); ctx.lineCap = 'round'; ctx.stroke(); } // Bright tip trail dot const tip = trail[trail.length - 1]; ctx.beginPath(); ctx.arc(tip.x, tip.y, 4, 0, Math.PI * 2); ctx.fillStyle = `hsla(${(t * 50) % 360}, 100%, 70%, 0.95)`; ctx.shadowBlur = 15; ctx.shadowColor = `hsla(${(t * 50) % 360}, 100%, 70%, 0.8)`; ctx.fill(); ctx.shadowBlur = 0; } // Animation loop let t = 0; const dt = 1 / 120; // 120 FPS simulation let lastTime = performance.now(); // Click to perturb canvas.addEventListener('click', (e) => { state.omega1 += (Math.random() - 0.5) * 3; state.omega2 += (Math.random() - 0.5) * 3; }); function animate(now) { const delta = (now - lastTime) / 1000; lastTime = now; // Fixed-step integration let accumulator = Math.min(delta, 0.05); while (accumulator >= dt) { state = rk4Step(state, t, dt); t += dt; accumulator -= dt; } // Get positions const joint1 = getJoint1(); const j = getJoint2(state.theta1, state.theta2); // Clear with fade-to-black for trail effect ctx.fillStyle = 'rgba(0, 0, 0, 0.08)'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Add current tip to trail addTrail(j.x2, j.y2); // Update trail fade updateTrail(); // Draw trail first (behind everything) drawTrail(); // Draw pendulum drawPendulum(j.x1, j.y1, j.x2, j.y2); // Draw instructions ctx.fillStyle = 'rgba(255, 255, 255, 0.25)'; ctx.font = '14px monospace'; ctx.textAlign = 'left'; ctx.fillText('Click anywhere to perturb the pendulum', 15, canvas.height - 20); ctx.fillText(`θ1: ${(state.theta1 * 180 / Math.PI).toFixed(1)}° θ2: ${((state.theta1 + state.theta2) * 180 / Math.PI).toFixed(1)}°`, 15, canvas.height - 5); requestAnimationFrame(animate); } // Draw a static background grid for depth function drawGrid() { ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)'; ctx.lineWidth = 1; const step = 40; for (let x = 0; x < canvas.width; x += step) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); } for (let y = 0; y < canvas.height; y += step) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } } // Initial grid draw drawGrid(); // Handle window resize - redraw grid window.addEventListener('resize', () => { resize(); drawGrid(); }); // Handle double-click to reset canvas.addEventListener('dblclick', () => { state = { theta1: Math.PI * 0.7, omega1: 0, theta2: Math.PI * 0.7, omega2: 0 }; trail = []; }); requestAnimationFrame(animate); </script> </body> </html> ``` ## Features ### Physics (Correct Equations of Motion) - Uses the standard **Lagrangian-derived equations** for a double pendulum with proper coupling terms - **RK4 (4th-order Runge-Kutta)** numerical integration for stable, accurate simulation - Full parameters: masses `m1`, `m2`, lengths `l1`, `l2`, gravity `g`, and damping ### Visuals - **Colorful tip**: The lower mass (bob) uses HSLA coloring based on position and time for a vibrant effect - **Fading trail**: The tip leaves a colorful, fading trail that decays over time using per-segment alpha and life tracking - **Glow effect**: Radial gradient glow emanates from the tip - **Anti-overdraw**: The canvas uses a slight fade-to-black (`0.08 alpha`) instead of clearing to black for smoother trails - **Grid background**: Subtle grid for spatial reference ### Interactivity - **Click** anywhere to impart a random impulse to the pendulum - **Double-click** to reset to the initial state - **Real-time angle display** in the bottom-left corner
TAKE IT TO CODE
This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.