🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
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 Viewer - ガメラ2</title>
    <!-- Tailwind CSS CDN -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- 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>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #030712;
            font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            user-select: none;
        }
        canvas {
            display: block;
            width: 100vw;
            height: 100vh;
        }
        .glass-panel {
            background: rgba(15, 23, 42, 0.82);
            backdrop-filter: blur(12px);
            border: 1px solid rgba(255, 255, 255, 0.12);
        }
        .glow-button {
            transition: all 0.25s ease;
            box-shadow: 0 0 10px rgba(217, 119, 6, 0.2);
        }
        .glow-button:hover {
            box-shadow: 0 0 20px rgba(217, 119, 6, 0.6);
            transform: translateY(-2px);
        }
    </style>
</head>
<body class="text-slate-100">

    <!-- UI Header Overlay -->
    <div class="absolute top-4 left-4 z-10 glass-panel rounded-xl p-4 max-w-xs md:max-w-sm pointer-events-auto">
        <div class="flex items-center space-x-3 mb-2">
            <div class="w-3 h-3 rounded-full bg-amber-500 animate-pulse"></div>
            <h1 class="text-lg font-bold tracking-wider text-amber-400">レギオン 3Dビューアー</h1>
        </div>
        <p class="text-xs text-slate-300 leading-relaxed mb-3">
            『ガメラ2 レギオン降臨』(1996)<br>
            黄金の巨大母体「マザーレギオン」と、前進し蠢く群生甲虫「ソルジャーレギオン」。
        </p>
        <div class="flex flex-wrap gap-2 text-xs">
            <span class="bg-amber-950/80 text-amber-300 border border-amber-700/50 px-2 py-0.5 rounded">マザー: 140m</span>
            <span class="bg-purple-950/80 text-purple-300 border border-purple-700/50 px-2 py-0.5 rounded">ソルジャー: 3m (1300体・常時前進)</span>
            <span class="bg-slate-800 text-slate-300 px-2 py-0.5 rounded">珪素生命体</span>
        </div>
    </div>

    <!-- Control Panel -->
    <div class="absolute bottom-6 left-1/2 -translate-x-1/2 z-10 glass-panel rounded-2xl p-3 flex items-center space-x-3 pointer-events-auto shadow-2xl">
        <button id="btn-attack" class="glow-button bg-gradient-to-r from-amber-600 to-red-600 hover:from-amber-500 hover:to-red-500 text-white font-semibold text-xs sm:text-sm px-4 py-2.5 rounded-xl flex items-center space-x-2">
            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
            <span>マイクロ波照射</span>
        </button>
        <button id="btn-toggle-swarm" class="bg-slate-800 hover:bg-slate-700 text-slate-200 font-medium text-xs sm:text-sm px-3.5 py-2.5 rounded-xl border border-slate-700 transition">
            ソルジャー群: <span id="swarm-status" class="text-purple-400 font-bold">ON (1300体前進中)</span>
        </button>
        <button id="btn-reset-cam" class="bg-slate-800 hover:bg-slate-700 text-slate-200 font-medium text-xs sm:text-sm px-3.5 py-2.5 rounded-xl border border-slate-700 transition">
            視点リセット
        </button>
    </div>

    <!-- Instructions Overlay -->
    <div class="absolute bottom-6 right-6 z-10 hidden md:block text-right pointer-events-none">
        <p class="text-xs text-slate-400">ドラッグ: 視点回転 | ホイール: ズーム | 右クリック: 平行移動</p>
    </div>

    <!-- 3D Scene Container Script -->
    <script>
        let scene, camera, renderer, controls;
        let motherGroup, soldierGroup, attackBeam, particleSystem;
        let motherHorns = { left: null, right: null };
        let motherEggSac, hornCoreLight;
        let isAttacking = false;
        let attackProgress = 0;
        let showSwarm = true;
        let soldierUnits = [];
        let instancedSoldierMeshes = [];
        let soldierComponents = [];
        const clock = new THREE.Clock();

        // Color & Material Definitions
        const materials = {
            carapace: new THREE.MeshStandardMaterial({
                color: 0xcfb06b,
                roughness: 0.35,
                metalness: 0.35,
                flatShading: true
            }),
            carapaceAccent: new THREE.MeshStandardMaterial({
                color: 0x9e7232,
                roughness: 0.4,
                metalness: 0.4
            }),
            carapaceDark: new THREE.MeshStandardMaterial({
                color: 0x141210,
                roughness: 0.8,
                metalness: 0.15
            }),
            hornCore: new THREE.MeshStandardMaterial({
                color: 0xffaa00,
                emissive: 0xff5500,
                emissiveIntensity: 1.2,
                roughness: 0.2
            }),
            eggSac: new THREE.MeshStandardMaterial({
                color: 0xc41414,
                emissive: 0x880505,
                emissiveIntensity: 1.1,
                transparent: true,
                opacity: 0.95,
                roughness: 0.25
            }),
            eggCore: new THREE.MeshStandardMaterial({
                color: 0xff3300,
                emissive: 0xff0000,
                emissiveIntensity: 2.2
            }),
            eyeBlue: new THREE.MeshStandardMaterial({
                color: 0x00f0ff,
                emissive: 0x00aaff,
                emissiveIntensity: 2.5,
                roughness: 0.1
            }),
            // --- Soldier Legion Glossy Deep Violet Material ---
            soldierCarapace: new THREE.MeshStandardMaterial({
                color: 0x241733,
                roughness: 0.28,
                metalness: 0.45,
                flatShading: true
            }),
            soldierCarapaceDark: new THREE.MeshStandardMaterial({
                color: 0x140a1f,
                roughness: 0.5,
                metalness: 0.2
            }),
            soldierJaw: new THREE.MeshStandardMaterial({
                color: 0x36234a,
                roughness: 0.2,
                metalness: 0.6
            }),
            beam: new THREE.MeshBasicMaterial({
                color: 0xffcc33,
                transparent: true,
                opacity: 0.0,
                side: THREE.DoubleSide
            })
        };

        window.onload = function() {
            initScene();
            buildEnvironment();
            buildMotherLegion();
            buildSoldierLegionSwarm();
            buildParticleSystem();
            setupUIEvents();
            animate();
        };

        function initScene() {
            scene = new THREE.Scene();
            scene.fog = new THREE.FogExp2(0x030712, 0.012);

            camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 14, 34);

            renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 1.25;
            document.body.appendChild(renderer.domElement);

            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.maxPolarAngle = Math.PI / 2 + 0.05;
            controls.minDistance = 5;
            controls.maxDistance = 100;
            controls.target.set(0, 5, 0);

            // Lighting Setup
            const ambientLight = new THREE.AmbientLight(0x282338, 1.5);
            scene.add(ambientLight);

            const mainLight = new THREE.DirectionalLight(0xfffaed, 2.5);
            mainLight.position.set(25, 45, 20);
            mainLight.castShadow = true;
            mainLight.shadow.mapSize.width = 2048;
            mainLight.shadow.mapSize.height = 2048;
            scene.add(mainLight);

            const violetRim = new THREE.DirectionalLight(0x8b5cf6, 2.2);
            violetRim.position.set(-25, 25, -20);
            scene.add(violetRim);

            const warmFillLight = new THREE.DirectionalLight(0xd97706, 1.2);
            warmFillLight.position.set(0, -10, 20);
            scene.add(warmFillLight);

            hornCoreLight = new THREE.PointLight(0xff7700, 2, 25);
            scene.add(hornCoreLight);
        }

        function buildEnvironment() {
            const groundGeo = new THREE.PlaneGeometry(140, 140);
            const groundMat = new THREE.MeshStandardMaterial({
                color: 0x080b14,
                roughness: 0.9,
                metalness: 0.2
            });
            const ground = new THREE.Mesh(groundGeo, groundMat);
            ground.rotation.x = -Math.PI / 2;
            ground.position.y = -0.1;
            ground.receiveShadow = true;
            scene.add(ground);

            const gridHelper = new THREE.GridHelper(140, 70, 0x8b5cf6, 0x1e293b);
            gridHelper.position.y = 0;
            scene.add(gridHelper);
        }

        function buildMotherLegion() {
            motherGroup = new THREE.Group();
            motherGroup.position.set(-2, 0, 0);

            const bodyContainer = new THREE.Group();
            bodyContainer.rotation.x = -0.22;
            motherGroup.add(bodyContainer);

            // Spine / Neck
            for (let i = 0; i < 7; i++) {
                const seg = new THREE.Mesh(
                    new THREE.CylinderGeometry(1.8 - i * 0.1, 2.2 - i * 0.1, 1.2, 12),
                    materials.carapaceDark
                );
                seg.position.set(0, 3.2 + i * 0.8, -1.2 - i * 0.7);
                seg.rotation.x = 0.45;
                seg.castShadow = true;
                bodyContainer.add(seg);
            }

            // Thorax Shells
            const thoraxGroup = new THREE.Group();
            for (let i = 0; i < 6; i++) {
                const shell = new THREE.Mesh(
                    new THREE.ConeGeometry(2.6 - i * 0.25, 3.2, 6),
                    materials.carapace
                );
                shell.rotation.x = Math.PI / 2.8 + (i * 0.05);
                shell.position.set(0, 7.8 + i * 0.5, 0.2 - i * 1.2);
                shell.scale.set(1.3, 1.1, 0.85);
                shell.castShadow = true;
                thoraxGroup.add(shell);
            }

            // Thoracic Rib Spikes
            for (let side of [-1, 1]) {
                for (let i = 0; i < 5; i++) {
                    const spikeGroup = new THREE.Group();
                    spikeGroup.position.set(side * (1.6 + i * 0.2), 6.2 + i * 0.5, 1.2 - i * 1.1);

                    const spikeBase = new THREE.Mesh(
                        new THREE.ConeGeometry(0.4, 3.2 - i * 0.3, 5),
                        materials.carapace
                    );
                    spikeBase.rotation.z = side * -1.25;
                    spikeBase.rotation.x = 0.25;
                    spikeBase.rotation.y = side * 0.4;
                    spikeBase.castShadow = true;
                    spikeGroup.add(spikeBase);

                    const spikeTip = new THREE.Mesh(
                        new THREE.ConeGeometry(0.2, 1.8, 4),
                        materials.carapaceAccent
                    );
                    spikeTip.position.set(side * 1.6, 0.4, 0.4);
                    spikeTip.rotation.z = side * -0.9;
                    spikeTip.rotation.x = 0.5;
                    spikeGroup.add(spikeTip);

                    thoraxGroup.add(spikeGroup);
                }
            }

            // Chest Red Egg Cluster
            const chestEggGroup = new THREE.Group();
            chestEggGroup.position.set(0, 6.2, 1.6);

            const chestFrame = new THREE.Mesh(
                new THREE.TorusGeometry(1.4, 0.35, 8, 16),
                materials.carapaceAccent
            );
            chestFrame.rotation.x = 0.3;
            chestFrame.scale.set(1.0, 1.4, 0.8);
            chestEggGroup.add(chestFrame);

            const chestBase = new THREE.Mesh(
                new THREE.SphereGeometry(1.4, 16, 16),
                materials.eggSac
            );
            chestBase.scale.set(0.9, 1.3, 0.7);
            chestEggGroup.add(chestBase);

            for (let i = 0; i < 40; i++) {
                const egg = new THREE.Mesh(
                    new THREE.SphereGeometry(0.22, 8, 8),
                    materials.eggCore
                );
                const phi = Math.random() * Math.PI;
                const theta = (Math.random() - 0.5) * Math.PI * 1.2;
                const radius = 1.1 + Math.random() * 0.25;
                egg.position.set(
                    radius * Math.sin(phi) * Math.sin(theta) * 0.8,
                    radius * Math.cos(phi) * 1.2,
                    radius * Math.sin(phi) * Math.cos(theta) * 0.5 + 0.3
                );
                chestEggGroup.add(egg);
            }
            thoraxGroup.add(chestEggGroup);
            bodyContainer.add(thoraxGroup);

            // Abdomen / Lower Egg Sac
            const eggGroup = new THREE.Group();
            motherEggSac = new THREE.Mesh(
                new THREE.SphereGeometry(3.4, 20, 20),
                materials.eggSac
            );
            motherEggSac.scale.set(0.95, 1.15, 2.2);
            motherEggSac.position.set(0, 3.5, -7.5);
            motherEggSac.castShadow = true;
            eggGroup.add(motherEggSac);

            for (let i = 0; i < 20; i++) {
                const egg = new THREE.Mesh(
                    new THREE.SphereGeometry(0.42, 10, 10),
                    materials.eggCore
                );
                const angle = Math.random() * Math.PI * 2;
                const dist = Math.random() * 1.8;
                const z = (Math.random() - 0.5) * 5.0;
                egg.position.set(Math.cos(angle) * dist, Math.sin(angle) * dist, z);
                motherEggSac.add(egg);
            }
            bodyContainer.add(eggGroup);

            // Head, Mouth Claws & Crest Horn
            const headGroup = new THREE.Group();
            headGroup.position.set(0, 9.2, 2.8);

            for (let side of [-1, 1]) {
                const eye = new THREE.Mesh(
                    new THREE.SphereGeometry(0.35, 12, 12),
                    materials.eyeBlue
                );
                eye.position.set(side * 1.0, 0.4, 1.4);
                eye.scale.set(1.2, 0.8, 1.5);
                headGroup.add(eye);
            }

            for (let side of [-1, 1]) {
                for (let j = 0; j < 5; j++) {
                    const clawArm = new THREE.Group();
                    clawArm.position.set(side * (0.3 + j * 0.32), -0.2 - j * 0.35, 1.6 + j * 0.45);

                    const seg1 = new THREE.Mesh(
                        new THREE.CylinderGeometry(0.18 - j * 0.02, 0.28 - j * 0.02, 1.6, 6),
                        materials.carapaceAccent
                    );
                    seg1.rotation.x = Math.PI / 2.3;
                    seg1.rotation.z = side * (-0.3 - j * 0.15);
                    clawArm.add(seg1);

                    const tipClaw = new THREE.Mesh(
                        new THREE.ConeGeometry(0.16 - j * 0.02, 1.6, 5),
                        materials.carapace
                    );
                    tipClaw.position.set(side * 0.2, 0.1, 1.2);
                    tipClaw.rotation.x = Math.PI / 1.8;
                    tipClaw.rotation.z = side * (-0.2);
                    clawArm.add(tipClaw);

                    headGroup.add(clawArm);
                }
            }

            const hornCoreGeo = new THREE.ConeGeometry(0.7, 5.5, 8);
            const hornCoreMesh = new THREE.Mesh(hornCoreGeo, materials.hornCore);
            hornCoreMesh.rotation.x = Math.PI / 2.2;
            hornCoreMesh.position.set(0, 0.8, 2.2);
            headGroup.add(hornCoreMesh);

            motherHorns.left = new THREE.Group();
            motherHorns.left.position.set(-0.3, 0, 0);

            const leftMainShell = new THREE.Mesh(
                new THREE.CylinderGeometry(0.25, 0.8, 8.5, 7),
                materials.carapace
            );
            leftMainShell.rotation.x = Math.PI / 3.0;
            leftMainShell.rotation.z = -0.18;
            leftMainShell.position.set(-0.6, 2.8, 2.8);
            leftMainShell.castShadow = true;
            motherHorns.left.add(leftMainShell);

            const leftUpperTip = new THREE.Mesh(
                new THREE.ConeGeometry(0.32, 4.8, 6),
                materials.carapaceAccent
            );
            leftUpperTip.rotation.x = Math.PI / 1.75;
            leftUpperTip.rotation.z = -0.1;
            leftUpperTip.position.set(-0.75, 5.8, 6.0);
            leftUpperTip.castShadow = true;
            motherHorns.left.add(leftUpperTip);

            motherHorns.right = new THREE.Group();
            motherHorns.right.position.set(0.3, 0, 0);

            const rightMainShell = leftMainShell.clone();
            rightMainShell.rotation.z = 0.18;
            rightMainShell.position.x = 0.6;
            motherHorns.right.add(rightMainShell);

            const rightUpperTip = leftUpperTip.clone();
            rightUpperTip.rotation.z = 0.1;
            rightUpperTip.position.x = 0.75;
            motherHorns.right.add(rightUpperTip);

            headGroup.add(motherHorns.left);
            headGroup.add(motherHorns.right);
            bodyContainer.add(headGroup);

            // Front Scythe Arms
            for (let side of [-1, 1]) {
                const scytheArm = new THREE.Group();
                scytheArm.position.set(side * 3.0, 7.8, 2.2);

                const joint = new THREE.Mesh(new THREE.SphereGeometry(0.95, 12, 12), materials.carapaceAccent);
                scytheArm.add(joint);

                const upperArm = new THREE.Mesh(
                    new THREE.BoxGeometry(0.8, 4.5, 1.2),
                    materials.carapace
                );
                upperArm.position.set(side * 1.2, -1.8, 0.8);
                upperArm.rotation.z = side * -0.5;
                scytheArm.add(upperArm);

                const mainBlade = new THREE.Mesh(
                    new THREE.ConeGeometry(0.7, 9.5, 6),
                    materials.carapace
                );
                mainBlade.position.set(side * 2.2, -5.2, 2.8);
                mainBlade.rotation.z = side * -0.3;
                mainBlade.rotation.x = 0.75;
                mainBlade.castShadow = true;
                scytheArm.add(mainBlade);

                bodyContainer.add(scytheArm);
            }

            // Vertical Blade Legs
            for (let i = 0; i < 3; i++) {
                for (let side of [-1, 1]) {
                    const legGroup = new THREE.Group();
                    legGroup.position.set(side * 2.5, 4.2, -i * 3.2 + 0.4);

                    const upperPlate = new THREE.Mesh(
                        new THREE.BoxGeometry(0.7, 10.0, 2.2),
                        materials.carapace
                    );
                    upperPlate.position.set(side * 2.8, 3.8, 0);
                    upperPlate.rotation.z = side * -0.32;
                    upperPlate.rotation.x = -0.15 + i * 0.12;
                    upperPlate.castShadow = true;
                    legGroup.add(upperPlate);

                    const topFin = new THREE.Mesh(
                        new THREE.ConeGeometry(1.5, 6.5, 4),
                        materials.carapaceAccent
                    );
                    topFin.position.set(side * 3.8, 7.8, -0.6);
                    topFin.rotation.z = side * -0.45;
                    topFin.rotation.x = -0.2;
                    topFin.scale.set(0.4, 1.0, 1.2);
                    topFin.castShadow = true;
                    legGroup.add(topFin);

                    const lowerLeg = new THREE.Mesh(
                        new THREE.ConeGeometry(0.5, 8.0, 5),
                        materials.carapaceAccent
                    );
                    lowerLeg.position.set(side * 4.4, -2.8, 0.5);
                    lowerLeg.rotation.z = side * 0.22;
                    lowerLeg.rotation.x = 0.2;
                    lowerLeg.castShadow = true;
                    legGroup.add(lowerLeg);

                    bodyContainer.add(legGroup);
                }
            }

            // Attack Beam Cone
            const beamGeo = new THREE.ConeGeometry(7.0, 42, 16, 1, true);
            attackBeam = new THREE.Mesh(beamGeo, materials.beam);
            attackBeam.rotation.x = -Math.PI / 2;
            attackBeam.position.set(0, 9.8, 23);
            bodyContainer.add(attackBeam);

            scene.add(motherGroup);
        }

        function createSingleSoldierModel() {
            const soldier = new THREE.Group();

            // 1. Thorax / Spine
            const headContainer = new THREE.Group();
            headContainer.position.set(0, 0.75, 0.8);
            headContainer.rotation.x = 0.35;

            // Main Head-Thorax Shell Body
            const headShellGeo = new THREE.ConeGeometry(0.55, 2.2, 7);
            const headShell = new THREE.Mesh(headShellGeo, materials.soldierCarapace);
            headShell.rotation.x = Math.PI / 2.3;
            headShell.castShadow = true;
            headContainer.add(headShell);

            // Segmented Ridges along spine
            for (let i = 0; i < 4; i++) {
                const ridge = new THREE.Mesh(
                    new THREE.TorusGeometry(0.42 - i * 0.08, 0.1, 6, 10),
                    materials.soldierCarapaceDark
                );
                ridge.rotation.x = Math.PI / 2.1;
                ridge.position.set(0, 0.1, -i * 0.35 + 0.2);
                headContainer.add(ridge);
            }

            // 2. Large Curved Top Crest Horn
            const crestGroup = new THREE.Group();
            crestGroup.position.set(0, 0.35, -0.1);

            const crestBase = new THREE.Mesh(
                new THREE.ConeGeometry(0.28, 1.8, 5),
                materials.soldierCarapace
            );
            crestBase.rotation.x = -Math.PI / 3.2;
            crestBase.position.set(0, 0.6, -0.3);
            crestBase.scale.set(0.45, 1.2, 1.0);
            crestBase.castShadow = true;
            crestGroup.add(crestBase);

            const crestTip = new THREE.Mesh(
                new THREE.ConeGeometry(0.18, 1.2, 4),
                materials.soldierJaw
            );
            crestTip.rotation.x = -Math.PI / 2.2;
            crestTip.position.set(0, 1.2, -0.7);
            crestTip.scale.set(0.35, 1.0, 0.8);
            crestGroup.add(crestTip);

            headContainer.add(crestGroup);

            // 3. Dual Forked Mouth Jaws / Mandibles
            for (let side of [-1, 1]) {
                const jawGroup = new THREE.Group();
                jawGroup.position.set(side * 0.22, -0.15, 1.1);

                const upperJaw = new THREE.Mesh(
                    new THREE.ConeGeometry(0.12, 1.1, 5),
                    materials.soldierJaw
                );
                upperJaw.rotation.x = Math.PI / 1.7;
                upperJaw.rotation.z = side * -0.25;
                jawGroup.add(upperJaw);

                const lowerJaw = new THREE.Mesh(
                    new THREE.ConeGeometry(0.08, 0.8, 4),
                    materials.soldierCarapaceDark
                );
                lowerJaw.position.set(0, -0.18, 0.2);
                lowerJaw.rotation.x = Math.PI / 1.5;
                lowerJaw.rotation.z = side * -0.1;
                jawGroup.add(lowerJaw);

                headContainer.add(jawGroup);
            }

            // 4. Large Front Scythe Legs
            for (let side of [-1, 1]) {
                const frontScythe = new THREE.Group();
                frontScythe.position.set(side * 0.55, 0.1, 0.4);

                const joint = new THREE.Mesh(
                    new THREE.SphereGeometry(0.18, 8, 8),
                    materials.soldierCarapaceDark
                );
                frontScythe.add(joint);

                const mainBlade = new THREE.Mesh(
                    new THREE.ConeGeometry(0.16, 2.6, 5),
                    materials.soldierCarapace
                );
                mainBlade.position.set(side * 0.35, -0.9, 0.3);
                mainBlade.rotation.z = side * -0.4;
                mainBlade.rotation.x = 0.35;
                mainBlade.castShadow = true;
                frontScythe.add(mainBlade);

                headContainer.add(frontScythe);
            }

            soldier.add(headContainer);

            // 5. Lateral Walking Legs (3 Pairs)
            for (let i = 0; i < 3; i++) {
                for (let side of [-1, 1]) {
                    const leg = new THREE.Group();
                    leg.position.set(side * 0.45, 0.4, -i * 0.45 + 0.2);

                    const upperLeg = new THREE.Mesh(
                        new THREE.CylinderGeometry(0.06, 0.1, 1.2),
                        materials.soldierCarapaceDark
                    );
                    upperLeg.position.set(side * 0.4, -0.1, 0);
                    upperLeg.rotation.z = side * -0.7;
                    upperLeg.rotation.x = -0.2 + i * 0.15;
                    upperLeg.castShadow = true;
                    leg.add(upperLeg);

                    const lowerSpike = new THREE.Mesh(
                        new THREE.ConeGeometry(0.05, 1.1, 4),
                        materials.soldierCarapace
                    );
                    lowerSpike.position.set(side * 0.7, -0.6, 0.1);
                    lowerSpike.rotation.z = side * 0.25;
                    leg.add(lowerSpike);

                    soldier.add(leg);
                }
            }

            soldier.scale.set(0.9, 0.9, 0.9);
            return soldier;
        }

        function buildSoldierLegionSwarm() {
            soldierGroup = new THREE.Group();
            soldierUnits = [];
            instancedSoldierMeshes = [];
            soldierComponents = [];

            // 1. Template soldier to extract component geometries & local transforms
            const templateSoldier = createSingleSoldierModel();
            templateSoldier.position.set(0, 0, 0);
            templateSoldier.rotation.set(0, 0, 0);
            templateSoldier.scale.set(1, 1, 1);
            templateSoldier.updateMatrixWorld(true);

            templateSoldier.traverse((child) => {
                if (child.isMesh) {
                    soldierComponents.push({
                        geometry: child.geometry,
                        material: child.material,
                        localMatrix: child.matrixWorld.clone()
                    });
                }
            });

            // 2. Initialize InstancedMeshes
            const count = 1300;
            soldierComponents.forEach((comp) => {
                const im = new THREE.InstancedMesh(comp.geometry, comp.material, count);
                im.castShadow = true;
                im.receiveShadow = true;
                instancedSoldierMeshes.push(im);
                soldierGroup.add(im);
            });

            // 3. Generate swarm units with independent movement vector & crawling parameters
            for (let i = 0; i < count; i++) {
                const angle = Math.random() * Math.PI * 2;
                const distBias = Math.pow(Math.random(), 1.3);
                const radius = 3.0 + distBias * 50.0;

                const posX = -2 + Math.cos(angle) * radius;
                const posZ = Math.sin(angle) * radius;

                // Random orientation for forward movement
                const moveAngle = Math.random() * Math.PI * 2;
                const scale = 0.75 + Math.random() * 0.3;

                soldierUnits.push({
                    posX: posX,
                    posZ: posZ,
                    baseY: 0,
                    angle: moveAngle,
                    scale: scale,
                    phase: Math.random() * Math.PI * 2,
                    crawlSpeed: 9.0 + Math.random() * 6.0,  // 足の高速ワサワサ速度
                    moveSpeed: 2.2 + Math.random() * 3.8    // 実際の前進速度 (m/s)
                });
            }

            scene.add(soldierGroup);
        }

        function buildParticleSystem() {
            const particleCount = 220;
            const geometry = new THREE.BufferGeometry();
            const positions = new Float32Array(particleCount * 3);

            for (let i = 0; i < particleCount * 3; i += 3) {
                positions[i] = (Math.random() - 0.5) * 70;
                positions[i + 1] = Math.random() * 30;
                positions[i + 2] = (Math.random() - 0.5) * 70;
            }

            geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

            const pMaterial = new THREE.PointsMaterial({
                color: 0x8b5cf6,
                size: 0.25,
                transparent: true,
                opacity: 0.5
            });

            particleSystem = new THREE.Points(geometry, pMaterial);
            scene.add(particleSystem);
        }

        function setupUIEvents() {
            document.getElementById('btn-attack').addEventListener('click', () => {
                if (!isAttacking) {
                    isAttacking = true;
                    attackProgress = 0;
                }
            });

            document.getElementById('btn-toggle-swarm').addEventListener('click', () => {
                showSwarm = !showSwarm;
                soldierGroup.visible = showSwarm;
                document.getElementById('swarm-status').textContent = showSwarm ? 'ON (1300体前進中)' : 'OFF';
                document.getElementById('swarm-status').className = showSwarm ? 'text-purple-400 font-bold' : 'text-slate-500 font-bold';
            });

            document.getElementById('btn-reset-cam').addEventListener('click', () => {
                camera.position.set(0, 14, 34);
                controls.target.set(0, 5, 0);
                controls.update();
            });

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

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

        function animate() {
            requestAnimationFrame(animate);

            const delta = clock.getDelta();
            const time = clock.getElapsedTime();

            // Organic breathing motion for Mother Legion
            if (motherGroup) {
                motherGroup.position.y = Math.sin(time * 1.6) * 0.18;
                if (motherEggSac) {
                    motherEggSac.scale.set(
                        0.95 + Math.sin(time * 3.0) * 0.04,
                        1.15 + Math.cos(time * 3.0) * 0.04,
                        2.2
                    );
                }
            }

            // Animate 1,300 Instanced Soldier Legions ALWAYS MOVING FORWARD & CRAWLING
            if (showSwarm && soldierUnits.length > 0 && instancedSoldierMeshes.length > 0) {
                const tempMatrix = new THREE.Matrix4();
                const tempPos = new THREE.Vector3();
                const tempRot = new THREE.Euler();
                const tempQuat = new THREE.Quaternion();
                const tempScale = new THREE.Vector3();
                const finalMatrix = new THREE.Matrix4();

                for (let i = 0; i < soldierUnits.length; i++) {
                    const u = soldierUnits[i];

                    // 1. Forward movement calculation (towards facing angle)
                    u.posX += Math.sin(u.angle) * u.moveSpeed * delta;
                    u.posZ += Math.cos(u.angle) * u.moveSpeed * delta;

                    // Respawn near Mother Legion when wandering beyond 58 meters
                    const distFromMother = Math.hypot(u.posX - (-2), u.posZ);
                    if (distFromMother > 58.0) {
                        const respawnAngle = Math.random() * Math.PI * 2;
                        const respawnRadius = 2.0 + Math.random() * 8.0;
                        u.posX = -2 + Math.cos(respawnAngle) * respawnRadius;
                        u.posZ = Math.sin(respawnAngle) * respawnRadius;
                        u.angle = Math.random() * Math.PI * 2; // New forward angle
                    }

                    // 2. Dynamic step crawl motions (synchronized pitch & y-bounce with move speed)
                    const stepCycle = time * u.crawlSpeed + u.phase;
                    const yOffset = Math.abs(Math.sin(stepCycle)) * 0.12;
                    const pitch = Math.sin(stepCycle) * 0.08;  // Forward-back tilt per gait
                    const roll = Math.sin(stepCycle * 0.8) * 0.04;

                    tempPos.set(u.posX, u.baseY + yOffset, u.posZ);
                    tempRot.set(pitch, u.angle, roll);
                    tempQuat.setFromEuler(tempRot);
                    tempScale.set(u.scale, u.scale, u.scale);
                    tempMatrix.compose(tempPos, tempQuat, tempScale);

                    for (let c = 0; c < soldierComponents.length; c++) {
                        finalMatrix.multiplyMatrices(tempMatrix, soldierComponents[c].localMatrix);
                        instancedSoldierMeshes[c].setMatrixAt(i, finalMatrix);
                    }
                }

                for (let c = 0; c < instancedSoldierMeshes.length; c++) {
                    instancedSoldierMeshes[c].instanceMatrix.needsUpdate = true;
                }
            }

            // Horn & Microwave Shell Attack Animation
            if (isAttacking) {
                attackProgress += delta * 1.5;
                if (attackProgress < 1.0) {
                    motherHorns.left.rotation.z = -attackProgress * 0.55;
                    motherHorns.right.rotation.z = attackProgress * 0.55;
                    materials.hornCore.emissiveIntensity = 1.2 + attackProgress * 4.0;
                    materials.beam.opacity = Math.sin(attackProgress * Math.PI) * 0.75;
                    hornCoreLight.intensity = attackProgress * 8;
                    hornCoreLight.position.set(-2, 9, 3);
                } else {
                    isAttacking = false;
                    motherHorns.left.rotation.z = 0;
                    motherHorns.right.rotation.z = 0;
                    materials.hornCore.emissiveIntensity = 1.2;
                    materials.beam.opacity = 0.0;
                    hornCoreLight.intensity = 2;
                }
            } else {
                materials.hornCore.emissiveIntensity = 1.2 + Math.sin(time * 4) * 0.3;
            }

            // Animate dust particles
            if (particleSystem) {
                const positions = particleSystem.geometry.attributes.position.array;
                for (let i = 1; i < positions.length; i += 3) {
                    positions[i] -= delta * 0.8;
                    if (positions[i] < 0) positions[i] = 30;
                }
                particleSystem.geometry.attributes.position.needsUpdate = true;
            }

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

レギオン

集合体恐怖症の人は注意

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