<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>メカゴジラ風 3Dモデル (Three.js)</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #050510;
font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', Meiryo, sans-serif;
color: #fff;
}
#canvas-container {
width: 100vw;
height: 100vh;
display: block;
}
#ui {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
pointer-events: none;
background: rgba(0, 0, 0, 0.6);
padding: 15px 20px;
border-radius: 8px;
border: 1px solid #333;
box-shadow: 0 4px 6px rgba(0,0,0,0.5);
}
h1 {
margin: 0 0 10px 0;
font-size: 1.2rem;
color: #00ffcc;
letter-spacing: 1px;
}
p {
margin: 5px 0;
font-size: 0.9rem;
color: #ccc;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.5rem;
font-weight: bold;
color: #00ffcc;
z-index: 20;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { opacity: 0.5; }
50% { opacity: 1; }
100% { opacity: 0.5; }
}
</style>
<!-- Import Map を追加してモジュールパスを解決 -->
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
}
}
</script>
</head>
<body>
<div id="ui">
<h1>メカゴジラ風 3Dモデル</h1>
<p>🖱️ 左ドラッグ:視点回転</p>
<p>🖱️ 右ドラッグ:平行移動</p>
<p>🖱️ スクロール:ズーム</p>
</div>
<div id="loading">システム起動中...</div>
<div id="canvas-container"></div>
<!-- Three.js と OrbitControls を CDN から読み込む -->
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- 基本設定 ---
const container = document.getElementById('canvas-container');
const loading = document.getElementById('loading');
const scene = new THREE.Scene();
// 背景とフォグを少し明るい青緑に変更し、霧の密度を薄くする
const fogColor = new THREE.Color(0x4a7a8c);
scene.background = fogColor;
scene.fog = new THREE.FogExp2(fogColor, 0.0035);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(60, 5, 180); // 巨大化に合わせてカメラを大きく引き、見上げるアングルに
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.target.set(0, 40, 0); // 視点の中心を巨大化したメカゴジラの胸の高さに変更
controls.maxPolarAngle = Math.PI / 2 + 0.1; // 地面より少し下まで
// --- 照明設定 ---
const ambientLight = new THREE.AmbientLight(0x4a7b9c, 3.5); // 環境光をかなり明るく設定
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xddeeff, 2.8); // 主光源も明るく設定
directionalLight.position.set(-50, 100, -100); // 奥から手前へ
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 500;
directionalLight.shadow.camera.left = -100;
directionalLight.shadow.camera.right = 100;
directionalLight.shadow.camera.top = 100;
directionalLight.shadow.camera.bottom = -100;
scene.add(directionalLight);
const blueLight = new THREE.PointLight(0x00ffff, 3, 100); // シティのエネルギー光
blueLight.position.set(0, 10, 0);
scene.add(blueLight);
// --- マテリアル定義 ---
const silverMaterial = new THREE.MeshStandardMaterial({
color: 0x2277aa, // より鮮やかなキャンディブルー
metalness: 1.0,
roughness: 0.2,
});
const darkMetalMaterial = new THREE.MeshStandardMaterial({
color: 0x05101a, // 深い影を作るためのダークブルー
metalness: 0.9,
roughness: 0.4,
});
const jointMaterial = new THREE.MeshStandardMaterial({
color: 0x111111,
metalness: 0.6,
roughness: 0.7,
});
const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); // 赤い発光
// --- メカゴジラ構築 ---
const mechaGroup = new THREE.Group();
// 1. 胴体(大きく前傾)
const torsoGroup = new THREE.Group();
torsoGroup.position.set(0, 10, -2); // 姿勢に合わせて高さと位置を調整
torsoGroup.rotation.x = Math.PI / 5; // 前傾角度を調整(過度な前傾による破綻を修正)
// 胸部コアブロック
const chestGeo = new THREE.BoxGeometry(8, 8, 7);
const chest = new THREE.Mesh(chestGeo, darkMetalMaterial);
chest.castShadow = true;
torsoGroup.add(chest);
// 胸部装甲(トゲトゲしく)
for(let i=0; i<3; i++) {
const width = 9 - i * 1.5;
const armorGeo = new THREE.BoxGeometry(width, 3, 3);
const armor = new THREE.Mesh(armorGeo, silverMaterial);
armor.position.set(0, 3 - i*2.5, 4);
armor.rotation.x = -Math.PI / 8;
armor.castShadow = true;
torsoGroup.add(armor);
}
// 腹部
const absGeo = new THREE.BoxGeometry(6, 7, 6);
const abs = new THREE.Mesh(absGeo, darkMetalMaterial);
abs.position.set(0, -6, 0);
abs.castShadow = true;
torsoGroup.add(abs);
// 腹部装甲
for(let i=0; i<3; i++) {
const armorGeo = new THREE.BoxGeometry(7, 2.5, 3);
const armor = new THREE.Mesh(armorGeo, silverMaterial);
armor.position.set(0, -3.5 - i*2.2, 3);
armor.rotation.x = -Math.PI / 12;
armor.castShadow = true;
torsoGroup.add(armor);
}
mechaGroup.add(torsoGroup);
// 2. 頭部・首
const neckGroup = new THREE.Group();
// 前傾した胴体から前上に首を伸ばす
neckGroup.position.set(0, 6, 3);
neckGroup.rotation.x = -Math.PI / 6;
// 首の装甲
for(let i=0; i<3; i++) {
const neckSegmentGeo = new THREE.BoxGeometry(3.5 - i*0.2, 2, 4 - i*0.2);
const neckSegment = new THREE.Mesh(neckSegmentGeo, silverMaterial);
neckSegment.position.set(0, i*1.5, 0);
neckGroup.add(neckSegment);
}
torsoGroup.add(neckGroup);
const headGroup = new THREE.Group();
headGroup.position.set(0, 5, 0);
headGroup.rotation.x = -Math.PI / 6; // さらに顔を前に向ける
// 頭蓋骨
const skullGeo = new THREE.BoxGeometry(3.5, 2.5, 5);
const skull = new THREE.Mesh(skullGeo, silverMaterial);
skull.castShadow = true;
headGroup.add(skull);
// 鼻先・上顎(分厚く鋭く)
const snoutGeo = new THREE.BoxGeometry(2, 1.5, 5);
const snout = new THREE.Mesh(snoutGeo, silverMaterial);
snout.position.set(0, -0.5, 4);
const snoutTipGeo = new THREE.ConeGeometry(1, 3, 4);
const snoutTip = new THREE.Mesh(snoutTipGeo, silverMaterial);
snoutTip.position.set(0, 0, 3);
snoutTip.rotation.x = Math.PI / 2;
snoutTip.rotation.y = Math.PI / 4;
snout.add(snoutTip);
headGroup.add(snout);
// 頬のトゲ(長く鋭く)
for (let sign of [1, -1]) {
const cheekSpike = new THREE.Mesh(new THREE.ConeGeometry(0.5, 4, 4), silverMaterial);
cheekSpike.position.set(2.5 * sign, -1, 3);
cheekSpike.rotation.z = (Math.PI / 3) * sign;
cheekSpike.rotation.x = Math.PI / 4;
headGroup.add(cheekSpike);
}
// 下顎
const jawGeo = new THREE.BoxGeometry(2, 1, 5);
const jaw = new THREE.Mesh(jawGeo, darkMetalMaterial);
jaw.position.set(0, -1.8, 3.5);
jaw.rotation.x = Math.PI / 16;
// 下顎の牙
const jawTipGeo = new THREE.ConeGeometry(0.8, 2, 4);
const jawTip = new THREE.Mesh(jawTipGeo, silverMaterial);
jawTip.position.set(0, -0.5, 2.5);
jawTip.rotation.x = Math.PI / 2.5;
jawTip.rotation.y = Math.PI / 4;
jaw.add(jawTip);
headGroup.add(jaw);
// 目
const eyeGeo = new THREE.BoxGeometry(0.2, 0.4, 1.5);
const leftEye = new THREE.Mesh(eyeGeo, eyeMaterial);
leftEye.position.set(1.8, 0, 2);
leftEye.rotation.x = -Math.PI / 8;
headGroup.add(leftEye);
const rightEye = new THREE.Mesh(eyeGeo, eyeMaterial);
rightEye.position.set(-1.8, 0, 2);
rightEye.rotation.x = -Math.PI / 8;
headGroup.add(rightEye);
// 頭頂部の巨大な角/アンテナ(画像のように長く斜め前へ)
const mainCrest = new THREE.Mesh(new THREE.ConeGeometry(0.5, 6, 4), silverMaterial);
mainCrest.position.set(0, 3, -1);
mainCrest.rotation.x = -Math.PI / 3;
headGroup.add(mainCrest);
const subCrest = new THREE.Mesh(new THREE.ConeGeometry(0.4, 4, 4), silverMaterial);
subCrest.position.set(0, 2.5, -2.5);
subCrest.rotation.x = -Math.PI / 2.5;
headGroup.add(subCrest);
neckGroup.add(headGroup);
// 3. 背びれ(放射状に広がるトゲ)
const createFin = (w, h, d) => {
const geo = new THREE.ConeGeometry(w, h, 4); // 板状より錐体に戻してシャープに
const mesh = new THREE.Mesh(geo, silverMaterial);
mesh.castShadow = true;
return mesh;
};
for (let i = 0; i < 7; i++) {
const scale = i < 3 ? 1 + (i * 0.3) : 1.9 - ((i-2) * 0.4);
const zPos = -1 - (i * 2);
const yPos = 4 - (i * 1.5);
// 中央列
const finC = createFin(1, 7 * scale, 4);
finC.position.set(0, yPos, zPos);
finC.rotation.x = -Math.PI / 3;
torsoGroup.add(finC);
// 左右列(外側に広げる)
const finL = createFin(0.8, 5 * scale, 4);
finL.position.set(2.5, yPos - 1, zPos + 1);
finL.rotation.x = -Math.PI / 3;
finL.rotation.z = -Math.PI / 6;
torsoGroup.add(finL);
const finR = createFin(0.8, 5 * scale, 4);
finR.position.set(-2.5, yPos - 1, zPos + 1);
finR.rotation.x = -Math.PI / 3;
finR.rotation.z = Math.PI / 6;
torsoGroup.add(finR);
}
// 4. 腕(巨大な前腕アーマー)
function createArm(isLeft) {
const armGroup = new THREE.Group();
const sign = isLeft ? 1 : -1;
// 肩
const shoulderBase = new THREE.Mesh(new THREE.BoxGeometry(4, 6, 5), darkMetalMaterial);
shoulderBase.position.set(6 * sign, 2, 0);
armGroup.add(shoulderBase);
// 肩アーマー
const shoulderArmor = new THREE.Mesh(new THREE.BoxGeometry(6, 6, 6), silverMaterial);
shoulderArmor.position.set(7 * sign, 4, 0);
shoulderArmor.rotation.z = (-Math.PI / 8) * sign;
armGroup.add(shoulderArmor);
// 肩の巨大スパイク
const sSpike = new THREE.Mesh(new THREE.ConeGeometry(1, 6, 4), silverMaterial);
sSpike.position.set(8 * sign, 7, 0);
sSpike.rotation.z = (-Math.PI / 4) * sign;
armGroup.add(sSpike);
// 上腕
const upperArm = new THREE.Mesh(new THREE.BoxGeometry(2.5, 5, 2.5), darkMetalMaterial);
upperArm.position.set(7 * sign, -2, 0);
armGroup.add(upperArm);
// 下腕(巨大な装甲)
const lowerArmGroup = new THREE.Group();
lowerArmGroup.position.set(7.5 * sign, -5, 2);
lowerArmGroup.rotation.x = -Math.PI / 4; // 前腕を前方へ突き出す
const lowerArmBase = new THREE.Mesh(new THREE.BoxGeometry(4, 7, 4), silverMaterial);
lowerArmGroup.add(lowerArmBase);
// 肘の丸い関節パーツ(画像に見られる特徴的な銀色の円形パーツ)
const jointMat = new THREE.MeshStandardMaterial({color: 0xcccccc, metalness: 0.9, roughness: 0.2});
const elbowJoint = new THREE.Mesh(new THREE.CylinderGeometry(1.8, 1.8, 4.5, 16), jointMat);
elbowJoint.rotation.z = Math.PI / 2;
elbowJoint.position.set(0, 3.5, 0);
lowerArmGroup.add(elbowJoint);
// 前腕の装甲ブレード(より巨大に)
const blade = new THREE.Mesh(new THREE.ConeGeometry(1.2, 10, 4), silverMaterial);
blade.position.set(0, -3, 2.5);
blade.rotation.x = Math.PI / 8;
lowerArmGroup.add(blade);
// 肘のトゲ
const elbowSpike = new THREE.Mesh(new THREE.ConeGeometry(0.8, 3, 4), silverMaterial);
elbowSpike.position.set(0, 3, -2.5);
elbowSpike.rotation.x = -Math.PI / 2;
lowerArmGroup.add(elbowSpike);
// 爪
for(let i=0; i<3; i++) {
const claw = new THREE.Mesh(new THREE.ConeGeometry(0.5, 3, 4), silverMaterial);
claw.position.set(((i-1)*1.2) * sign, -4.5, 1);
claw.rotation.x = Math.PI / 6;
lowerArmGroup.add(claw);
}
armGroup.add(lowerArmGroup);
return armGroup;
}
torsoGroup.add(createArm(true));
torsoGroup.add(createArm(false));
// ※胴体はここまで。完成した胴体をメカゴジラ全体に追加
mechaGroup.add(torsoGroup);
// 5. 脚(踏ん張った逆関節感)
function createLeg(isLeft) {
const legGroup = new THREE.Group();
const sign = isLeft ? 1 : -1;
// 胴体から切り離し、絶対座標で脚の付け根を配置(脚を細くした分少し内側に寄せる)
legGroup.position.set(4.5 * sign, 1, -2);
const thighGroup = new THREE.Group();
thighGroup.position.set(0, 8, 0);
thighGroup.rotation.x = -Math.PI / 6; // 太ももを少し前へ
// 太ももベース(細く)
const thighBase = new THREE.Mesh(new THREE.BoxGeometry(4, 10, 6), darkMetalMaterial);
thighGroup.add(thighBase);
// 太もも装甲(細く)
for(let i=0; i<3; i++) {
const tArmorFront = new THREE.Mesh(new THREE.BoxGeometry(4.5, 3.5, 2.5), silverMaterial);
tArmorFront.position.set(0, 3 - i*3, 3.5);
tArmorFront.rotation.x = -Math.PI / 12;
thighGroup.add(tArmorFront);
}
legGroup.add(thighGroup); // legGroupに直接追加
// 膝スパイク(細く)
const kneeSpike = new THREE.Mesh(new THREE.ConeGeometry(0.8, 5, 4), silverMaterial);
kneeSpike.position.set(0, -4.5, 4);
kneeSpike.rotation.x = Math.PI / 4;
thighGroup.add(kneeSpike);
// すね
const shinGroup = new THREE.Group();
shinGroup.position.set(0, -6.5, 2);
shinGroup.rotation.x = Math.PI / 4; // 膝を曲げて後ろへ
// すねベース(細く)
const shinBase = new THREE.Mesh(new THREE.BoxGeometry(3.5, 9, 5), silverMaterial);
shinGroup.add(shinBase);
// すね装甲(細く)
const sArmorFront = new THREE.Mesh(new THREE.BoxGeometry(4, 8, 2.5), silverMaterial);
sArmorFront.position.set(0, 0, 3);
shinGroup.add(sArmorFront);
thighGroup.add(shinGroup);
// 足(接地面)
const footGroup = new THREE.Group();
footGroup.position.set(0, -5, 2);
footGroup.rotation.x = -Math.PI / 12; // 地面に並行に
// 足ベース(細く)
const footBase = new THREE.Mesh(new THREE.BoxGeometry(5, 3, 10), silverMaterial);
footGroup.add(footBase);
// 足の爪(細く、間隔を狭める)
for(let i=0; i<3; i++) {
const toe = new THREE.Mesh(new THREE.ConeGeometry(0.8, 5, 4), silverMaterial);
toe.position.set(((i-1)*1.5) * sign, -0.5, 6);
toe.rotation.x = Math.PI / 2;
footGroup.add(toe);
}
shinGroup.add(footGroup);
return legGroup;
}
// 脚をメカゴジラ全体に直接追加(胴体の傾きに影響されない)
mechaGroup.add(createLeg(true));
mechaGroup.add(createLeg(false));
// 6. 尻尾(地面に這うように後方に長く)
const tailGroup = new THREE.Group();
tailGroup.position.set(0, 6, -5); // 腰の位置の絶対座標から生やす
const numTailSegments = 16;
for (let i = 0; i < numTailSegments; i++) {
const size = 5.5 * (1 - i * 0.05);
const segmentGroup = new THREE.Group();
const zPos = - (i * 4);
const yPos = - (i * 0.8) - (i * i * 0.02); // 徐々に地面に下りていく
segmentGroup.position.set(0, Math.max(yPos, -7), zPos);
segmentGroup.rotation.x = Math.max(-Math.PI / 8 + (i * 0.05), 0); // 根本は少し下向き、先は水平
// 尻尾コア
const core = new THREE.Mesh(new THREE.BoxGeometry(size*0.8, size*0.8, size), darkMetalMaterial);
segmentGroup.add(core);
// 尻尾装甲
const topArmor = new THREE.Mesh(new THREE.BoxGeometry(size, size*0.4, size*1.2), silverMaterial);
topArmor.position.set(0, size*0.4, 0);
segmentGroup.add(topArmor);
const sideArmorL = new THREE.Mesh(new THREE.BoxGeometry(size*0.4, size*0.8, size*1.2), silverMaterial);
sideArmorL.position.set(size*0.4, 0, 0);
segmentGroup.add(sideArmorL);
const sideArmorR = new THREE.Mesh(new THREE.BoxGeometry(size*0.4, size*0.8, size*1.2), silverMaterial);
sideArmorR.position.set(-size*0.4, 0, 0);
segmentGroup.add(sideArmorR);
// 上部スパイク
const spike = new THREE.Mesh(new THREE.ConeGeometry(size*0.2, size*1.5, 4), silverMaterial);
spike.position.set(0, size*0.8, 0);
segmentGroup.add(spike);
// 左右スパイク
if (i > 0) {
const lSpike = new THREE.Mesh(new THREE.ConeGeometry(size*0.15, size, 4), silverMaterial);
lSpike.position.set(size*0.8, 0, 0);
lSpike.rotation.z = -Math.PI / 2;
segmentGroup.add(lSpike);
const rSpike = new THREE.Mesh(new THREE.ConeGeometry(size*0.15, size, 4), silverMaterial);
rSpike.position.set(-size*0.8, 0, 0);
rSpike.rotation.z = Math.PI / 2;
segmentGroup.add(rSpike);
}
tailGroup.add(segmentGroup);
}
// 尻尾もメカゴジラ全体に直接追加
mechaGroup.add(tailGroup);
// --- メカゴジラ本体を巨大化 ---
const scaleFactor = 4.0; // 4倍のサイズに拡大
mechaGroup.scale.set(scaleFactor, scaleFactor, scaleFactor);
// 巨大化した分、足が地面(y=-4)にぴったり接するようにY座標を計算して持ち上げる
mechaGroup.position.y = 4 * (scaleFactor - 1);
scene.add(mechaGroup);
// --- 背景・地形(メカゴジラシティ風) ---
// 1. 荒涼とした起伏のある地面(岩肌)
const groundGeo = new THREE.PlaneGeometry(800, 800, 128, 128);
const pos = groundGeo.attributes.position;
for (let i = 0; i < pos.count; i++) {
const x = pos.getX(i);
const y = pos.getY(i);
// 複数のサイン波を重ねて自然な起伏を作る
let z = Math.sin(x * 0.02) * Math.cos(y * 0.02) * 15;
z += Math.sin(x * 0.05) * Math.cos(y * 0.04) * 5;
// 中央(メカゴジラの足元)はめり込まないように平らに均す
const dist = Math.sqrt(x*x + y*y);
if (dist < 40) {
z = z * (dist / 40);
}
pos.setZ(i, z);
}
groundGeo.computeVertexNormals();
const groundMat = new THREE.MeshStandardMaterial({
color: 0x1a2b3c, // 暗い岩肌の色
roughness: 0.9,
metalness: 0.2,
flatShading: true // ポリゴン感を出してゴツゴツさせる
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -4; // 足元の高さ調整
ground.receiveShadow = true;
scene.add(ground);
// 2. 遠景のドーム型建造物群
const cityMat = new THREE.MeshStandardMaterial({
color: 0x223344,
roughness: 0.6,
metalness: 0.7,
flatShading: true
});
function createDome(x, z, scale) {
const domeGroup = new THREE.Group();
domeGroup.position.set(x, -4, z); // 地面と同じ基準高さ
// ドーム本体(半球)
const domeGeo = new THREE.SphereGeometry(15 * scale, 32, 16, 0, Math.PI * 2, 0, Math.PI / 2);
const dome = new THREE.Mesh(domeGeo, cityMat);
dome.castShadow = true;
dome.receiveShadow = true;
domeGroup.add(dome);
// ドームのリング装飾
const ringGeo = new THREE.TorusGeometry(15.2 * scale, 0.5 * scale, 8, 32);
const ring = new THREE.Mesh(ringGeo, cityMat);
ring.rotation.x = Math.PI / 2;
ring.position.y = 5 * scale;
domeGroup.add(ring);
// 側面から伸びる巨大なパイプ
for(let i=0; i<4; i++) {
const pipeAngle = (Math.PI / 2) * i + Math.random();
const pipeGeo = new THREE.CylinderGeometry(1.5 * scale, 1.5 * scale, 25 * scale, 8);
const pipe = new THREE.Mesh(pipeGeo, cityMat);
// ドームから斜め下へ伸びる角度
pipe.rotation.z = Math.PI / 4;
pipe.rotation.y = pipeAngle;
// 位置調整
pipe.position.x = Math.cos(pipeAngle) * (15 * scale);
pipe.position.y = 8 * scale;
pipe.position.z = -Math.sin(pipeAngle) * (15 * scale);
domeGroup.add(pipe);
}
return domeGroup;
}
// 遠景にドームを複数配置
scene.add(createDome(-80, -120, 3));
scene.add(createDome(60, -180, 4.5));
scene.add(createDome(140, -100, 2.5));
scene.add(createDome(-160, -80, 2));
scene.add(createDome(0, -250, 6));
// --- ロード完了処理 ---
loading.style.display = 'none';
// --- アニメーションループ ---
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
// 待機モーション(ゆっくりとした呼吸)
// 胴体が上下すると脚と分離してしまうため、胴体の角度(胸の張り)だけで表現
torsoGroup.rotation.x = Math.PI / 5 + Math.sin(elapsedTime * 1.5) * 0.02;
// 尻尾をゆっくり揺らす
tailGroup.children.forEach((segment, index) => {
segment.position.x = Math.sin(elapsedTime * 2 - index * 0.5) * (index * 0.2);
});
// 目の明滅(呼吸に合わせて)
eyeMaterial.color.setHSL(0, 1, 0.3 + Math.sin(elapsedTime * 4) * 0.2);
controls.update();
renderer.render(scene, camera);
}
animate();
// --- ウィンドウリサイズ対応 ---
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>
メカゴジラです