メインコンテンツへスキップ
見出し画像

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

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




     
     
     
    フォロバ100%です。暇な時間を折り紙でつぶす人。たまに自分でも何を言っているのか分からなくなる。でもマイクラもフォトナもやる。(?)実は折り紙より得意。 …………………………

    あなたへのおすすめ