let width, height, scale, originX, originY; let g = 9.81; let l1 = 0.2, l2 = 0.2, l3 = 0.2; let m1 = 1, m2 = 1, m3 = 1; let theta1 = Math.PI / 2, theta2 = 0, theta3 = 0; let omega1 = 0, omega2 = 0, omega3 = 0; const initialTheta1 = theta1, initialTheta2 = theta2, initialTheta3 = theta3; const dt = 0.005; let accumulator = 0; let previousTime = performance.now();
const trail1 = [], trail2 = [], trail3 = []; const maxTrailLength = 1000; const trailColors = ['rgba(255, 0, 0, 0.5)', 'rgba(0, 255, 0, 0.5)', 'rgba(0, 0, 255, 0.5)'];
function resize() { width = window.innerWidth; height = window.innerHeight; canvas.width = width; canvas.height = height; scale = Math.min(width, height) / 1.5; originX = width / 2; originY = height / 4; // Reset simulation on resize for clean scaling theta1 = initialTheta1; theta2 = initialTheta2; theta3 = initialTheta3; omega1 = 0; omega2 = 0; omega3 = 0; trail1.length = 0; trail2.length = 0; trail3.length = 0; }
window.addEventListener('resize', resize); resize();
function det3(mat) { return mat[0][0] * (mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]) - mat[0][1] * (mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]) + mat[0][2] * (mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]); }
function computeAlphas(t1, t2, t3, w1, w2, w3) { const thetas = [t1, t2, t3]; const omegas = [w1, w2, w3]; const ls = [l1, l2, l3]; const ms = [m1, m2, m3];
let M = Array.from({length: 3}, () => Array(3).fill(0)); for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { let sum = 0; for (let k = Math.max(i, j); k < 3; k++) { sum += ms[k]; } M[i][j] = ls[i] * ls[j] * sum * Math.cos(thetas[i] - thetas[j]); } }
let b = Array(3).fill(0); for (let i = 0; i < 3; i++) { let sumM = 0; for (let k = i; k < 3; k++) { sumM += ms[k]; } const g_term = -g * ls[i] * Math.sin(thetas[i]) * sumM;
let c_term = 0;
for (let j = 0; j < 3; j++) {
let sumM2 = 0;
for (let k = Math.max(i, j); k < 3; k++) {
sumM2 += ms[k];
}
c_term -= ls[i] * ls[j] * Math.sin(thetas[i] - thetas[j]) * (omegas[j] ** 2) * sumM2;
}
b[i] = g_term + c_term;
let c_term = 0;
for (let j = 0; j < 3; j++) {
let sumM2 = 0;
for (let k = Math.max(i, j); k < 3; k++) {
sumM2 += ms[k];
}
c_term -= ls[i] * ls[j] * Math.sin(thetas[i] - thetas[j]) * (omegas[j] ** 2) * sumM2;
}
b[i] = g_term + c_term;
}
const detM = det3(M); if (Math.abs(detM) < 1e-10) { return [0, 0, 0]; }
let alphas = []; for (let k = 0; k < 3; k++) { const copy = M.map(row => row.slice()); for (let r = 0; r < 3; r++) { copy[r][k] = b[r]; } alphas[k] = det3(copy) / detM; } return alphas; }
function derivs(state) { const [t1, w1, t2, w2, t3, w3] = state; const [a1, a2, a3] = computeAlphas(t1, t2, t3, w1, w2, w3); return [w1, a1, w2, a2, w3, a3]; }
function rk4_step(state, dt) { const d1 = derivs(state); const temp1 = state.map((s, i) => s + d1[i] * dt / 2); const d2 = derivs(temp1); const temp2 = state.map((s, i) => s + d2[i] * dt / 2); const d3 = derivs(temp2); const temp3 = state.map((s, i) => s + d3[i] * dt); const d4 = derivs(temp3);
return state.map((s, i) => s + (d1[i] + 2 * d2[i] + 2 * d3[i] + d4[i]) * dt / 6); }
function step(dt) { const state = [theta1, omega1, theta2, omega2, theta3, omega3]; const newState = rk4_step(state, dt); theta1 = newState[0]; omega1 = newState[1]; theta2 = newState[2]; omega2 = newState[3]; theta3 = newState[4]; omega3 = newState[5]; }
function getPositions() { const x1 = l1 * Math.sin(theta1); const y1 = l1 * Math.cos(theta1); const x2 = x1 + l2 * Math.sin(theta2); const y2 = y1 + l2 * Math.cos(theta2); const x3 = x2 + l3 * Math.sin(theta3); const y3 = y2 + l3 * Math.cos(theta3); return [{x: x1, y: y1}, {x: x2, y: y2}, {x: x3, y: y3}]; }
function drawTrail(trail, color) { if (trail.length < 2) return; ctx.beginPath(); ctx.moveTo(originX + trail[0].x * scale, originY + trail[0].y * scale); for (let i = 1; i < trail.length; i++) { ctx.lineTo(originX + trail[i].x * scale, originY + trail[i].y * scale); } ctx.strokeStyle = color; ctx.lineWidth = 1; ctx.stroke(); }
function draw() { ctx.fillStyle = 'black'; ctx.fillRect(0, 0, width, height);
const positions = getPositions();
// Add to trails trail1.push(positions[0]); trail2.push(positions[1]); trail3.push(positions[2]); if (trail1.length > maxTrailLength) trail1.shift(); if (trail2.length > maxTrailLength) trail2.shift(); if (trail3.length > maxTrailLength) trail3.shift();
// Draw trails drawTrail(trail1, trailColors[0]); drawTrail(trail2, trailColors[1]); drawTrail(trail3, trailColors[2]);
// Draw arms ctx.beginPath(); ctx.moveTo(originX, originY); ctx.lineTo(originX + positions[0].x * scale, originY + positions[0].y * scale); ctx.lineTo(originX + positions[1].x * scale, originY + positions[1].y * scale); ctx.lineTo(originX + positions[2].x * scale, originY + positions[2].y * scale); ctx.strokeStyle = 'white'; ctx.lineWidth = 2; ctx.stroke();
// Draw bobs const bobRadius = 5; ctx.fillStyle = 'red'; ctx.beginPath(); ctx.arc(originX + positions[0].x * scale, originY + positions[0].y * scale, bobRadius, 0, 2 * Math.PI); ctx.fill(); ctx.fillStyle = 'green'; ctx.beginPath(); ctx.arc(originX + positions[1].x * scale, originY + positions[1].y * scale, bobRadius, 0, 2 * Math.PI); ctx.fill(); ctx.fillStyle = 'blue'; ctx.beginPath(); ctx.arc(originX + positions[2].x * scale, originY + positions[2].y * scale, bobRadius, 0, 2 * Math.PI); ctx.fill(); }
function animate(currentTime) { requestAnimationFrame(animate); const delta = (currentTime - previousTime) / 1000; previousTime = currentTime; accumulator += delta; while (accumulator >= dt) { step(dt); accumulator -= dt; } draw(); }
requestAnimationFrame(animate); </script>
</body> </html>_