🔬 AI発明ギャラリー syunsuke ミズダコ3D
🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
HTML / CSS / JS
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ミズダコ 3Dモデル</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #011125; /* 深海の背景色 */
            font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', Meiryo, sans-serif;
            color: white;
            user-select: none;
        }
        #canvas-container {
            width: 100vw;
            height: 100vh;
            display: block;
        }
        #ui-layer {
            position: absolute;
            top: 20px;
            left: 20px;
            pointer-events: none; /* UIの裏側のキャンバスを操作できるようにする */
            z-index: 10;
        }
        h1 {
            margin: 0 0 10px 0;
            font-size: 24px;
            text-shadow: 0 2px 4px rgba(0,0,0,0.5);
            letter-spacing: 2px;
        }
        p {
            margin: 0;
            font-size: 14px;
            color: #a0c4ff;
            text-shadow: 0 1px 2px rgba(0,0,0,0.8);
        }
        #loading {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-size: 20px;
            transition: opacity 0.5s ease;
        }
    </style>
</head>
<body>
    <div id="ui-layer">
        <h1>ミズダコ & タラバガニ & オニカマス</h1>
        <p>左ドラッグ: 回転 | 右ドラッグ: 移動 | スクロール: ズーム</p>
    </div>
    <div id="loading">海に潜っています...</div>
    <div id="canvas-container"></div>

    <!-- Three.js本体とOrbitControlsの読み込み -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>

    <script>
        window.onload = function() {
            init();
            document.getElementById('loading').style.opacity = 0;
            setTimeout(() => document.getElementById('loading').style.display = 'none', 500);
        };

        let scene, camera, renderer, controls;
        let octopusGroup;
        let crabGroup; 
        let barracudaGroup; 
        const tentacles = []; 
        let bubbles = []; 

        let clock = new THREE.Clock();

        function createSeabed() {
            const seabedGeo = new THREE.PlaneGeometry(100, 100, 64, 64);
            const vertices = seabedGeo.attributes.position.array;
            for (let i = 0; i < vertices.length; i += 3) {
                vertices[i + 2] += Math.random() * 0.5 - 0.25;
                vertices[i + 2] += Math.sin(vertices[i] * 0.5) * 0.5;
                vertices[i + 2] += Math.cos(vertices[i+1] * 0.5) * 0.5;
            }
            seabedGeo.computeVertexNormals();

            const seabedMat = new THREE.MeshStandardMaterial({ 
                color: 0x5a4d3c, 
                roughness: 0.9,
                metalness: 0.1
            });
            const seabed = new THREE.Mesh(seabedGeo, seabedMat);
            seabed.rotation.x = -Math.PI / 2;
            seabed.position.y = -0.5; 
            seabed.receiveShadow = true;
            scene.add(seabed);
        }

        function init() {
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x0a4a7c); 
            scene.fog = new THREE.FogExp2(0x0a4a7c, 0.015);

            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 8, 12); 

            renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            document.getElementById('canvas-container').appendChild(renderer.domElement);

            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.maxDistance = 30; 
            controls.minDistance = 3;  
            controls.target.set(0, 0, 0);

            const ambientLight = new THREE.AmbientLight(0x6b8eb3, 1.0);
            scene.add(ambientLight);

            const directionalLight = new THREE.DirectionalLight(0xffeedd, 1.5);
            directionalLight.position.set(5, 15, 10);
            directionalLight.castShadow = true;
            directionalLight.shadow.camera.top = 10;
            directionalLight.shadow.camera.bottom = -10;
            directionalLight.shadow.camera.left = -10;
            directionalLight.shadow.camera.right = 10;
            scene.add(directionalLight);

            const bottomLight = new THREE.DirectionalLight(0x2a3a4a, 0.6);
            bottomLight.position.set(-5, -10, -5);
            scene.add(bottomLight);

            createSeabed();
            createOctopus();
            createRealCrab();
            createBarracuda(); 
            createBubbles();

            window.addEventListener('resize', onWindowResize, false);
            animate();
        }

        function createOctopus() {
            octopusGroup = new THREE.Group();
            scene.add(octopusGroup);

            const skinColor = 0xb84b39; 
            const octopusMaterial = new THREE.MeshStandardMaterial({
                color: skinColor,
                roughness: 0.8, 
                metalness: 0.05,
            });

            const headGeometry = new THREE.SphereGeometry(2.2, 32, 32);
            const head = new THREE.Mesh(headGeometry, octopusMaterial);
            head.position.set(0, 1.6, -2.5); 
            head.scale.set(1.2, 0.7, 1.6); 
            head.rotation.x = -0.1;
            head.castShadow = true;
            head.receiveShadow = true;
            octopusGroup.add(head);

            const bodyGeo = new THREE.SphereGeometry(1.5, 32, 32);
            const body = new THREE.Mesh(bodyGeo, octopusMaterial);
            body.position.set(0, 1.0, -1.0);
            body.scale.set(1.2, 0.5, 1.2);
            body.castShadow = true;
            body.receiveShadow = true;
            octopusGroup.add(body);

            const eyeGeometry = new THREE.SphereGeometry(0.3, 16, 16);
            const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0xffffee, roughness: 0.1 }); 
            const pupilGeometry = new THREE.SphereGeometry(0.15, 16, 16);
            const pupilMaterial = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0 }); 

            const eyelidGeo = new THREE.SphereGeometry(0.45, 16, 16);
            eyelidGeo.scale(1, 0.8, 1);

            const rightEyeGroup = new THREE.Group();
            rightEyeGroup.position.set(1.4, 1.7, -0.6); 
            const rightEyelid = new THREE.Mesh(eyelidGeo, octopusMaterial);
            rightEyeGroup.add(rightEyelid);
            const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
            rightEye.position.set(0.1, 0.1, 0.2); 
            const rightPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
            rightPupil.scale.set(1, 0.3, 1);
            rightPupil.position.set(0.15, 0.05, 0.2); 
            rightPupil.rotation.y = Math.PI / 4; 
            rightEye.add(rightPupil);
            rightEyeGroup.add(rightEye);
            octopusGroup.add(rightEyeGroup);

            const leftEyeGroup = new THREE.Group();
            leftEyeGroup.position.set(-1.4, 1.7, -0.6); 
            const leftEyelid = new THREE.Mesh(eyelidGeo, octopusMaterial);
            leftEyeGroup.add(leftEyelid);
            const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
            leftEye.position.set(-0.1, 0.1, 0.2);
            const leftPupil = new THREE.Mesh(pupilGeometry, pupilMaterial);
            leftPupil.scale.set(1, 0.3, 1);
            leftPupil.position.set(-0.15, 0.05, 0.2);
            leftPupil.rotation.y = -Math.PI / 4;
            leftEye.add(leftPupil);
            leftEyeGroup.add(leftEye);
            octopusGroup.add(leftEyeGroup);

            const funnelGeo = new THREE.CylinderGeometry(0.15, 0.3, 0.8, 16);
            const funnel = new THREE.Mesh(funnelGeo, octopusMaterial);
            funnel.position.set(-1.2, 0.8, -1.0);
            funnel.rotation.x = Math.PI / 2;
            funnel.rotation.z = Math.PI / 4;
            octopusGroup.add(funnel);

            const beakGeo = new THREE.ConeGeometry(0.35, 0.6, 16);
            const beakMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.3 }); 
            const beak = new THREE.Mesh(beakGeo, beakMat);
            beak.position.set(0, 0.2, 0); 
            beak.rotation.x = Math.PI; 
            beak.castShadow = true;
            octopusGroup.add(beak);

            const webSegments = 9; 
            const radialDiv = 4; 
            const xSegments = 8 * radialDiv; 
            
            const webGeo = new THREE.PlaneGeometry(1, 1, xSegments, webSegments - 1);
            const webMat = new THREE.MeshStandardMaterial({
                color: skinColor,
                roughness: 0.8,
                metalness: 0.05,
                side: THREE.DoubleSide 
            });
            const webMesh = new THREE.Mesh(webGeo, webMat);
            webMesh.castShadow = true;
            webMesh.receiveShadow = true;
            octopusGroup.add(webMesh);
            
            octopusGroup.userData.webMesh = webMesh;
            octopusGroup.userData.webSegments = webSegments;
            octopusGroup.userData.radialDiv = radialDiv;

            const numTentacles = 8;
            const segmentsPerTentacle = 20; 

            for (let i = 0; i < numTentacles; i++) {
                const angle = (i / numTentacles) * Math.PI * 2;
                
                const tentacleBase = new THREE.Group();
                const radius = 1.4;
                tentacleBase.position.set(Math.cos(angle) * radius, 0.5, Math.sin(angle) * radius);
                tentacleBase.rotation.set(Math.PI / 2, -angle + Math.PI / 2, 0, 'YXZ');
                octopusGroup.add(tentacleBase);
                
                let currentParent = tentacleBase;
                const segmentArray = [];

                for (let j = 0; j < segmentsPerTentacle; j++) {
                    const ratioTop = 1 - (j / segmentsPerTentacle);
                    const ratioBottom = 1 - ((j + 1) / segmentsPerTentacle);
                    const radiusTop = 0.85 * ratioTop; 
                    const radiusBottom = 0.85 * ratioBottom;
                    const length = 0.8;

                    const segmentGeo = new THREE.CylinderGeometry(radiusTop, radiusBottom, length, 16);
                    segmentGeo.translate(0, -length / 2, 0);

                    const segment = new THREE.Mesh(segmentGeo, octopusMaterial);
                    segment.castShadow = true;
                    segment.receiveShadow = true;
                    
                    segment.position.y = (j === 0) ? 0 : -length;
                    segment.rotation.set(0, 0, 0);

                    if (j < segmentsPerTentacle - 2) {
                        const suckerGeo = new THREE.SphereGeometry(radiusBottom * 0.4, 8, 8);
                        const suckerMat = new THREE.MeshStandardMaterial({ color: 0xe6ccba, roughness: 0.9 });
                        
                        const sucker1 = new THREE.Mesh(suckerGeo, suckerMat);
                        sucker1.scale.set(1, 0.3, 1); 
                        sucker1.position.set(radiusBottom * 0.4, -length/2, radiusBottom * 0.7);
                        segment.add(sucker1);

                        const sucker2 = new THREE.Mesh(suckerGeo, suckerMat);
                        sucker2.scale.set(1, 0.3, 1);
                        sucker2.position.set(-radiusBottom * 0.4, -length/2, radiusBottom * 0.7);
                        segment.add(sucker2);
                    }

                    currentParent.add(segment);
                    segmentArray.push(segment);
                    currentParent = segment;
                }
                tentacles.push(segmentArray);
            }
        }

        function createRealCrab() {
            crabGroup = new THREE.Group();
            scene.add(crabGroup);

            const shellColor = 0x3d2828; 
            const jointColor = 0xc2a689; 
            const spikeColor = 0xa38c7a; 

            const crabMaterial = new THREE.MeshStandardMaterial({
                color: shellColor,
                roughness: 0.8,
                metalness: 0.1
            });
            const jointMaterial = new THREE.MeshStandardMaterial({
                color: jointColor,
                roughness: 0.6
            });
            const spikeMaterial = new THREE.MeshStandardMaterial({
                color: spikeColor,
                roughness: 0.9
            });

            const shellGeo = new THREE.SphereGeometry(0.55, 32, 16);
            const pos = shellGeo.attributes.position;
            for (let i = 0; i < pos.count; i++) {
                let x = pos.getX(i);
                let y = pos.getY(i);
                let z = pos.getZ(i);
                
                if (z > 0) {
                    x *= (1 - z * 0.8); 
                } else {
                    x *= (1 - z * 0.4); 
                }
                
                if (y > 0) {
                    y += Math.sin(x * 12) * Math.cos(z * 12) * 0.02; 
                    y *= 0.6; 
                } else {
                    y *= 0.2; 
                }
                pos.setXYZ(i, x, y, z);
            }
            shellGeo.computeVertexNormals();
            const shell = new THREE.Mesh(shellGeo, crabMaterial);
            shell.position.y = 0.4;
            shell.castShadow = true;
            shell.receiveShadow = true;
            crabGroup.add(shell);

            const regions = [
                {x: 0, z: -0.1, size: 0.08}, {x: 0.15, z: 0, size: 0.06}, {x: -0.15, z: 0, size: 0.06},
                {x: 0, z: 0.2, size: 0.07}, {x: 0.25, z: -0.15, size: 0.05}, {x: -0.25, z: -0.15, size: 0.05}
            ];
            regions.forEach(r => {
                const s = new THREE.Mesh(new THREE.ConeGeometry(r.size*0.4, r.size, 5), spikeMaterial);
                s.position.set(r.x, 0.6, r.z);
                crabGroup.add(s);
            });
            for(let i=0; i<20; i++) {
                const angle = (i/20) * Math.PI * 2;
                const r = 0.5;
                const x = Math.cos(angle) * r * (Math.sin(angle)>0 ? 0.8 : 1.1);
                const z = Math.sin(angle) * r;
                const s = new THREE.Mesh(new THREE.ConeGeometry(0.015, 0.06, 4), spikeMaterial);
                s.position.set(x, 0.45, z);
                s.rotation.x = Math.PI/2;
                s.rotation.y = -angle + Math.PI/2;
                crabGroup.add(s);
            }

            const eyeStalkGeo = new THREE.CylinderGeometry(0.015, 0.02, 0.1);
            const eyeGeo = new THREE.SphereGeometry(0.04, 16, 16);
            const eyeMat = new THREE.MeshStandardMaterial({ color: 0x050505, roughness: 0.2 });
            
            const rightEyeGroup = new THREE.Group();
            rightEyeGroup.position.set(0.12, 0.5, -0.45);
            rightEyeGroup.rotation.x = -Math.PI/4;
            const rightEyeStalk = new THREE.Mesh(eyeStalkGeo, crabMaterial);
            const rightEye = new THREE.Mesh(eyeGeo, eyeMat);
            rightEye.position.y = 0.05;
            rightEyeGroup.add(rightEyeStalk);
            rightEyeGroup.add(rightEye);
            crabGroup.add(rightEyeGroup);

            const leftEyeGroup = new THREE.Group();
            leftEyeGroup.position.set(-0.12, 0.5, -0.45);
            leftEyeGroup.rotation.x = -Math.PI/4;
            const leftEyeStalk = new THREE.Mesh(eyeStalkGeo, crabMaterial);
            const leftEye = new THREE.Mesh(eyeGeo, eyeMat);
            leftEye.position.y = 0.05;
            leftEyeGroup.add(leftEyeStalk);
            leftEyeGroup.add(leftEye);
            crabGroup.add(leftEyeGroup);

            const legs = [];

            function addSpikeRow(parent, radius, length, count, size, angleOffset) {
                for (let i = 0; i < count; i++) {
                    const spike = new THREE.Mesh(new THREE.ConeGeometry(size*0.3, size, 4), spikeMaterial);
                    const yPos = (i / (count - 1)) * length * 0.8 + length * 0.1;
                    spike.position.set(0, yPos, 0);
                    spike.rotation.y = angleOffset;
                    spike.rotation.x = Math.PI / 2;
                    spike.translateY(radius * 0.9);
                    parent.add(spike);
                }
            }

            function createJoint(size) {
                const joint = new THREE.Mesh(new THREE.SphereGeometry(size, 8, 8), jointMaterial);
                joint.scale.set(1, 0.8, 1);
                return joint;
            }

            function createRealCrabLeg(sideSign, yRot, len, isClaw, clawScale) {
                const root = new THREE.Group();
                root.position.set(sideSign * 0.3, 0.3, yRot * 0.5); 
                root.rotation.y = yRot * sideSign; 

                const coxa = new THREE.Group();
                const baseZRot = sideSign * (isClaw ? -0.2 : -0.3); 
                coxa.rotation.z = baseZRot; 
                root.add(coxa);

                const thick = isClaw ? 0.08 * clawScale : 0.05;
                
                const merusLen = len * 0.45;
                const merus = new THREE.Group();
                const merusGeo = new THREE.CylinderGeometry(thick, thick*0.7, merusLen, 8);
                merusGeo.translate(0, merusLen/2, 0);
                const merusMesh = new THREE.Mesh(merusGeo, crabMaterial);
                merusMesh.scale.z = 0.7; 
                merusMesh.castShadow = true;
                merus.add(merusMesh);
                
                addSpikeRow(merus, thick, merusLen, 6, 0.08, 0);
                addSpikeRow(merus, thick, merusLen, 6, 0.08, Math.PI);
                addSpikeRow(merus, thick, merusLen, 4, 0.06, Math.PI/2);

                coxa.add(merus);

                const carpus = new THREE.Group();
                carpus.position.y = merusLen;
                carpus.rotation.z = sideSign * (isClaw ? -0.6 : -1.2); 
                carpus.add(createJoint(thick*0.9)); 
                merus.add(carpus);

                if (isClaw) {
                    const propodusLen = len * 0.4;
                    const propodusGeo = new THREE.CylinderGeometry(thick*1.1, thick*1.6, propodusLen, 6);
                    propodusGeo.translate(0, propodusLen/2, 0);
                    const propodus = new THREE.Mesh(propodusGeo, crabMaterial);
                    propodus.scale.z = 0.6; 
                    propodus.castShadow = true;
                    
                    addSpikeRow(propodus, thick*1.1, propodusLen, 5, 0.08, Math.PI/2);
                    addSpikeRow(propodus, thick*1.1, propodusLen, 5, 0.08, -Math.PI/2);
                    carpus.add(propodus);

                    const fixedFinger = new THREE.Mesh(new THREE.ConeGeometry(thick*0.5, propodusLen*0.8, 8), crabMaterial);
                    fixedFinger.position.set(thick*0.6 * sideSign, propodusLen, 0);
                    fixedFinger.rotation.z = -0.15 * sideSign;
                    fixedFinger.castShadow = true;
                    carpus.add(fixedFinger);

                    const dactylus = new THREE.Group();
                    dactylus.position.set(-thick*0.5 * sideSign, propodusLen*0.9, 0); 
                    
                    const movableFinger = new THREE.Mesh(new THREE.ConeGeometry(thick*0.4, propodusLen*0.8, 8), jointMaterial);
                    movableFinger.position.y = propodusLen*0.3; 
                    movableFinger.rotation.z = 0.2 * sideSign; 
                    movableFinger.castShadow = true;
                    dactylus.add(movableFinger);
                    carpus.add(dactylus);

                    root.userData = { coxa, carpus, dactylus, baseYRot: yRot * sideSign, baseZRot, side: sideSign };

                } else {
                    const carpusLen = len * 0.15;
                    const carpusMeshGeo = new THREE.CylinderGeometry(thick*0.8, thick*0.7, carpusLen, 8);
                    carpusMeshGeo.translate(0, carpusLen/2, 0);
                    const carpusMesh = new THREE.Mesh(carpusMeshGeo, crabMaterial);
                    carpusMesh.castShadow = true;
                    addSpikeRow(carpusMesh, thick*0.7, carpusLen, 2, 0.06, 0);
                    carpus.add(carpusMesh);

                    const propodusGroup = new THREE.Group();
                    propodusGroup.position.y = carpusLen;
                    propodusGroup.rotation.z = sideSign * 0.2; 
                    propodusGroup.add(createJoint(thick*0.7));
                    carpus.add(propodusGroup);

                    const propodusLen = len * 0.35;
                    const propodusMeshGeo = new THREE.CylinderGeometry(thick*0.7, thick*0.4, propodusLen, 8);
                    propodusMeshGeo.translate(0, propodusLen/2, 0);
                    const propodusMesh = new THREE.Mesh(propodusMeshGeo, crabMaterial);
                    propodusMesh.castShadow = true;
                    addSpikeRow(propodusMesh, thick*0.5, propodusLen, 4, 0.05, 0);
                    propodusGroup.add(propodusMesh);

                    const dactylusGroup = new THREE.Group();
                    dactylusGroup.position.y = propodusLen;
                    dactylusGroup.rotation.z = sideSign * -0.3; 
                    dactylusGroup.add(createJoint(thick*0.4));
                    propodusGroup.add(dactylusGroup);

                    const dactylusLen = len * 0.25;
                    const dactylusGeo = new THREE.ConeGeometry(thick*0.4, dactylusLen, 8);
                    dactylusGeo.translate(0, dactylusLen/2, 0);
                    
                    const dacPos = dactylusGeo.attributes.position;
                    for(let i=0; i<dacPos.count; i++) {
                        let y = dacPos.getY(i);
                        let ratio = y / dactylusLen;
                        dacPos.setX(i, dacPos.getX(i) + Math.pow(ratio, 2) * dactylusLen * 0.4 * sideSign);
                    }
                    dactylusGeo.computeVertexNormals();
                    
                    const dactylusMesh = new THREE.Mesh(dactylusGeo, jointMaterial); 
                    dactylusMesh.castShadow = true;
                    dactylusGroup.add(dactylusMesh);

                    root.userData = { coxa, carpus: propodusGroup, dactylus: null, baseYRot: yRot * sideSign, baseZRot, side: sideSign };
                }

                crabGroup.add(root);
                return root;
            }

            crabGroup.userData.clawR = createRealCrabLeg(1, -0.6, 1.8, true, 1.4);
            crabGroup.userData.clawL = createRealCrabLeg(-1, -0.6, 1.5, true, 0.8);

            legs.push(createRealCrabLeg(1, -0.1, 2.5, false));
            legs.push(createRealCrabLeg(-1, -0.1, 2.5, false));
            legs.push(createRealCrabLeg(1, 0.4, 2.7, false));
            legs.push(createRealCrabLeg(-1, 0.4, 2.7, false));
            legs.push(createRealCrabLeg(1, 0.9, 2.2, false));
            legs.push(createRealCrabLeg(-1, 0.9, 2.2, false));

            crabGroup.userData.legs = legs;
            crabGroup.scale.set(1.5, 1.5, 1.5);
        }

        // ====== リアルに修正したオニカマス ======
        function createBarracuda() {
            barracudaGroup = new THREE.Group();
            scene.add(barracudaGroup);

            const silverMat = new THREE.MeshStandardMaterial({ color: 0xd0d8e0, metalness: 0.9, roughness: 0.4 });
            const darkMat = new THREE.MeshStandardMaterial({ color: 0x4a5a6a, metalness: 0.7, roughness: 0.5 });
            const eyeMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.1 });
            const finMat = new THREE.MeshStandardMaterial({ color: 0x778899, transparent: true, opacity: 0.8, side: THREE.DoubleSide });
            const stripeMat = new THREE.MeshStandardMaterial({ color: 0x223344 });

            // 魚全体を組み立てるコンテナ
            // -90度倒すことで、+Y(頭)を-Z(進行方向)に向け、+Z(背中)を+Y(上)に向ける
            const fishRig = new THREE.Group();
            fishRig.rotation.x = -Math.PI / 2;
            barracudaGroup.add(fishRig);

            const fishBody = new THREE.Group();
            fishRig.add(fishBody);

            // --- 胴体 ---
            const torsoGeo = new THREE.CylinderGeometry(0.4, 0.25, 5.0, 16);
            const torso = new THREE.Mesh(torsoGeo, silverMat);
            torso.castShadow = true;
            fishBody.add(torso);

            // 背中(暗い色)
            const backGeo = new THREE.CylinderGeometry(0.41, 0.26, 5.0, 16, 1, false, -Math.PI/2, Math.PI);
            const back = new THREE.Mesh(backGeo, darkMat);
            fishBody.add(back);

            // 側面の黒い縞模様
            const stripeGeo = new THREE.BoxGeometry(0.85, 0.15, 0.3);
            for(let i=0; i<6; i++) {
                const stripe = new THREE.Mesh(stripeGeo, stripeMat);
                stripe.position.set(0, 1.5 - i*0.8, 0); 
                stripe.rotation.x = Math.PI/8; 
                fishBody.add(stripe);
            }

            // --- 頭部 ---
            const headGeo = new THREE.ConeGeometry(0.4, 2.0, 16);
            const head = new THREE.Mesh(headGeo, silverMat);
            head.position.y = 3.5; 
            head.castShadow = true;
            fishBody.add(head);

            // 頭部の背中
            const headBackGeo = new THREE.ConeGeometry(0.41, 2.0, 16, 1, false, -Math.PI/2, Math.PI);
            const headBack = new THREE.Mesh(headBackGeo, darkMat);
            headBack.position.y = 3.5;
            fishBody.add(headBack);

            // 上顎(追加して、頭らしさを強調)
            const upperJawGeo = new THREE.ConeGeometry(0.2, 1.8, 16);
            const upperJaw = new THREE.Mesh(upperJawGeo, silverMat);
            upperJaw.position.set(0, 4.0, 0.05); 
            upperJaw.rotation.x = -0.05;
            fishBody.add(upperJaw);
            const upperJawBack = new THREE.Mesh(new THREE.ConeGeometry(0.21, 1.8, 16, 1, false, -Math.PI/2, Math.PI), darkMat);
            upperJawBack.position.set(0, 4.0, 0.05);
            upperJawBack.rotation.x = -0.05;
            fishBody.add(upperJawBack);

            // 下顎(鋭く突き出ている)
            const jawGeo = new THREE.ConeGeometry(0.2, 2.2, 16);
            const jaw = new THREE.Mesh(jawGeo, silverMat);
            jaw.position.set(0, 4.2, -0.15); 
            jaw.rotation.x = 0.05; 
            jaw.castShadow = true;
            fishBody.add(jaw);

            // 目
            const eyeGeo = new THREE.SphereGeometry(0.08, 16, 16);
            const eyeR = new THREE.Mesh(eyeGeo, eyeMat);
            eyeR.position.set(0.3, 3.2, 0.15); 
            fishBody.add(eyeR);
            const eyeL = new THREE.Mesh(eyeGeo, eyeMat);
            eyeL.position.set(-0.3, 3.2, 0.15);
            fishBody.add(eyeL);

            // 歯
            const toothGeo = new THREE.ConeGeometry(0.02, 0.2, 4);
            for(let i=0; i<7; i++) {
                const yPos = 3.6 + i * 0.2; 
                
                // 下顎の歯(上向き)
                const tR = new THREE.Mesh(toothGeo, silverMat);
                tR.position.set(0.12, yPos, -0.1);
                tR.rotation.x = Math.PI * 0.6; 
                fishBody.add(tR);
                const tL = new THREE.Mesh(toothGeo, silverMat);
                tL.position.set(-0.12, yPos, -0.1);
                tL.rotation.x = Math.PI * 0.6;
                fishBody.add(tL);

                // 上顎の歯(下向き)
                if (i < 5) {
                    const tuR = new THREE.Mesh(toothGeo, silverMat);
                    tuR.position.set(0.12, yPos, 0.05);
                    tuR.rotation.x = -Math.PI * 0.6; 
                    fishBody.add(tuR);
                    const tuL = new THREE.Mesh(toothGeo, silverMat);
                    tuL.position.set(-0.12, yPos, 0.05);
                    tuL.rotation.x = -Math.PI * 0.6;
                    fishBody.add(tuL);
                }
            }

            // --- 尾部(尻尾を振るために別グループ化) ---
            const tailGroup = new THREE.Group();
            tailGroup.position.y = -2.5; 
            fishRig.add(tailGroup);

            const tailBodyGeo = new THREE.CylinderGeometry(0.25, 0.08, 2.0, 16);
            const tailBody = new THREE.Mesh(tailBodyGeo, silverMat);
            tailBody.position.y = -1.0; 
            tailBody.castShadow = true;
            tailGroup.add(tailBody);

            const tailBackGeo = new THREE.CylinderGeometry(0.26, 0.09, 2.0, 16, 1, false, -Math.PI/2, Math.PI);
            const tailBack = new THREE.Mesh(tailBackGeo, darkMat);
            tailBack.position.y = -1.0;
            tailGroup.add(tailBack);

            // --- ヒレ(★修正: 横幅ではなく縦方向(X軸)を細くして自然なヒレにする) ---
            const dFinGeo = new THREE.ConeGeometry(0.4, 1.5, 3);
            
            const dFin1 = new THREE.Mesh(dFinGeo, finMat);
            dFin1.position.set(0, 0.5, 0.4); 
            dFin1.rotation.x = Math.PI * 0.6; 
            dFin1.scale.x = 0.05; // 縦方向に薄くする
            fishBody.add(dFin1);

            const dFin2 = new THREE.Mesh(dFinGeo, finMat);
            dFin2.position.set(0, -1.0, 0.25); 
            dFin2.rotation.x = Math.PI * 0.6;
            dFin2.scale.x = 0.05; 
            tailGroup.add(dFin2);

            const aFin = new THREE.Mesh(dFinGeo, finMat);
            aFin.position.set(0, -1.0, -0.25); 
            aFin.rotation.x = -Math.PI * 0.6; 
            aFin.scale.x = 0.05; 
            tailGroup.add(aFin);

            // 胸ビレ
            const pFinGeo = new THREE.ConeGeometry(0.3, 1.2, 3);
            const pFinR = new THREE.Mesh(pFinGeo, finMat);
            pFinR.position.set(0.4, 1.5, -0.2); 
            pFinR.rotation.set(-Math.PI * 0.8, 0, -Math.PI/6); 
            pFinR.scale.z = 0.05; // 胸ビレは横向きでOK
            fishBody.add(pFinR);

            const pFinL = new THREE.Mesh(pFinGeo, finMat);
            pFinL.position.set(-0.4, 1.5, -0.2);
            pFinL.rotation.set(-Math.PI * 0.8, 0, Math.PI/6);
            pFinL.scale.z = 0.05;
            fishBody.add(pFinL);

            // 尾ビレ
            const tailFinGroup = new THREE.Group();
            tailFinGroup.position.y = -2.0; 
            tailGroup.add(tailFinGroup);

            const tFinGeo = new THREE.ConeGeometry(0.8, 2.5, 3); 
            
            const tFinTop = new THREE.Mesh(tFinGeo, finMat);
            tFinTop.position.set(0, -0.8, 0.5);
            tFinTop.rotation.x = Math.PI * 0.6; 
            tFinTop.scale.x = 0.05; // 縦方向に薄く
            tailFinGroup.add(tFinTop);

            const tFinBot = new THREE.Mesh(tFinGeo, finMat);
            tFinBot.position.set(0, -0.8, -0.5);
            tFinBot.rotation.x = -Math.PI * 0.6; 
            tFinBot.scale.x = 0.05; 
            tailFinGroup.add(tFinBot);

            barracudaGroup.scale.set(2.2, 2.2, 2.2);

            barracudaGroup.userData.tail = tailGroup;
            barracudaGroup.userData.fishBody = fishBody;
            barracudaGroup.userData.fishRig = fishRig; 
        }

        function createBubbles() {
            const bubbleGeo = new THREE.SphereGeometry(0.1, 8, 8);
            const bubbleMat = new THREE.MeshStandardMaterial({
                color: 0xffffff,
                transparent: true,
                opacity: 0.3,
                roughness: 0,
                transmission: 0.9 
            });

            for (let i = 0; i < 50; i++) {
                const bubble = new THREE.Mesh(bubbleGeo, bubbleMat);
                bubble.position.set(
                    (Math.random() - 0.5) * 40,
                    (Math.random() - 0.5) * 40 - 10,
                    (Math.random() - 0.5) * 40
                );
                const scale = Math.random() * 1.5 + 0.5;
                bubble.scale.set(scale, scale, scale);
                bubble.userData.speed = Math.random() * 0.05 + 0.02;
                scene.add(bubble);
                bubbles.push(bubble);
            }
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {
            requestAnimationFrame(animate);
            const elapsedTime = clock.getElapsedTime();

            // --- タコのアニメーション ---
            if (octopusGroup) {
                const head = octopusGroup.children[0];
                head.scale.y = 0.7 + Math.sin(elapsedTime * 1.5) * 0.05;
                head.scale.x = 1.2 + Math.sin(elapsedTime * 1.5 + Math.PI) * 0.05;
                
                const body = octopusGroup.children[1];
                body.scale.y = 0.6 + Math.sin(elapsedTime * 1.5 + 0.5) * 0.02;
            }

            tentacles.forEach((segmentArray, tIndex) => {
                segmentArray.forEach((segment, sIndex) => {
                    const waveOffset = tIndex * 0.2 - sIndex * 0.4;
                    const tipFactor = sIndex / segmentArray.length; 
                    const amplitude = 0.05 + tipFactor * 0.2;
                    segment.rotation.z = Math.sin(elapsedTime * 2.0 + waveOffset) * amplitude;
                    segment.rotation.x = 0.02 + (tipFactor * 0.1);
                });
            });

            if (octopusGroup) {
                octopusGroup.updateMatrixWorld(true);
            }

            if (octopusGroup && octopusGroup.userData.webMesh) {
                const web = octopusGroup.userData.webMesh;
                const pos = web.geometry.attributes.position;
                const webSegs = octopusGroup.userData.webSegments;
                const rDiv = octopusGroup.userData.radialDiv;
                const xSegments = 8 * rDiv; 
                
                const invMat = new THREE.Matrix4().copy(octopusGroup.matrixWorld).invert();
                
                for (let y = 0; y < webSegs; y++) {
                    for (let i = 0; i < 8; i++) {
                        const segA = tentacles[i][y];
                        const segB = tentacles[(i+1)%8][y];
                        
                        const posA = new THREE.Vector3();
                        segA.getWorldPosition(posA);
                        posA.applyMatrix4(invMat);
                        posA.y -= 0.15; 
                        
                        const posB = new THREE.Vector3();
                        segB.getWorldPosition(posB);
                        posB.applyMatrix4(invMat);
                        posB.y -= 0.15;
                        
                        for (let j = 0; j < rDiv; j++) {
                            const x = i * rDiv + j;
                            const t = j / rDiv; 
                            const idx = y * (xSegments + 1) + x;
                            
                            const vec = new THREE.Vector3().lerpVectors(posA, posB, t);
                            
                            if (j > 0) {
                                const valley = Math.sin(t * Math.PI); 
                                const factor = y / (webSegs - 1); 
                                vec.y -= valley * factor * 0.6; 
                                vec.x *= (1 - valley * factor * 0.3);
                                vec.z *= (1 - valley * factor * 0.3);
                            }
                            
                            if (y === 0) {
                                vec.y = 0.4;  
                                vec.x *= 0.25; 
                                vec.z *= 0.25;
                            }
                            pos.setXYZ(idx, vec.x, vec.y, vec.z);
                        }
                    }
                    const pos0 = new THREE.Vector3();
                    tentacles[0][y].getWorldPosition(pos0);
                    pos0.applyMatrix4(invMat);
                    pos0.y -= 0.15;
                    if (y === 0) {
                        pos0.y = 0.4;
                        pos0.x *= 0.25;
                        pos0.z *= 0.25;
                    }
                    const idxEnd = y * (xSegments + 1) + xSegments;
                    pos.setXYZ(idxEnd, pos0.x, pos0.y, pos0.z);
                }
                pos.needsUpdate = true;
                web.geometry.computeVertexNormals(); 
            }

            // --- カニ(タラバガニ)のアニメーション ---
            if (crabGroup) {
                const speed = elapsedTime * 3.0;
                
                const clawR = crabGroup.userData.clawR.userData;
                const clawL = crabGroup.userData.clawL.userData;

                clawR.coxa.rotation.z = clawR.baseZRot + Math.sin(speed * 0.5) * 0.1;
                clawL.coxa.rotation.z = clawL.baseZRot + Math.sin(speed * 0.5 + Math.PI) * 0.1;
                
                clawR.dactylus.rotation.z = Math.abs(Math.sin(speed)) * 0.4;
                clawL.dactylus.rotation.z = -Math.abs(Math.sin(speed + 1.0)) * 0.4;

                crabGroup.userData.legs.forEach((legObj, i) => {
                    const leg = legObj.userData;
                    const offset = (i % 2 === 0) ? 0 : Math.PI;
                    leg.coxa.rotation.z = leg.baseZRot + Math.max(0, Math.sin(speed + offset)) * 0.2 * leg.side;
                    legObj.rotation.y = leg.baseYRot + Math.cos(speed + offset) * 0.15;
                });

                const crabSpeed = elapsedTime * 0.12; 
                const crabRadius = 11.0; 
                crabGroup.position.x = Math.cos(crabSpeed) * crabRadius;
                crabGroup.position.z = Math.sin(crabSpeed) * crabRadius;
                
                crabGroup.lookAt(0, 0, 0); 
                crabGroup.position.y = Math.sin(crabGroup.position.x * 0.5) * 0.3 + 0.3;
            }

            // --- オニカマスのアニメーション ---
            if (barracudaGroup) {
                const bSpeed = elapsedTime * 0.4;
                const bRadius = 16.0; 
                
                // 軌道上を前進する
                const bx = Math.cos(bSpeed) * bRadius;
                const bz = Math.sin(bSpeed) * bRadius;
                const by = 12 + Math.sin(elapsedTime * 0.5) * 2.0; 
                
                barracudaGroup.position.set(bx, by, bz);
                
                // 少し先の未来の座標を見て、顔を進行方向に向ける
                const nextX = Math.cos(bSpeed + 0.1) * bRadius;
                const nextZ = Math.sin(bSpeed + 0.1) * bRadius;
                const nextY = 12 + Math.sin((elapsedTime + 0.1) * 0.5) * 2.0;
                barracudaGroup.lookAt(nextX, nextY, nextZ);

                // 体から尾ビレにかけて力が伝わる(波打つ)自然なアニメーション
                barracudaGroup.userData.fishBody.rotation.z = Math.sin(elapsedTime * 8) * 0.05;
                barracudaGroup.userData.tail.rotation.z = Math.sin(elapsedTime * 8 - 1.0) * 0.2;
                
                barracudaGroup.userData.fishRig.rotation.z = Math.sin(elapsedTime * 4) * 0.05;
            }

            // --- 泡のアニメーション ---
            bubbles.forEach(bubble => {
                bubble.position.y += bubble.userData.speed;
                bubble.position.x += Math.sin(elapsedTime * 2 + bubble.position.y) * 0.01;
                
                if (bubble.position.y > 20) {
                    bubble.position.y = -20;
                    bubble.position.x = (Math.random() - 0.5) * 40;
                }
            });

            controls.update(); 
            renderer.render(scene, camera);
        }
    </script>
</body>
</html>

ミズダコ3D

眺める

🤖 使用したAI
Gemini
👁 2 回閲覧
📅 投稿:2026年4月12日 🔄 更新:2026年4月13日
応援スタンプを押してみよう!