今Babylon.jsを知った私がChatGPT先生となにかを作ってみる

# はじめに

この記事は[Babylon.js Advent Calendar 2024](https://qiita.com/advent-calendar/2024/babylonjs)の12日目の記事です。

Babylon.jsを今知ったところですが、
とても便利そうなので、ChatGPT先生と一緒に試してみます。

Babylon.jsを知っているかChatGPT先生に聞いてみる。

![スクリーンショット 2024-12-20 0.17.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/d469c97e-f93e-8159-e919-06b973d398b0.png)

# 早速プログラムを作ってもらう

![スクリーンショット 2024-12-20 0.18.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/ed1c800f-85e9-f5c8-6c95-6ea0a83e80ee.png)


![スクリーンショット 2024-12-20 0.20.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/ecc5be50-00c3-f45a-2941-501777b5cfc0.png)

![スクリーンショット 2024-12-20 0.21.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/ab796f08-526f-1d01-b48a-b2d543447ca4.png)

```html:index.html
<!DOCTYPE html>
<html>
<head>
    <title>Flower Petal Dance</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
</head>
<body>
<canvas id="renderCanvas" touch-action="none"></canvas>
<input type="file" id="fileInput" accept="audio/*" style="position: absolute; top: 10px; left: 10px; z-index: 1000;">
<script>
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.Engine(canvas, true);
    const scene = new BABYLON.Scene(engine);
    const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 4, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

    // 花びらの形状
    const flowerPetal = BABYLON.MeshBuilder.CreateDisc("petal", { radius: 0.5, tessellation: 64 }, scene);
    flowerPetal.rotation.x = Math.PI / 2;

    // マテリアル
    const petalMaterial = new BABYLON.StandardMaterial("petalMaterial", scene);
    petalMaterial.diffuseColor = new BABYLON.Color3(1, 0.5, 0.5);
    flowerPetal.material = petalMaterial;

    // 花びらを大量に生成
    const petals = [];
    for (let i = 0; i < 100; i++) {
        const petalInstance = flowerPetal.clone("petal" + i);
        petalInstance.position = new BABYLON.Vector3(
            Math.random() * 20 - 10,
            Math.random() * 10,
            Math.random() * 20 - 10
        );
        petals.push(petalInstance);
    }

    // 音楽の再生設定
    const fileInput = document.getElementById("fileInput");
    let audioContext = null;
    let analyser = null;
    let frequencyData = null;

    fileInput.addEventListener("change", (event) => {
        const file = event.target.files[0];
        if (file) {
            const audioElement = new Audio(URL.createObjectURL(file));
            audioElement.crossOrigin = "anonymous"; // クロスオリジンの制限を回避
            audioElement.play();

            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 256;
                frequencyData = new Uint8Array(analyser.frequencyBinCount);
            }

            const source = audioContext.createMediaElementSource(audioElement);
            source.connect(analyser);
            analyser.connect(audioContext.destination);
        }
    });

    // アニメーション
    scene.registerBeforeRender(() => {
        if (analyser && frequencyData) {
            analyser.getByteFrequencyData(frequencyData);
            const beatStrength = frequencyData[0] / 255; // 周波数データを正規化

            petals.forEach(petal => {
                petal.position.y += Math.sin(scene.getEngine().getDeltaTime() * 0.001 + Math.random()) * beatStrength;
                petal.rotation.y += beatStrength * 0.1;
            });
        }
    });

    engine.runRenderLoop(() => {
        scene.render();
    });

    window.addEventListener("resize", () => {
        engine.resize();
    });
</script>
</body>
</html>

```

![スクリーンショット 2024-12-20 0.24.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/1c312a83-2133-b452-e317-1288a8a9fc0d.png)
![スクリーンショット 2024-12-20 0.25.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/a8a8d500-f9c1-6d78-0d0d-18f1fa3d3f23.png)

```html:index.html
<!DOCTYPE html>
<html>
<head>
    <title>Flower Petal Dance</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
</head>
<body>
<canvas id="renderCanvas" touch-action="none"></canvas>
<input type="file" id="fileInput" accept="audio/*" style="position: absolute; top: 10px; left: 10px; z-index: 1000;">
<script>
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.Engine(canvas, true);
    const scene = new BABYLON.Scene(engine);
    const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 4, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

    // 花びらの形状
    const flowerPetal = BABYLON.MeshBuilder.CreateDisc("petal", { radius: 0.5, tessellation: 64 }, scene);
    flowerPetal.rotation.x = Math.PI / 2;

    // マテリアル
    const petalMaterial = new BABYLON.StandardMaterial("petalMaterial", scene);
    flowerPetal.material = petalMaterial;

    // 花びらを大量に生成
    const petals = [];
    for (let i = 0; i < 100; i++) {
        const petalInstance = flowerPetal.clone("petal" + i);
        petalInstance.position = new BABYLON.Vector3(
            Math.random() * 20 - 10,
            Math.random() * 10,
            Math.random() * 20 - 10
        );
        petals.push(petalInstance);
    }

    // 音楽の再生設定
    const fileInput = document.getElementById("fileInput");
    let audioContext = null;
    let analyser = null;
    let frequencyData = null;
    let isPlaying = false;

    fileInput.addEventListener("change", (event) => {
        const file = event.target.files[0];
        if (file) {
            const audioElement = new Audio(URL.createObjectURL(file));
            audioElement.crossOrigin = "anonymous"; // クロスオリジンの制限を回避
            audioElement.play();
            isPlaying = true;

            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 256;
                frequencyData = new Uint8Array(analyser.frequencyBinCount);
            }

            const source = audioContext.createMediaElementSource(audioElement);
            source.connect(analyser);
            analyser.connect(audioContext.destination);

            // 再生終了時にアニメーション停止
            audioElement.addEventListener("ended", () => {
                isPlaying = false;
            });
        }
    });

    // アニメーション
    scene.registerBeforeRender(() => {
        if (isPlaying && analyser && frequencyData) {
            analyser.getByteFrequencyData(frequencyData);
            const beatStrength = frequencyData[0] / 255; // 周波数データを正規化

            petals.forEach(petal => {
                // 花びらの動き
                petal.position.y += Math.sin(scene.getEngine().getDeltaTime() * 0.001 + Math.random()) * beatStrength;
                if (petal.position.y > 10) petal.position.y = -10; // Y座標が上限を超えたらリセット
                petal.rotation.y += beatStrength * 0.1;

                // 色の変化
                const colorFactor = frequencyData[Math.floor(Math.random() * frequencyData.length)] / 255;
                petal.material.diffuseColor = new BABYLON.Color3(colorFactor, 1 - colorFactor, Math.random());
            });
        }
    });

    engine.runRenderLoop(() => {
        scene.render();
    });

    window.addEventListener("resize", () => {
        engine.resize();
    });
</script>
</body>
</html>
```
![スクリーンショット 2024-12-20 0.26.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/57b6c56c-b780-6a5b-40ce-c9d1a46a0e19.png)


![スクリーンショット 2024-12-20 0.27.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/b38bacb8-e8c2-bf7a-9e7e-a77a32669e7e.png)

![スクリーンショット 2024-12-20 0.26.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/57b6c56c-b780-6a5b-40ce-c9d1a46a0e19.png)


```html:index.html
<!DOCTYPE html>
<html>
<head>
    <title>Flower Petal Dance</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
</head>
<body>
<canvas id="renderCanvas" touch-action="none"></canvas>
<input type="file" id="fileInput" accept="audio/*" style="position: absolute; top: 10px; left: 10px; z-index: 1000;">
<script>
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.Engine(canvas, true);
    const scene = new BABYLON.Scene(engine);
    const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 4, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

    // 花びらの形状
    const flowerPetal = BABYLON.MeshBuilder.CreateDisc("petal", { radius: 0.5, tessellation: 64 }, scene);
    flowerPetal.rotation.x = Math.PI / 2;

    // マテリアル
    const petalMaterial = new BABYLON.StandardMaterial("petalMaterial", scene);
    flowerPetal.material = petalMaterial;

    // 花びらを大量に生成
    const petals = [];
    for (let i = 0; i < 100; i++) {
        const petalInstance = flowerPetal.clone("petal" + i);
        petalInstance.position = new BABYLON.Vector3(
            Math.random() * 20 - 10,
            Math.random() * 10,
            Math.random() * 20 - 10
        );
        petalInstance.rotation = new BABYLON.Vector3(
            Math.random() * Math.PI,
            Math.random() * Math.PI,
            Math.random() * Math.PI
        );
        petalInstance.colorChangeFactor = Math.random(); // 色変化速度を個別に設定
        petals.push(petalInstance);
    }

    // 音楽の再生設定
    const fileInput = document.getElementById("fileInput");
    let audioContext = null;
    let analyser = null;
    let frequencyData = null;
    let isPlaying = false;

    fileInput.addEventListener("change", (event) => {
        const file = event.target.files[0];
        if (file) {
            const audioElement = new Audio(URL.createObjectURL(file));
            audioElement.crossOrigin = "anonymous"; // クロスオリジンの制限を回避
            audioElement.play();
            isPlaying = true;

            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 256;
                frequencyData = new Uint8Array(analyser.frequencyBinCount);
            }

            const source = audioContext.createMediaElementSource(audioElement);
            source.connect(analyser);
            analyser.connect(audioContext.destination);

            // 再生終了時にアニメーション停止
            audioElement.addEventListener("ended", () => {
                isPlaying = false;
            });
        }
    });

    // アニメーション
    let colorChangeSpeed = 0.01; // 色変化の速さを調整

    scene.registerBeforeRender(() => {
        if (isPlaying && analyser && frequencyData) {
            analyser.getByteFrequencyData(frequencyData);
            const beatStrength = frequencyData[0] / 255; // 周波数データを正規化

            petals.forEach(petal => {
                // 花びらのひらひら動き
                petal.position.y += Math.sin(scene.getEngine().getDeltaTime() * 0.001 + petal.position.x) * 0.05;
                petal.position.x += Math.cos(scene.getEngine().getDeltaTime() * 0.001 + petal.position.z) * 0.02;

                // Y軸を超えたらリセット
                if (petal.position.y > 10) petal.position.y = -10;

                // ゆっくりとした回転
                petal.rotation.x += 0.005;
                petal.rotation.z += 0.003;

                // 色の変化
                petal.colorChangeFactor += colorChangeSpeed * Math.random(); // 個別の色変化速度
                if (petal.colorChangeFactor > 1) petal.colorChangeFactor = 0;
                const colorFactor = petal.colorChangeFactor;
                petal.material.diffuseColor = new BABYLON.Color3(
                    Math.sin(colorFactor * Math.PI),
                    Math.sin((colorFactor + 0.33) * Math.PI),
                    Math.sin((colorFactor + 0.66) * Math.PI)
                );
            });
        }
    });

    engine.runRenderLoop(() => {
        scene.render();
    });

    window.addEventListener("resize", () => {
        engine.resize();
    });
</script>
</body>
</html>
```
![スクリーンショット 2024-12-20 0.28.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/79ede0e5-b17f-7c62-1715-7b26be06a8b0.png)

![スクリーンショット 2024-12-20 0.28.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/db1da2ea-ea48-3b19-1e36-b81277aa3d25.png)

![スクリーンショット 2024-12-20 0.29.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/3044d1ad-0467-3360-3e5f-58f5ea8564d6.png)


```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flower Petal Dance</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            overflow: hidden;
            width: 100%;
            height: 100%;
        }
        #renderCanvas {
            display: block;
            width: 100%;
            height: 100%;
        }
        #fileInput {
            position: absolute;
            top: 10px;
            left: 10px;
            z-index: 1000;
        }
    </style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<input type="file" id="fileInput" accept="audio/*">
<script>
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.Engine(canvas, true);
    const scene = new BABYLON.Scene(engine);
    const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 4, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

    // 花びらの形状
    const flowerPetal = BABYLON.MeshBuilder.CreateDisc("petal", { radius: 0.5, tessellation: 64 }, scene);
    flowerPetal.rotation.x = Math.PI / 2;

    // マテリアル
    const petalMaterial = new BABYLON.StandardMaterial("petalMaterial", scene);
    flowerPetal.material = petalMaterial;

    // 花びらを大量に生成
    const petals = [];
    for (let i = 0; i < 100; i++) {
        const petalInstance = flowerPetal.clone("petal" + i);
        petalInstance.position = new BABYLON.Vector3(
            Math.random() * 20 - 10,
            Math.random() * 10,
            Math.random() * 20 - 10
        );
        petalInstance.rotation = new BABYLON.Vector3(
            Math.random() * Math.PI,
            Math.random() * Math.PI,
            Math.random() * Math.PI
        );
        petalInstance.colorChangeFactor = Math.random();
        petals.push(petalInstance);
    }

    // 音楽の再生設定
    const fileInput = document.getElementById("fileInput");
    let audioContext = null;
    let analyser = null;
    let frequencyData = null;
    let isPlaying = false;

    fileInput.addEventListener("change", (event) => {
        const file = event.target.files[0];
        if (file) {
            const audioElement = new Audio(URL.createObjectURL(file));
            audioElement.crossOrigin = "anonymous";
            audioElement.play();
            isPlaying = true;

            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 256;
                frequencyData = new Uint8Array(analyser.frequencyBinCount);
            }

            const source = audioContext.createMediaElementSource(audioElement);
            source.connect(analyser);
            analyser.connect(audioContext.destination);

            // 再生終了時にアニメーション停止
            audioElement.addEventListener("ended", () => {
                isPlaying = false;
            });
        }
    });

    // アニメーション
    let colorChangeSpeed = 0.01;

    scene.registerBeforeRender(() => {
        if (isPlaying && analyser && frequencyData) {
            analyser.getByteFrequencyData(frequencyData);
            const beatStrength = frequencyData[0] / 255;

            petals.forEach(petal => {
                petal.position.y += Math.sin(scene.getEngine().getDeltaTime() * 0.001 + petal.position.x) * 0.05;
                petal.position.x += Math.cos(scene.getEngine().getDeltaTime() * 0.001 + petal.position.z) * 0.02;
                if (petal.position.y > 10) petal.position.y = -10;

                petal.rotation.x += 0.005;
                petal.rotation.z += 0.003;

                petal.colorChangeFactor += colorChangeSpeed * Math.random();
                if (petal.colorChangeFactor > 1) petal.colorChangeFactor = 0;
                const colorFactor = petal.colorChangeFactor;
                petal.material.diffuseColor = new BABYLON.Color3(
                    Math.sin(colorFactor * Math.PI),
                    Math.sin((colorFactor + 0.33) * Math.PI),
                    Math.sin((colorFactor + 0.66) * Math.PI)
                );
            });
        }
    });

    engine.runRenderLoop(() => {
        scene.render();
    });

    window.addEventListener("resize", () => {
        engine.resize();
    });
</script>
</body>
</html>

```
![スクリーンショット 2024-12-20 0.30.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/a3d0f8aa-fada-70cc-bcc3-2c0f43b19f17.png)

![スクリーンショット 2024-12-20 0.31.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/b6e8c7a6-c7d5-702d-a592-34b73f28f9e3.png)

![スクリーンショット 2024-12-20 0.32.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/500c48b3-3777-b604-3e25-9190cbbf9256.png)

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flower Petal Dance</title>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            overflow: hidden;
            width: 100%;
            height: 100%;
        }
        #renderCanvas {
            display: block;
            width: 100%;
            height: 100%;
        }
        #fileInput {
            position: absolute;
            top: 10px;
            left: 10px;
            z-index: 1000;
        }
    </style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<input type="file" id="fileInput" accept="audio/*">
