見出し画像

3 D で 五 目 並 べ を 作 る ①

キモ過ぎるタイトルとサムネの先には…?

(特に何も無い)



AIを駆使して

3D五目並べ

を作ります。

コード書くのってマジで大変


正二十面体


一つの面を右クリックすると〇が現れる
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>インタラクティブ正二十面体</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            overflow: hidden;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
        }

        #canvas-container {
            width: 100%;
            height: 100%;
            cursor: grab;
        }

        #canvas-container:active {
            cursor: grabbing;
        }

        #info {
            position: absolute;
            top: 20px;
            left: 20px;
            color: white;
            background: rgba(0, 0, 0, 0.5);
            padding: 20px;
            border-radius: 10px;
            backdrop-filter: blur(10px);
            font-size: 14px;
            line-height: 1.6;
        }

        #debug {
            position: absolute;
            top: 150px;
            left: 20px;
            color: yellow;
            background: rgba(255, 0, 0, 0.7);
            padding: 15px;
            border-radius: 10px;
            font-size: 16px;
            font-weight: bold;
            min-width: 200px;
        }

        #info h1 {
            font-size: 24px;
            margin-bottom: 10px;
            font-weight: 600;
        }

        #info p {
            margin: 5px 0;
        }
    </style>
</head>

<body>
    <div id="info">
        <h1>正二十面体</h1>
        <p>🖱️ 左クリック長押しでドラッグして回転</p>
        <p>⭕ 左クリック: ○を描画</p>
        <p>⭕ 右クリック: ○を描画</p>
        <p>📐 面の数: 20</p>
        <p>🔺 頂点の数: 12</p>
    </div>
    <div id="debug">デバッグ情報がここに表示されます</div>
    <div id="canvas-container"></div>

    <script src="/https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        // シーン、カメラ、レンダラーの設定
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });

        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        document.getElementById('canvas-container').appendChild(renderer.domElement);

        // カメラの位置
        camera.position.z = 5;

        // 照明の設定
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
        scene.add(ambientLight);

        const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight1.position.set(5, 5, 5);
        scene.add(directionalLight1);

        const directionalLight2 = new THREE.DirectionalLight(0x6667ab, 0.5);
        directionalLight2.position.set(-5, -5, -5);
        scene.add(directionalLight2);

        // 正二十面体の作成
        const geometry = new THREE.IcosahedronGeometry(2, 0);

        // メインマテリアル(統一された色)
        const material = new THREE.MeshBasicMaterial({
            color: 0x00d4ff,
            side: THREE.DoubleSide
        });

        const icosahedron = new THREE.Mesh(geometry, material);

        // ワイヤーフレームの追加(エッジを強調)
        const wireframeGeometry = new THREE.EdgesGeometry(geometry);
        const wireframeMaterial = new THREE.LineBasicMaterial({
            color: 0xffffff,
            linewidth: 2,
            transparent: true,
            opacity: 0.6
        });
        const wireframe = new THREE.LineSegments(wireframeGeometry, wireframeMaterial);
        icosahedron.add(wireframe);

        // 一つの面が正面を向くように回転を調整
        const faces = geometry.attributes.position;
        const v0 = new THREE.Vector3(faces.getX(0), faces.getY(0), faces.getZ(0));
        const v1 = new THREE.Vector3(faces.getX(1), faces.getY(1), faces.getZ(1));
        const v2 = new THREE.Vector3(faces.getX(2), faces.getY(2), faces.getZ(2));

        const edge1 = new THREE.Vector3().subVectors(v1, v0);
        const edge2 = new THREE.Vector3().subVectors(v2, v0);
        const faceNormal = new THREE.Vector3().crossVectors(edge1, edge2).normalize();

        const targetNormal = new THREE.Vector3(0, 0, 1);
        const quaternion = new THREE.Quaternion().setFromUnitVectors(faceNormal, targetNormal);
        icosahedron.quaternion.copy(quaternion);

        scene.add(icosahedron);

        // Raycaster for click detection
        const raycaster = new THREE.Raycaster();
        const mouse = new THREE.Vector2();

        // Store face labels
        const faceLabels = new Map();

        // Function to create 3D circle (torus)
        function create3DCircle(color = 0xff0000) {
            const geometry = new THREE.TorusGeometry(0.5, 0.1, 16, 32);
            const material = new THREE.MeshBasicMaterial({ color: color, side: THREE.DoubleSide });
            const torus = new THREE.Mesh(geometry, material);
            return torus;
        }

        // Function to get face center
        function getFaceCenter(faceIndex) {
            const positions = geometry.attributes.position;
            const indices = geometry.index;

            let i1, i2, i3;

            if (indices) {
                // インデックスがある場合
                i1 = indices.array[faceIndex * 3];
                i2 = indices.array[faceIndex * 3 + 1];
                i3 = indices.array[faceIndex * 3 + 2];
            } else {
                // インデックスがない場合(非インデックスジオメトリ)
                i1 = faceIndex * 3;
                i2 = faceIndex * 3 + 1;
                i3 = faceIndex * 3 + 2;
            }

            const v1 = new THREE.Vector3(positions.array[i1 * 3], positions.array[i1 * 3 + 1], positions.array[i1 * 3 + 2]);
            const v2 = new THREE.Vector3(positions.array[i2 * 3], positions.array[i2 * 3 + 1], positions.array[i2 * 3 + 2]);
            const v3 = new THREE.Vector3(positions.array[i3 * 3], positions.array[i3 * 3 + 1], positions.array[i3 * 3 + 2]);

            const center = new THREE.Vector3();
            center.add(v1).add(v2).add(v3).divideScalar(3);

            return center;
        }

        // 初期化:全ての面に○を配置(非表示)
        function initializeFaceCircles() {
            const faceCount = 20; // 正二十面体は常に20面
            console.log('🎯 全', faceCount, '面に○を配置開始...');

            for (let faceIndex = 0; faceIndex < faceCount; faceIndex++) {
                const circle = create3DCircle(0xff0000);

                // 面の中心を取得
                const faceCenter = getFaceCenter(faceIndex);

                // 正二十面体の中心から面の中心へ向かうベクトルが法線
                const normal = faceCenter.clone().normalize();

                // トーラスを面の法線に向ける
                const up = new THREE.Vector3(0, 0, 1);
                const quat = new THREE.Quaternion().setFromUnitVectors(up, normal);
                circle.quaternion.copy(quat);

                // 面の中心から少し外側に配置
                const position = faceCenter.clone().add(normal.multiplyScalar(0.2));
                circle.position.copy(position);

                // 非表示にして追加
                circle.visible = false;
                icosahedron.add(circle);
                faceLabels.set(faceIndex, circle);
            }

            console.log('✅ 全ての面に○を配置完了(非表示)。数:', faceLabels.size);
        }

        // 起動時に全面に○を配置
        console.log('初期化関数を呼び出します...');
        try {
            initializeFaceCircles();
        } catch (e) {
            console.error('初期化関数でエラー:', e);
        }

        // 面の○を表示
        function showFaceLabel(faceIndex) {
            if (faceLabels.has(faceIndex)) {
                const circle = faceLabels.get(faceIndex);
                circle.visible = true;
                console.log('✅ 面', faceIndex, 'の○を表示しました');
            }
        }

        // マウスドラッグによる回転の実装
        let isDragging = false;
        let hasDragged = false;
        let mouseDownTime = 0;
        let mouseDownPos = { x: 0, y: 0 };
        let previousMousePosition = { x: 0, y: 0 };
        let rotationVelocity = { x: 0, y: 0 };

        renderer.domElement.addEventListener('mousedown', (e) => {
            if (e.button === 0) {
                isDragging = true;
                hasDragged = false;
                mouseDownTime = Date.now();
                mouseDownPos = { x: e.clientX, y: e.clientY };
                previousMousePosition = { x: e.clientX, y: e.clientY };
                rotationVelocity = { x: 0, y: 0 };
            }
        });

        window.addEventListener('mousemove', (e) => {
            if (isDragging) {
                const deltaX = e.clientX - previousMousePosition.x;
                const deltaY = e.clientY - previousMousePosition.y;

                if (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3) {
                    hasDragged = true;
                }

                rotationVelocity.x = deltaY * 0.005;
                rotationVelocity.y = deltaX * 0.005;

                const rotationSpeed = 0.005;

                const quaternionY = new THREE.Quaternion();
                quaternionY.setFromAxisAngle(new THREE.Vector3(0, 1, 0), deltaX * rotationSpeed);

                const quaternionX = new THREE.Quaternion();
                const xAxis = new THREE.Vector3(1, 0, 0);
                xAxis.applyQuaternion(camera.quaternion);
                quaternionX.setFromAxisAngle(xAxis, deltaY * rotationSpeed);

                icosahedron.quaternion.multiplyQuaternions(quaternionY, icosahedron.quaternion);
                icosahedron.quaternion.multiplyQuaternions(quaternionX, icosahedron.quaternion);

                previousMousePosition = { x: e.clientX, y: e.clientY };
            }
        });

        window.addEventListener('mouseup', (e) => {
            if (e.button === 0) {
                const clickDuration = Date.now() - mouseDownTime;
                const distMoved = Math.sqrt(
                    Math.pow(e.clientX - mouseDownPos.x, 2) +
                    Math.pow(e.clientY - mouseDownPos.y, 2)
                );

                if (!hasDragged && clickDuration < 300 && distMoved < 5) {
                    mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
                    mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;

                    raycaster.setFromCamera(mouse, camera);
                    const intersects = raycaster.intersectObject(icosahedron, false);

                    if (intersects.length > 0) {
                        const faceIndex = intersects[0].faceIndex;
                        console.log('左クリック検出 - 面:', faceIndex);
                        showFaceLabel(faceIndex);
                    }
                }

                isDragging = false;
                hasDragged = false;
            }
        });

        // Right click handler
        renderer.domElement.addEventListener('contextmenu', (e) => {
            e.preventDefault();

            const debugDiv = document.getElementById('debug');
            debugDiv.innerHTML = '右クリック検出!<br>マウス位置: ' + e.clientX + ', ' + e.clientY;

            mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;

            debugDiv.innerHTML += '<br>正規化座標: ' + mouse.x.toFixed(2) + ', ' + mouse.y.toFixed(2);

            raycaster.setFromCamera(mouse, camera);
            const intersects = raycaster.intersectObject(icosahedron, false);

            debugDiv.innerHTML += '<br>交点数: ' + intersects.length;

            if (intersects.length > 0) {
                const faceIndex = intersects[0].faceIndex;
                debugDiv.innerHTML += '<br>面番号: ' + faceIndex;

                if (faceIndex !== undefined && faceIndex !== null) {
                    console.log('右クリック検出 - 面:', faceIndex);
                    showFaceLabel(faceIndex);
                    debugDiv.innerHTML += '<br>✅ ○を表示!';
                }
            } else {
                debugDiv.innerHTML += '<br>❌ 面が見つかりません';
            }

            return false;
        });

        // タッチデバイス対応
        let touchStartPos = { x: 0, y: 0 };

        renderer.domElement.addEventListener('touchstart', (e) => {
            e.preventDefault();
            const touch = e.touches[0];
            touchStartPos = { x: touch.clientX, y: touch.clientY };
            previousMousePosition = { x: touch.clientX, y: touch.clientY };
            isDragging = true;
        });

        renderer.domElement.addEventListener('touchmove', (e) => {
            e.preventDefault();
            if (isDragging && e.touches.length === 1) {
                const touch = e.touches[0];
                const deltaX = touch.clientX - previousMousePosition.x;
                const deltaY = touch.clientY - previousMousePosition.y;

                rotationVelocity.x = deltaY * 0.005;
                rotationVelocity.y = deltaX * 0.005;

                const rotationSpeed = 0.005;

                const quaternionY = new THREE.Quaternion();
                quaternionY.setFromAxisAngle(new THREE.Vector3(0, 1, 0), deltaX * rotationSpeed);

                const quaternionX = new THREE.Quaternion();
                const xAxis = new THREE.Vector3(1, 0, 0);
                xAxis.applyQuaternion(camera.quaternion);
                quaternionX.setFromAxisAngle(xAxis, deltaY * rotationSpeed);

                icosahedron.quaternion.multiplyQuaternions(quaternionY, icosahedron.quaternion);
                icosahedron.quaternion.multiplyQuaternions(quaternionX, icosahedron.quaternion);

                previousMousePosition = { x: touch.clientX, y: touch.clientY };
            }
        });

        renderer.domElement.addEventListener('touchend', (e) => {
            e.preventDefault();
            isDragging = false;
        });

        // ウィンドウリサイズ対応
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        // アニメーションループ
        function animate() {
            requestAnimationFrame(animate);

            if (!isDragging) {
                rotationVelocity.x *= 0.95;
                rotationVelocity.y *= 0.95;

                if (Math.abs(rotationVelocity.x) > 0.0001 || Math.abs(rotationVelocity.y) > 0.0001) {
                    const quaternionY = new THREE.Quaternion();
                    quaternionY.setFromAxisAngle(new THREE.Vector3(0, 1, 0), rotationVelocity.y);

                    const quaternionX = new THREE.Quaternion();
                    const xAxis = new THREE.Vector3(1, 0, 0);
                    xAxis.applyQuaternion(camera.quaternion);
                    quaternionX.setFromAxisAngle(xAxis, rotationVelocity.x);

                    icosahedron.quaternion.multiplyQuaternions(quaternionY, icosahedron.quaternion);
                    icosahedron.quaternion.multiplyQuaternions(quaternionX, icosahedron.quaternion);
                }
            }

            renderer.render(scene, camera);
        }

        animate();
    </script>
</body>

</html>

Created with assistance from AI

まだ作成途中なので、あまり期待しないで💦。




いいなと思ったら応援しよう!

Rikuriku|フォロバ100 ⚠️警告⚠️ もしあなたがチップをくれたら、その分あなたのお金が減ります(?)。お金は大切に。