3
4

複雑でカオス的な動きを持つ4重振り子のアニメーションのゲーム。

Posted at

スクリーンショット 2024-08-25 042519.png

スクリーンショット 2024-08-25 042452.png

複雑な動きを持つ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>

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4