<script>
    const canvas = document.getElementById("renderCanvas");
    const engine = new BABYLON.Engine(canvas, true);
    const scene = new BABYLON.Scene(engine);

    // 背景を白に設定
    scene.clearColor = new BABYLON.Color3(1, 1, 1);

    const camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 4, 20, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

    // 花びらの形状
    const flowerPetal = BABYLON.MeshBuilder.CreateDisc("petal", { radius: 0.5, tessellation: 64 }, scene);
    flowerPetal.rotation.x = Math.PI / 2;

    // マテリアル
    const petalMaterial = new BABYLON.StandardMaterial("petalMaterial", scene);
    flowerPetal.material = petalMaterial;

    // 花びらを大量に生成
    const petals = [];
    const totalPetals = 300; // 花びらの数を増加
    for (let i = 0; i < totalPetals; i++) {
        const petalInstance = flowerPetal.clone("petal" + i);
        petalInstance.position = new BABYLON.Vector3(
            Math.random() * 40 - 20,
            Math.random() * 20,
            Math.random() * 40 - 20
        );
        petalInstance.rotation = new BABYLON.Vector3(
            Math.random() * Math.PI,
            Math.random() * Math.PI,
            Math.random() * Math.PI
        );
        petalInstance.colorChangeFactor = Math.random();
        petals.push(petalInstance);
    }

    // 音楽の再生設定
    const fileInput = document.getElementById("fileInput");
    let audioContext = null;
    let analyser = null;
    let frequencyData = null;
    let isPlaying = false;

    fileInput.addEventListener("change", (event) => {
        const file = event.target.files[0];
        if (file) {
            const audioElement = new Audio(URL.createObjectURL(file));
            audioElement.crossOrigin = "anonymous";
            audioElement.play();
            isPlaying = true;

            if (!audioContext) {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                analyser = audioContext.createAnalyser();
                analyser.fftSize = 256;
                frequencyData = new Uint8Array(analyser.frequencyBinCount);
            }

            const source = audioContext.createMediaElementSource(audioElement);
            source.connect(analyser);
            analyser.connect(audioContext.destination);

            // 再生終了時にアニメーション停止
            audioElement.addEventListener("ended", () => {
                isPlaying = false;
            });
        }
    });

    // アニメーション
    let colorChangeSpeed = 0.005; // 色変化をゆっくり

    scene.registerBeforeRender(() => {
        if (isPlaying && analyser && frequencyData) {
            analyser.getByteFrequencyData(frequencyData);
            const beatStrength = frequencyData[0] / 255;

            petals.forEach(petal => {
                // 花びらのひらひら動き
                petal.position.y += Math.sin(scene.getEngine().getDeltaTime() * 0.001 + petal.position.x) * 0.03;
                petal.position.x += Math.cos(scene.getEngine().getDeltaTime() * 0.001 + petal.position.z) * 0.02;
                if (petal.position.y > 20) petal.position.y = -5;

                petal.rotation.x += 0.002;
                petal.rotation.z += 0.001;

                petal.colorChangeFactor += colorChangeSpeed * Math.random();
                if (petal.colorChangeFactor > 1) petal.colorChangeFactor = 0;
                const colorFactor = petal.colorChangeFactor;
                petal.material.diffuseColor = new BABYLON.Color3(
                    Math.sin(colorFactor * Math.PI),
                    Math.sin((colorFactor + 0.33) * Math.PI),
                    Math.sin((colorFactor + 0.66) * Math.PI)
                );
            });
        }
    });

    engine.runRenderLoop(() => {
        scene.render();
    });

    window.addEventListener("resize", () => {
        engine.resize();
    });
</script>
</body>
</html>
```
![スクリーンショット 2024-12-20 0.33.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1032713/9456554c-63d8-d765-48d5-bfdb1ffde57c.png)

# 実行結果

音楽に合わせて？オブジェクトが舞うようになった！
マウス操作で、３D空間を移動、拡大縮小ができる。
とても便利なライブラリ！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Babylon.jsの実験中 <a href="https://t.co/yTVmCjPqdA">pic.twitter.com/yTVmCjPqdA</a></p>&mdash; MakerYuki (@maker_yuki) <a href="https://twitter.com/maker_yuki/status/1869755751268508077?ref_src=twsrc%5Etfw">December 19, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>