<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name = "viewport" content = "width=device-width, initial-scale = 1.0">
<title>テスト</title>
<style>
#canvas {
    display: block;
    margin-bottom: 10px;
}
#rotate-right, #rotate-top {
    width: 100px;
    height: 50px;
}
</style>
</head>

<body>
<canvas id="canvas"></canvas>
<button id = "rotate-right">右側を回転</button>
<button id = "rotate-top">上側を回転</button>

<script type="module">
    import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.167.0/build/three.module.js";

    // サイズを指定
    const width = 360;
    const height = 360;

    // レンダラー,シーン,カメラを作成
    const renderer = new THREE.WebGLRenderer({
        canvas: document.getElementById('canvas'),
    });
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(45, width / height);

    // 箱
    /** @type {THREE.Mesh} */
    let box;

    window.onload = () => {
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(width, height);
        camera.position.set(0, 0, 150);

        addEventListeners();
        addObjects();

        const INTERVAL = 1000 / 60;
        let nextUpdateTime = new Date().getTime() + INTERVAL;
        frameProc();

        function frameProc(){
            const curTime = new Date().getTime();
            if(nextUpdateTime < curTime){
                nextUpdateTime +=  INTERVAL;
                update();
            }
            requestAnimationFrame(() => frameProc());
        }
    }

    // 箱
    /** @type {THREE.Mesh[]} */
    let boxes = [];

    function addObjects(){
        // MeshPhongMaterialを使用するのでライトが必要
        // 環境光
        const ambientLight = new THREE.AmbientLight(0xFFFFFF, 1.0);
        scene.add(ambientLight);

        // 平行光源
        const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 3);
        directionalLight.position.set(0, 0, 150);
        scene.add(directionalLight);

        const geometry = new THREE.BoxGeometry(16, 16, 16);

        const arr = [
            {x:24, y:24, z:24},
            {x:-24, y:24, z:24},
            {x:24, y:-24, z:24},
            {x:-24, y:-24, z:24},
            {x:24, y:24, z:-24},
            {x:-24, y:24, z:-24},
            {x:24, y:-24, z:-24},
            {x:-24, y:-24, z:-24},
        ];
        const materials = [
            new THREE.MeshPhongMaterial({color: 0xff0000}),
            new THREE.MeshPhongMaterial({color: 0x00ff00}),
            new THREE.MeshPhongMaterial({color: 0x0000ff}),
            new THREE.MeshPhongMaterial({color: 0xffff00}),
            new THREE.MeshPhongMaterial({color: 0xff00ff}),
            new THREE.MeshPhongMaterial({color: 0x00ffff}),
            new THREE.MeshPhongMaterial({color: 0x800080}),
            new THREE.MeshPhongMaterial({color: 0x008080}),
        ];
        
        // 一辺の長さが48の立方体の頂点にあたる座標に8個の立方体をつくる
        for(let i = 0; i < arr.length; i++){
            const box = new THREE.Mesh(geometry, materials[i]);
            box.position.set(arr[i].x, arr[i].y, arr[i].z);
            scene.add(box);
            boxes.push(box);
        }
    }

    function addEventListeners(){
        // 回転処理が重なると位置関係がおかしくなるので複数の回転が同時におきないようにする
        let noMeve = false;

        document.getElementById('rotate-right')?.addEventListener('click', async() => {
            if(noMeve)
                return;

            noMeve = true;

            // グループを作り、3D空間に追加する
            /** @type {THREE.Group} */
            let group = new THREE.Group();
            scene.add(group);

            const rights = boxes.filter(box => box.position.x > 10);

            // グループに該当するボックスを追加
            for(let i=0; i<rights.length; i++)
                group.add(rights[i]);

            // 64回の更新で90度回転させる
            const angle = Math.PI / 2 / 64;
            for(let i= 0; i < 64; i++){
                await new Promise(resolve => setTimeout(resolve, 1000 / 60));
                group.rotation.x += angle;
            }

            // 回転処理が終わったのでグループを削除するが、
            // 3Dオブジェクトをsceneに追加しなおす
            // そのさい3Dオブジェクトの位置と回転状態を取得して設定しなおす
            for(let i=0; i<rights.length; i++){
                const worldPosition = rights[i].getWorldPosition(new THREE.Vector3());
                const worldQuaternion = rights[i].getWorldQuaternion(new THREE.Quaternion());
                group.remove(rights[i]);

                rights[i].position.set(worldPosition.x, worldPosition.y, worldPosition.z);
                rights[i].setRotationFromQuaternion(worldQuaternion);
                scene.add(rights[i]);
            }
            scene.remove(group);
            noMeve = false;
        });
        document.getElementById('rotate-top')?.addEventListener('click', async() => {
            if(noMeve)
                return;

            noMeve = true;
            let group = new THREE.Group();
            scene.add(group);

            const tops = boxes.filter(box => box.position.y > 10);

            for(let i=0; i<tops.length; i++)
                group.add(tops[i]);

            const angle = Math.PI / 2 / 64;
            for(let i=0; i<64; i++){
                await new Promise(resolve => setTimeout(resolve, 1000 / 60));
                group.rotateY(angle);
            }

            for(let i=0; i<tops.length; i++){
                const worldPosition = tops[i].getWorldPosition(new THREE.Vector3());
                const worldQuaternion = tops[i].getWorldQuaternion(new THREE.Quaternion());
                group.remove(tops[i]);

                tops[i].position.set(worldPosition.x, worldPosition.y, worldPosition.z);
                tops[i].setRotationFromQuaternion(worldQuaternion);
                scene.add(tops[i]);
            }
            scene.remove(group);
            noMeve = false;
        });
    }

    function update() {
        renderer.render(scene, camera); // レンダリング
    }
</script>
</body>
</html>