Última atividade 1757455898

gpt-oss:120b.md Bruto

Below is a single HTML file that creates a self‑contained triple‑pendulum animation.
The physics are handled with a simple Verlet integrator plus distance‑constraints, which gives a realistic motion that obeys gravity.
Each mass leaves a fading trail, so you can see the path it follows.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Triple Pendulum</title>
<style>
    body {margin:0; background:#111; overflow:hidden;}
    canvas {display:block;}
</style>
</head>
<body>
<canvas id="c"></canvas>

<script>
// ------------------------------------------------------------
// 1️⃣  Setup
// ------------------------------------------------------------
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
function resize() {
    canvas.width = innerWidth;
    canvas.height = innerHeight;
}
window.addEventListener('resize', resize);
resize();

// ------------------------------------------------------------
// 2️⃣  Pendulum parameters
// ------------------------------------------------------------
const g = 980;                 // gravity (px/s²) – scaled for screen size
const dt = 0.016;              // time step (seconds)
const substeps = 5;            // for stability

// rod lengths (in pixels)
const L1 = 180, L2 = 130, L3 = 100;

// masses (only needed for visual size)
const R1 = 12, R2 = 10, R3 = 8;

// pivot point (fixed)
const pivot = {x: canvas.width/2, y: canvas.height/4};

// -----------------------------------------------------------------
// 3️⃣  Verlet state (position + previous position) for each bob
// -----------------------------------------------------------------
function Vec(x,y){ return {x, y}; }

let bob = [
    { // bob 0 – attached to pivot (doesn't move, but keep for simplicity)
        pos:   Vec(pivot.x, pivot.y),
        prev:  Vec(pivot.x, pivot.y)
    },
    { // bob 1
        pos:   Vec(pivot.x + L1*Math.sin(Math.PI/2), pivot.y + L1*Math.cos(Math.PI/2)),
        prev:  Vec(pivot.x + L1*Math.sin(Math.PI/2), pivot.y + L1*Math.cos(Math.PI/2))
    },
    { // bob 2
        pos:   Vec(pivot.x + L1*Math.sin(Math.PI/2) + L2*Math.sin(Math.PI/2),
                   pivot.y + L1*Math.cos(Math.PI/2) + L2*Math.cos(Math.PI/2)),
        prev:  Vec(pivot.x + L1*Math.sin(Math.PI/2) + L2*Math.sin(Math.PI/2),
                   pivot.y + L1*Math.cos(Math.PI/2) + L2*Math.cos(Math.PI/2))
    },
    { // bob 3 – end of the chain
        pos:   Vec(pivot.x + L1*Math.sin(Math.PI/2) + L2*Math.sin(Math.PI/2) + L3*Math.sin(Math.PI/2),
                   pivot.y + L1*Math.cos(Math.PI/2) + L2*Math.cos(Math.PI/2) + L3*Math.cos(Math.PI/2)),
        prev:  Vec(pivot.x + L1*Math.sin(Math.PI/2) + L2*Math.sin(Math.PI/2) + L3*Math.sin(Math.PI/2),
                   pivot.y + L1*Math.cos(Math.PI/2) + L2*Math.cos(Math.PI/2) + L3*Math.cos(Math.PI/2))
    }
];

// -----------------------------------------------------------------
// 4️⃣  Trail storage (capped length)
// -----------------------------------------------------------------
const maxTrail = 300; // how many points to keep
let trails = [ [], [], [] ]; // one per moving bob (1,2,3)

// -----------------------------------------------------------------
// 5️⃣  Physics helpers
// -----------------------------------------------------------------
function verletIntegrate(b, dt) {
    const ax = 0, ay = g; // gravity only
    const nx = b.pos.x + (b.pos.x - b.prev.x) + ax*dt*dt;
    const ny = b.pos.y + (b.pos.y - b.prev.y) + ay*dt*dt;
    b.prev.x = b.pos.x;
    b.prev.y = b.pos.y;
    b.pos.x = nx;
    b.pos.y = ny;
}

// enforce distance constraints (rigid rods)
function satisfyConstraints() {
    // we iterate a few times for convergence
    for (let i = 0; i < 5; i++) {
        // 0 <-> 1  (pivot is fixed, so we only move bob1)
        let a = bob[0], b = bob[1];
        let dx = b.pos.x - a.pos.x, dy = b.pos.y - a.pos.y;
        let d = Math.hypot(dx, dy);
        let diff = (d - L1) / d;
        b.pos.x -= dx * diff;
        b.pos.y -= dy * diff;

        // 1 <-> 2
        a = bob[1]; b = bob[2];
        dx = b.pos.x - a.pos.x; dy = b.pos.y - a.pos.y;
        d = Math.hypot(dx, dy);
        diff = (d - L2) / d;
        // move both ends equally
        a.pos.x += dx * diff * 0.5;
        a.pos.y += dy * diff * 0.5;
        b.pos.x -= dx * diff * 0.5;
        b.pos.y -= dy * diff * 0.5;

        // 2 <-> 3
        a = bob[2]; b = bob[3];
        dx = b.pos.x - a.pos.x; dy = b.pos.y - a.pos.y;
        d = Math.hypot(dx, dy);
        diff = (d - L3) / d;
        a.pos.x += dx * diff * 0.5;
        a.pos.y += dy * diff * 0.5;
        b.pos.x -= dx * diff * 0.5;
        b.pos.y -= dy * diff * 0.5;
    }
}

// -----------------------------------------------------------------
// 6️⃣  Main loop
// -----------------------------------------------------------------
function step() {
    // sub‑step integration for stability
    const subDt = dt / substeps;
    for (let s = 0; s < substeps; s++) {
        // integrate bobs 1‑3 (bob0 is the fixed pivot)
        for (let i = 1; i < bob.length; i++) verletIntegrate(bob[i], subDt);
        satisfyConstraints();
    }

    // store trail points
    for (let i = 1; i < bob.length; i++) {
        const tr = trails[i-1];
        tr.push({x: bob[i].pos.x, y: bob[i].pos.y});
        if (tr.length > maxTrail) tr.shift();
    }
}

// -----------------------------------------------------------------
// 7️⃣  Rendering
// -----------------------------------------------------------------
function draw() {
    ctx.clearRect(0,0,canvas.width,canvas.height);

    // draw trails (fading)
    for (let i = 0; i < trails.length; i++) {
        const tr = trails[i];
        if (tr.length < 2) continue;
        ctx.beginPath();
        for (let j = 0; j < tr.length; j++) {
            const p = tr[j];
            const alpha = j / tr.length; // older points are more transparent
            ctx.strokeStyle = `rgba(${255-30*i},${150+30*i},${200},${alpha})`;
            if (j===0) ctx.moveTo(p.x,p.y);
            else ctx.lineTo(p.x,p.y);
        }
        ctx.lineWidth = 2;
        ctx.stroke();
    }

    // draw rods
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(pivot.x, pivot.y);
    ctx.lineTo(bob[1].pos.x, bob[1].pos.y);
    ctx.lineTo(bob[2].pos.x, bob[2].pos.y);
    ctx.lineTo(bob[3].pos.x, bob[3].pos.y);
    ctx.stroke();

    // draw bobs
    const radii = [0, R1, R2, R3];
    const colors = ['#ff6', '#6cf', '#f66'];
    for (let i = 1; i < bob.length; i++) {
        ctx.beginPath();
        ctx.arc(bob[i].pos.x, bob[i].pos.y, radii[i], 0, Math.PI*2);
        ctx.fillStyle = colors[i-1];
        ctx.fill();
        ctx.strokeStyle = '#fff';
        ctx.stroke();
    }
}

// -----------------------------------------------------------------
// 8️⃣  Animation loop
// -----------------------------------------------------------------
function animate() {
    step();
    draw();
    requestAnimationFrame(animate);
}
animate();

</script>
</body>
</html>

How it works

  1. Verlet integration – each bob’s new position is computed from its current and previous positions plus gravity.
  2. Distance constraints – after integration the three rod lengths (L1, L2, L3) are enforced by repeatedly adjusting the positions, giving a realistic rigid‑rod behavior.
  3. Trails – every frame the current positions of the three moving bobs are pushed onto three separate arrays. Old points are discarded after maxTrail frames, and the trail is drawn with a fading alpha so the path slowly disappears.
  4. Rendering – the canvas is cleared each frame, trails are drawn first (so they appear beneath the pendulum), then the rods and the three bobs are drawn.

Open the file in any modern browser and you’ll see a continuously moving triple pendulum, each bob leaving a colorful fading trail behind it. No controls or user interaction are required. Enjoy!