複雑な動きを持つ4重振り子のアニメーションを楽しむことができます。スペースキーで異なる初期条件での振り子の動きを試すことができます。
コードをメモ帳などのテキストエディタに貼り付け、ファイルを「index.html」などの拡張子が.htmlのファイルとして保存します。その後、保存したファイルをブラウザで開けば、コードが実行されます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>カラフルな4重振り子アニメーション</title>
<style>
canvas {
display: block;
margin: 0 auto;
background-color: #000; /* 黒い背景 */
}
</style>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const g = 9.81; // 重力加速度
let lengths = []; // ロッドの長さを格納する配列
const masses = [15, 15, 15, 15]; // 各ボブの質量
let angles = [Math.PI / 4, Math.PI / 4, Math.PI / 4, Math.PI / 4]; // 初期角度
let angleVels = [0, 0, 0, 0]; // 角速度
let angleAccels = [0, 0, 0, 0]; // 角加速度
const damping = 0.995; // 減衰係数(動きをゆっくりにする)
let colors = []; // ボブの色を格納する配列
// ロッドの長さとボブの色をランダムに設定
function randomizeProperties() {
lengths = Array.from({ length: 4 }, () => 80 + Math.random() * 120); // ロッドの長さをランダムに設定
colors = Array.from({ length: 4 }, () => `hsl(${Math.random() * 360}, 100%, 50%)`); // ボブの色をランダムに設定
}
// 初期条件をランダムに設定
function randomizeInitialConditions() {
angles = angles.map(() => Math.PI / 4 + (Math.random() - 0.5) * Math.PI / 4);
angleVels = [0, 0, 0, 0];
angleAccels = [0, 0, 0, 0];
randomizeProperties(); // ロッドの長さとボブの色もリセット
}
// 振り子の描画
function drawPendulum() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 画面をクリア
ctx.translate(canvas.width / 2, 100); // 振り子の中心位置を画面中央に設定
let [x0, y0] = [0, 0]; // 最初の位置
let [x1, y1] = [x0 + lengths[0] * Math.sin(angles[0]), y0 + lengths[0] * Math.cos(angles[0])];
let [x2, y2] = [x1 + lengths[1] * Math.sin(angles[1]), y1 + lengths[1] * Math.cos(angles[1])];
let [x3, y3] = [x2 + lengths[2] * Math.sin(angles[2]), y2 + lengths[2] * Math.cos(angles[2])];
let [x4, y4] = [x3 + lengths[3] * Math.sin(angles[3]), y3 + lengths[3] * Math.cos(angles[3])];
// 各振り子の描画
drawLinkAndBob(x0, y0, x1, y1, colors[0], masses[0]);
drawLinkAndBob(x1, y1, x2, y2, colors[1], masses[1]);
drawLinkAndBob(x2, y2, x3, y3, colors[2], masses[2]);
drawLinkAndBob(x3, y3, x4, y4, colors[3], masses[3]);
ctx.resetTransform(); // 変換をリセット
}
// 振り子のロッドとボブの描画関数
function drawLinkAndBob(x0, y0, x1, y1, color, mass) {
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
ctx.beginPath();
ctx.arc(x1, y1, mass, 0, 2 * Math.PI);
ctx.fill();
}
// 振り子の更新
function updatePendulum() {
// 角加速度の計算(以下のコードでは、物理的な詳細な計算が必要)
const num1 = -g * (2 * masses[0] + masses[1]) * Math.sin(angles[0]);
const num2 = -masses[1] * g * Math.sin(angles[0] - 2 * angles[1]);
const num3 = -2 * Math.sin(angles[0] - angles[1]) * masses[1];
const num4 = angleVels[1] ** 2 * lengths[1] + angleVels[0] ** 2 * lengths[0] * Math.cos(angles[0] - angles[1]);
angleAccels[0] = (num1 + num2 + num3 * num4) / (lengths[0] * (2 * masses[0] + masses[1] - masses[1] * Math.cos(2 * angles[0] - 2 * angles[1])));
const num5 = 2 * Math.sin(angles[0] - angles[1]);
const num6 = angleVels[0] ** 2 * lengths[0] * (masses[0] + masses[1]);
const num7 = g * (masses[0] + masses[1]) * Math.cos(angles[0]);
const num8 = angleVels[1] ** 2 * lengths[1] * masses[1] * Math.cos(angles[0] - angles[1]);
angleAccels[1] = (num5 * (num6 + num7 + num8)) / (lengths[1] * (2 * masses[0] + masses[1] - masses[1] * Math.cos(2 * angles[0] - 2 * angles[1])));
const num9 = 2 * Math.sin(angles[1] - angles[2]);
const num10 = angleVels[1] ** 2 * lengths[1] * (masses[1] + masses[2]);
const num11 = g * (masses[1] + masses[2]) * Math.cos(angles[1]);
const num12 = angleVels[2] ** 2 * lengths[2] * masses[2] * Math.cos(angles[1] - angles[2]);
angleAccels[2] = (num9 * (num10 + num11 + num12)) / (lengths[2] * (2 * masses[1] + masses[2] - masses[2] * Math.cos(2 * angles[1] - 2 * angles[2])));
const num13 = 2 * Math.sin(angles[2] - angles[3]);
const num14 = angleVels[2] ** 2 * lengths[2] * (masses[2] + masses[3]);
const num15 = g * (masses[2] + masses[3]) * Math.cos(angles[2]);
const num16 = angleVels[3] ** 2 * lengths[3] * masses[3] * Math.cos(angles[2] - angles[3]);
angleAccels[3] = (num13 * (num14 + num15 + num16)) / (lengths[3] * (2 * masses[2] + masses[3] - masses[3] * Math.cos(2 * angles[2] - 2 * angles[3])));
// 角速度と角度の更新
angleVels = angleVels.map((vel, i) => (vel + angleAccels[i]) * damping); // 減衰を適用して速度を低下
angles = angles.map((angle, i) => angle + angleVels[i]);
}
// アニメーションループ
function animate() {
updatePendulum(); // 振り子を更新
drawPendulum(); // 振り子を描画
requestAnimationFrame(animate); // 次のフレームをリクエスト
}
// スペースキーで初期条件をリセット
document.body.addEventListener('keydown', function(event) {
if (event.code === 'Space') {
randomizeInitialConditions();
}
});
// 初期化
randomizeInitialConditions(); // 初期条件をランダムに設定
animate(); // アニメーションを開始
</script>
</body>
</html>