<!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: #000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#canvas-container {
width: 100vw;
height: 100vh;
display: block;
}
#ui-layer {
position: absolute;
top: 20px;
left: 20px;
color: #FFD700;
pointer-events: auto;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
background: rgba(0, 0, 0, 0.5);
padding: 15px;
border-radius: 8px;
border: 1px solid #FFD700;
}
h1 {
margin: 0 0 10px 0;
font-size: 20px;
letter-spacing: 2px;
}
p {
margin: 0 0 10px 0;
font-size: 14px;
color: #ddd;
}
select {
background-color: #222;
color: #FFD700;
border: 1px solid #FFD700;
padding: 8px;
font-size: 16px;
border-radius: 4px;
outline: none;
cursor: pointer;
width: 100%;
}
select:hover {
background-color: #333;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #FFD700;
font-size: 20px;
font-weight: bold;
transition: opacity 0.5s;
pointer-events: none;
}
</style>
<!-- Three.js本体 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- OrbitControls(カメラ操作用) -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<div id="ui-layer">
<h1 id="title-text">黄金の三つ首竜</h1>
<p>ドラッグ:視点移動 / スクロール:ズーム</p>
<select id="version-select" onchange="changeVersion()">
<option value="monsterverse" selected>ハリウッド版 (MonsterVerse)</option>
<option value="heisei">平成版 (Heisei / VS) + 昼の街とゴジラ</option>
<option value="showa">初代版 (Showa) + 怪獣島</option>
</select>
</div>
<div id="loading">モデルを生成中...</div>
<div id="canvas-container"></div>
<script>
let scene, camera, renderer, controls;
let ghidorahGroup, leftWingPivot, rightWingPivot, bodyGroup, environmentGroup;
let neckGroups = [];
let tailGroups = [];
let clock = new THREE.Clock();
// ライトのグローバル変数
let ambientLight, dirLight, hemiLight, pointLight;
// バージョン定義 (環境・背景色を追加)
const VERSIONS = {
monsterverse: {
title: "ハリウッド版 (Flying Pose)",
color: 0xDDAA00,
metalness: 0.8,
roughness: 0.35,
bodyScale: [1.0, 1.8, 1.2],
neckScale: 1.4,
neckCount: 40,
tailScale: 1.3,
tailCount: 45,
hasMace: true,
wingType: 'wyvern',
wingScale: 1.2,
pose: 'flying',
bgColor: 0x0a224a, // 深い夜空
fogDensity: 0.003,
envType: 'none', // 背景なし
lightIntensity: 2.5,
ambientColor: 0x556688
},
heisei: {
title: "平成版 (Standing in Day City)",
color: 0xE5D5A5, // シャンパンゴールド系
metalness: 0.85,
roughness: 0.25,
bodyScale: [1.6, 1.5, 1.5], // よりマッシブに
neckScale: 1.1,
neckCount: 35,
tailScale: 1.1,
tailCount: 40,
hasMace: false,
wingType: 'fan',
wingScale: 1.4,
pose: 'standing', // 直立ポーズ
bgColor: 0x66AADD, // 昼の青空
fogDensity: 0.002, // 視界を少しクリアに
envType: 'day_city', // 昼の街ステージON
lightIntensity: 3.5, // 太陽光を強く
ambientColor: 0xAACCDD // 明るい環境光
},
showa: {
title: "初代版 (Standing in Monster Island)",
color: 0xCC9933, // 真鍮のような落ち着いた金(ソフビ風)
metalness: 0.6,
roughness: 0.6,
bodyScale: [1.5, 1.4, 1.4], // ずんぐり
neckScale: 1.0, // 首の長さを延長
neckCount: 32,
tailScale: 0.9, // 尻尾の長さを延長
tailCount: 35,
hasMace: false,
wingType: 'fan_showa',// 初代特有の丸みのある扇型
wingScale: 1.1,
pose: 'standing', // 直立ポーズ
bgColor: 0x82a4b3, // 特撮ホリゾント風の少し濁った青空
fogDensity: 0.002, // 砂埃・空気感
envType: 'island', // 怪獣島ステージON
lightIntensity: 2.0, // 昼間の光
ambientColor: 0x88aabb // 環境光も少し明るい昼色に
}
};
let currentVersion = 'monsterverse';
// 共通マテリアル
let goldMaterial = new THREE.MeshStandardMaterial({
side: THREE.DoubleSide
});
const eyeMaterial = new THREE.MeshStandardMaterial({
color: 0xFF0000,
emissive: 0x880000,
metalness: 0.1,
roughness: 0.1
});
window.addEventListener('DOMContentLoaded', init);
function init() {
const container = document.getElementById('canvas-container');
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a224a);
scene.fog = new THREE.FogExp2(0x0a224a, 0.003);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.set(0, -30, 150);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.target.set(0, 0, 0);
controls.maxDistance = 400;
controls.minDistance = 50;
setupLighting();
// 初期生成
applyVersionSettings();
buildGhidorah();
document.getElementById('loading').style.opacity = 0;
setTimeout(() => document.getElementById('loading').remove(), 500);
window.addEventListener('resize', onWindowResize);
animate();
}
// バージョン切り替え処理
window.changeVersion = function() {
const select = document.getElementById('version-select');
currentVersion = select.value;
if (ghidorahGroup) {
scene.remove(ghidorahGroup);
}
neckGroups = [];
tailGroups = [];
leftWingPivot = null;
rightWingPivot = null;
applyVersionSettings();
buildGhidorah();
};
function applyVersionSettings() {
const vData = VERSIONS[currentVersion];
// マテリアルの更新
goldMaterial.color.setHex(vData.color);
goldMaterial.metalness = vData.metalness;
goldMaterial.roughness = vData.roughness;
document.getElementById('title-text').innerText = vData.title;
// 背景色とフォグの更新
scene.background.setHex(vData.bgColor);
scene.fog.color.setHex(vData.bgColor);
scene.fog.density = vData.fogDensity;
// ライトの強さと色の更新
if (dirLight) dirLight.intensity = vData.lightIntensity;
if (ambientLight) ambientLight.color.setHex(vData.ambientColor);
// 環境(ステージ)の再構築
buildEnvironment(vData);
}
function setupLighting() {
ambientLight = new THREE.AmbientLight(0x556688, 1.2);
scene.add(ambientLight);
dirLight = new THREE.DirectionalLight(0xffffff, 2.5);
dirLight.position.set(50, 50, 50);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.near = 10;
dirLight.shadow.camera.far = 500;
// 影の範囲を広く設定
dirLight.shadow.camera.left = -200;
dirLight.shadow.camera.right = 200;
dirLight.shadow.camera.top = 200;
dirLight.shadow.camera.bottom = -200;
scene.add(dirLight);
hemiLight = new THREE.HemisphereLight(0x8899bb, 0x334455, 1.0);
scene.add(hemiLight);
pointLight = new THREE.PointLight(0xFFFFFF, 1.5, 300);
pointLight.position.set(0, 30, 50);
scene.add(pointLight);
}
function buildEnvironment(vData) {
// 古いステージを削除
if (environmentGroup) {
scene.remove(environmentGroup);
}
environmentGroup = new THREE.Group();
scene.add(environmentGroup);
if (vData.envType === 'island') {
// 地面 (荒涼とした土)
const groundGeo = new THREE.PlaneGeometry(1500, 1500, 64, 64);
const pos = groundGeo.attributes.position;
for (let i = 0; i < pos.count; i++) {
pos.setZ(i, Math.random() * 5); // ゴツゴツさせる
}
groundGeo.computeVertexNormals();
const groundMat = new THREE.MeshStandardMaterial({
color: 0x5c4033, // 特撮の土の色
roughness: 1.0,
metalness: 0.0
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -16; // ギドラの足がぴったり着く高さ
ground.receiveShadow = true;
environmentGroup.add(ground);
// 岩山の配置 (怪獣島らしさ)
for(let i=0; i<35; i++) {
const rockSize = Math.random() * 25 + 10;
const rockGeo = new THREE.DodecahedronGeometry(rockSize, 1);
// 岩をランダムに歪ませる
const rPos = rockGeo.attributes.position;
for(let j=0; j<rPos.count; j++) {
rPos.setXYZ(j,
rPos.getX(j) * (1 + Math.random()*0.3),
rPos.getY(j) * (1 + Math.random()*0.3),
rPos.getZ(j) * (1 + Math.random()*0.3)
);
}
rockGeo.computeVertexNormals();
const rockMat = new THREE.MeshStandardMaterial({
color: 0x665544,
roughness: 0.9
});
const rock = new THREE.Mesh(rockGeo, rockMat);
// 手前を塞がないように、奥や左右に配置
const angle = Math.random() * Math.PI;
const radius = Math.random() * 200 + 80;
rock.position.set(
Math.cos(angle) * radius * 1.5,
-16 + rockSize * 0.5,
-Math.sin(angle) * radius - 30 // Zは奥(マイナス)へ
);
rock.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0);
rock.scale.set(1, Math.random() * 2 + 1.2, 1);
rock.castShadow = true;
rock.receiveShadow = true;
environmentGroup.add(rock);
}
} else if (vData.envType === 'city' || vData.envType === 'day_city') {
// 街のジオラマ(昼夜共通ベース)
const isDay = vData.envType === 'day_city';
const groundGeo = new THREE.PlaneGeometry(1500, 1500);
const groundMat = new THREE.MeshStandardMaterial({
color: isDay ? 0x444444 : 0x1a1a20, // 昼は少し明るいアスファルト色
roughness: 0.9,
metalness: 0.1
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -15.5;
ground.receiveShadow = true;
environmentGroup.add(ground);
// ゴジラの配置(昼の街の場合のみ)
if (isDay) {
const godzilla = createGodzilla();
// ギドラの手前(Z方向)に配置し、ギドラの方を向かせる
godzilla.position.set(20, -15.5, 70);
godzilla.lookAt(0, -10, 0);
environmentGroup.add(godzilla);
}
// ビル群の生成
const buildingGeo = new THREE.BoxGeometry(1, 1, 1);
buildingGeo.translate(0, 0.5, 0);
const mats = [
new THREE.MeshStandardMaterial({ color: isDay ? 0x888899 : 0x222233, roughness: 0.6 }),
new THREE.MeshStandardMaterial({ color: isDay ? 0x777788 : 0x333344, roughness: 0.5 }),
new THREE.MeshStandardMaterial({ color: isDay ? 0x9999AA : 0x111118, roughness: 0.8 }),
];
for(let i=0; i<80; i++) {
const width = Math.random() * 15 + 10;
const depth = Math.random() * 15 + 10;
const height = Math.random() * 60 + 20;
const mat = mats[Math.floor(Math.random() * mats.length)];
const bMesh = new THREE.Mesh(buildingGeo, mat);
bMesh.scale.set(width, height, depth);
// 中央(怪獣たちのいる場所)を空けて配置
const angle = Math.random() * Math.PI * 2;
const radius = Math.random() * 200 + 80;
bMesh.position.set(
Math.cos(angle) * radius,
-15.5,
-Math.sin(angle) * radius
);
bMesh.rotation.y = Math.random() * Math.PI;
bMesh.castShadow = true;
bMesh.receiveShadow = true;
environmentGroup.add(bMesh);
// 夜の街の場合のみ航空障害灯を付ける
if (!isDay && Math.random() > 0.5) {
const lightGeo = new THREE.BoxGeometry(1.5, 1.5, 1.5);
const lightMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const lightMesh = new THREE.Mesh(lightGeo, lightMat);
lightMesh.position.set(bMesh.position.x, -15.5 + height, bMesh.position.z);
environmentGroup.add(lightMesh);
}
}
} else if (vData.pose === 'standing') {
// その他の直立ポーズ(予備)用:スタジオ撮影風の反射する暗い床
const groundGeo = new THREE.PlaneGeometry(500, 500);
const groundMat = new THREE.MeshStandardMaterial({
color: 0x111111,
roughness: 0.2,
metalness: 0.8
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -15.5;
ground.receiveShadow = true;
environmentGroup.add(ground);
}
}
function buildGhidorah() {
const vData = VERSIONS[currentVersion];
const isStanding = vData.pose === 'standing';
ghidorahGroup = new THREE.Group();
bodyGroup = new THREE.Group();
ghidorahGroup.add(bodyGroup);
scene.add(ghidorahGroup);
// 飛行か直立かで全体の姿勢を変える
ghidorahGroup.rotation.x = isStanding ? 0 : Math.PI / 8;
// --- 体 ---
const bodyGeo = new THREE.SphereGeometry(7, 16, 16);
const bodyMesh = new THREE.Mesh(bodyGeo, goldMaterial);
bodyMesh.scale.set(vData.bodyScale[0], vData.bodyScale[1], vData.bodyScale[2]);
if (isStanding) {
bodyMesh.position.set(0, 5, 0);
bodyMesh.rotation.x = Math.PI / 10; // ほぼ直立(少し前傾)
} else {
bodyMesh.position.set(0, 0, 0);
bodyMesh.rotation.x = Math.PI / 2.5; // 水平に近い
}
bodyMesh.castShadow = true;
bodyGroup.add(bodyMesh);
// --- 首 ---
const nS = vData.neckScale;
// 直立時は体の上部から、飛行時は体の前方から生やす
const nBaseY = isStanding ? 10 : 5;
const nBaseZ = isStanding ? 5 : 8; // 首の根本を前方に移動して翼から離す
const centerControl = isStanding ? new THREE.Vector3(0, nBaseY + 15*nS, nBaseZ + 15*nS) : new THREE.Vector3(0, 15*nS, 25*nS);
const centerEnd = isStanding ? new THREE.Vector3(0, nBaseY + 30*nS, nBaseZ + 20*nS) : new THREE.Vector3(0, 30*nS, 40*nS);
createNeckWithHead({
start: new THREE.Vector3(0, nBaseY, nBaseZ),
control: centerControl,
end: centerEnd,
radiusStart: 3.2, radiusEnd: 1.5,
animSpeed: 1.2, animPhase: 0.0,
count: vData.neckCount
});
// 左右の首も外側・前方に広げる
const leftControl = isStanding ? new THREE.Vector3(-12*nS, nBaseY + 12*nS, nBaseZ + 10*nS) : new THREE.Vector3(-15*nS, 12*nS, 20*nS);
const leftEnd = isStanding ? new THREE.Vector3(-18*nS, nBaseY + 25*nS, nBaseZ + 15*nS) : new THREE.Vector3(-25*nS, 25*nS, 35*nS);
createNeckWithHead({
start: new THREE.Vector3(-4, nBaseY-1, nBaseZ-1),
control: leftControl,
end: leftEnd,
radiusStart: 3.0, radiusEnd: 1.4,
animSpeed: 1.0, animPhase: Math.PI / 3,
count: vData.neckCount
});
const rightControl = isStanding ? new THREE.Vector3(12*nS, nBaseY + 12*nS, nBaseZ + 10*nS) : new THREE.Vector3(15*nS, 12*nS, 20*nS);
const rightEnd = isStanding ? new THREE.Vector3(18*nS, nBaseY + 25*nS, nBaseZ + 15*nS) : new THREE.Vector3(25*nS, 25*nS, 35*nS);
createNeckWithHead({
start: new THREE.Vector3(4, nBaseY-1, nBaseZ-1),
control: rightControl,
end: rightEnd,
radiusStart: 3.0, radiusEnd: 1.4,
animSpeed: 1.4, animPhase: -Math.PI / 4,
count: vData.neckCount
});
// --- 尻尾 ---
const tS = vData.tailScale;
const tBaseY = isStanding ? -2 : -2;
const tBaseZ = isStanding ? -4 : -8;
// 直立ポーズ時は、尻尾を地面(Y=-12付近)に這わせながら後方へ長く伸ばす
const tailLControl = isStanding ? new THREE.Vector3(-8*tS, 0, -25*tS) : new THREE.Vector3(-8*tS, -10*tS, -40*tS);
const tailLEnd = isStanding ? new THREE.Vector3(-15*tS, -12, -60*tS) : new THREE.Vector3(-15*tS, -5*tS, -80*tS);
createTail(new THREE.Vector3(-2, tBaseY, tBaseZ), tailLControl, tailLEnd, 0, vData);
const tailRControl = isStanding ? new THREE.Vector3(8*tS, 0, -25*tS) : new THREE.Vector3(8*tS, -10*tS, -40*tS);
const tailREnd = isStanding ? new THREE.Vector3(15*tS, -12, -60*tS) : new THREE.Vector3(15*tS, -5*tS, -80*tS);
createTail(new THREE.Vector3(2, tBaseY, tBaseZ), tailRControl, tailREnd, Math.PI, vData);
// --- 足 ---
createLeg(-5, vData, isStanding);
createLeg(5, vData, isStanding);
// --- 翼 ---
createWings(vData, isStanding);
}
function createNeckWithHead(params) {
const neckRootGroup = new THREE.Group();
neckRootGroup.position.copy(params.start);
const localControl = new THREE.Vector3().subVectors(params.control, params.start);
const localEnd = new THREE.Vector3().subVectors(params.end, params.start);
const count = params.count;
const segments = [];
for (let i = 0; i <= count; i++) {
const t = i / count;
const r = params.radiusStart * (1 - t) + params.radiusEnd * t;
const sphereGeo = new THREE.SphereGeometry(r, 16, 16);
const mesh = new THREE.Mesh(sphereGeo, goldMaterial);
mesh.scale.y = 0.8;
mesh.castShadow = true;
neckRootGroup.add(mesh);
segments.push({ mesh: mesh, t: t, r: r });
}
const headGroup = createHeadMesh();
neckRootGroup.add(headGroup);
neckRootGroup.userData = {
baseLocalControl: localControl,
baseLocalEnd: localEnd,
segments: segments,
headGroup: headGroup,
speed: params.animSpeed,
phase: params.animPhase
};
neckGroups.push(neckRootGroup);
bodyGroup.add(neckRootGroup);
}
function createHeadMesh() {
const headGroup = new THREE.Group();
const skullGeo = new THREE.BoxGeometry(2.5, 2.5, 3.5);
const skull = new THREE.Mesh(skullGeo, goldMaterial);
skull.position.set(0, 0, 1);
skull.castShadow = true;
headGroup.add(skull);
const snoutGeo = new THREE.BoxGeometry(1.8, 1.5, 3);
const snout = new THREE.Mesh(snoutGeo, goldMaterial);
snout.position.set(0, -0.5, 4);
snout.castShadow = true;
headGroup.add(snout);
const hornGeo = new THREE.ConeGeometry(0.4, 3, 8);
hornGeo.translate(0, 1.5, 0);
const leftHorn = new THREE.Mesh(hornGeo, goldMaterial);
leftHorn.position.set(-1, 1, 0);
leftHorn.rotation.x = -Math.PI / 3;
leftHorn.rotation.z = Math.PI / 6;
leftHorn.castShadow = true;
headGroup.add(leftHorn);
const rightHorn = new THREE.Mesh(hornGeo, goldMaterial);
rightHorn.position.set(1, 1, 0);
rightHorn.rotation.x = -Math.PI / 3;
rightHorn.rotation.z = -Math.PI / 6;
rightHorn.castShadow = true;
headGroup.add(rightHorn);
const eyeGeo = new THREE.SphereGeometry(0.3, 8, 8);
const leftEye = new THREE.Mesh(eyeGeo, eyeMaterial);
leftEye.position.set(-1.3, 0.5, 2);
headGroup.add(leftEye);
const rightEye = new THREE.Mesh(eyeGeo, eyeMaterial);
rightEye.position.set(1.3, 0.5, 2);
headGroup.add(rightEye);
return headGroup;
}
function createTail(start, control, end, animPhase, vData) {
const tailRootGroup = new THREE.Group();
tailRootGroup.position.copy(start);
const localControl = new THREE.Vector3().subVectors(control, start);
const localEnd = new THREE.Vector3().subVectors(end, start);
const count = vData.tailCount;
const segments = [];
const radiusStart = 3.5 * (vData.bodyScale[0]);
const radiusEnd = 0.5;
for (let i = 0; i <= count; i++) {
const t = i / count;
const r = radiusStart * (1 - t) + radiusEnd * t;
const segmentGroup = new THREE.Group();
const sphereGeo = new THREE.SphereGeometry(r, 16, 16);
const mesh = new THREE.Mesh(sphereGeo, goldMaterial);
mesh.castShadow = true;
segmentGroup.add(mesh);
if (i % 2 === 0 && i > 5) {
const spikeGeo = new THREE.ConeGeometry(r*0.4, r*1.5, 8);
const spike = new THREE.Mesh(spikeGeo, goldMaterial);
const dirX = (i % 4 === 0) ? 1 : -1;
const spikeDir = new THREE.Vector3(dirX, 0, 0);
spike.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), spikeDir);
spike.position.copy(spikeDir.multiplyScalar(r));
spike.castShadow = true;
segmentGroup.add(spike);
}
tailRootGroup.add(segmentGroup);
segments.push({ group: segmentGroup, t: t, r: r });
}
let tipObj = new THREE.Group();
if (vData.hasMace) {
const tipMaceGeo = new THREE.IcosahedronGeometry(2.5, 1);
const tipMace = new THREE.Mesh(tipMaceGeo, goldMaterial);
tipMace.castShadow = true;
tipObj.add(tipMace);
} else {
const tipSpikeGeo = new THREE.ConeGeometry(0.8, 4, 8);
const tipSpike = new THREE.Mesh(tipSpikeGeo, goldMaterial);
tipSpike.rotation.x = Math.PI / 2;
tipSpike.castShadow = true;
tipObj.add(tipSpike);
}
tailRootGroup.add(tipObj);
tailRootGroup.userData = {
baseLocalControl: localControl,
baseLocalEnd: localEnd,
segments: segments,
tipMace: tipObj,
speed: 0.8,
phase: animPhase
};
tailGroups.push(tailRootGroup);
bodyGroup.add(tailRootGroup);
}
function createLeg(xOffset, vData, isStanding) {
const legGroup = new THREE.Group();
if (isStanding) {
// 直立ポーズ(地面を踏みしめる)
legGroup.position.set(xOffset * vData.bodyScale[0], 2, 0);
const thighGeo = new THREE.SphereGeometry(3.0 * vData.bodyScale[0], 16, 16);
const thigh = new THREE.Mesh(thighGeo, goldMaterial);
thigh.scale.set(1, 1.5, 1);
thigh.position.set(0, -3, 0);
thigh.rotation.x = Math.PI / 6; // 少し前に出す
thigh.castShadow = true;
legGroup.add(thigh);
const shinGeo = new THREE.SphereGeometry(2.0 * vData.bodyScale[0], 16, 16);
const shin = new THREE.Mesh(shinGeo, goldMaterial);
shin.scale.set(1, 2.0, 1);
shin.position.set(0, -9, 2);
shin.rotation.x = -Math.PI / 12; // 地面に立てる
shin.castShadow = true;
legGroup.add(shin);
const footGroup = new THREE.Group();
footGroup.position.set(0, -13, 3);
const footGeo = new THREE.BoxGeometry(3, 1.5, 4);
const foot = new THREE.Mesh(footGeo, goldMaterial);
foot.position.y = 0.75;
foot.castShadow = true;
footGroup.add(foot);
for (let i = -1; i <= 1; i++) {
const clawGeo = new THREE.ConeGeometry(0.4, 2.0, 8);
clawGeo.translate(0, 1.0, 0);
const claw = new THREE.Mesh(clawGeo, goldMaterial);
claw.position.set(i * 1.2, 0.5, 2.0);
claw.rotation.x = Math.PI / 2;
claw.castShadow = true;
footGroup.add(claw);
}
legGroup.add(footGroup);
} else {
// 飛行ポーズ(後ろへ流す)
legGroup.position.set(xOffset * vData.bodyScale[0], 0, -5);
const thighGeo = new THREE.SphereGeometry(3.0 * vData.bodyScale[0], 16, 16);
const thigh = new THREE.Mesh(thighGeo, goldMaterial);
thigh.scale.set(1, 1.5, 1);
thigh.position.set(0, -2, 0);
thigh.rotation.x = Math.PI / 1.5;
thigh.castShadow = true;
legGroup.add(thigh);
const shinGeo = new THREE.SphereGeometry(2.0 * vData.bodyScale[0], 16, 16);
const shin = new THREE.Mesh(shinGeo, goldMaterial);
shin.scale.set(1, 2.0, 1);
shin.position.set(0, -6, -3);
shin.rotation.x = Math.PI / 1.8;
shin.castShadow = true;
legGroup.add(shin);
const footGroup = new THREE.Group();
footGroup.position.set(0, -9, -5);
footGroup.rotation.x = Math.PI / 2;
const footGeo = new THREE.BoxGeometry(2.5, 1, 3);
const foot = new THREE.Mesh(footGeo, goldMaterial);
foot.castShadow = true;
footGroup.add(foot);
for (let i = -1; i <= 1; i++) {
const clawGeo = new THREE.ConeGeometry(0.4, 2.0, 8);
clawGeo.translate(0, 1.0, 0);
const claw = new THREE.Mesh(clawGeo, goldMaterial);
claw.position.set(i * 1.0, 0, 1.5);
claw.rotation.x = Math.PI / 2;
claw.castShadow = true;
footGroup.add(claw);
}
legGroup.add(footGroup);
}
bodyGroup.add(legGroup);
}
function createWings(vData, isStanding) {
function createStraightBone(start, end, rStart, rEnd) {
const distance = start.distanceTo(end);
const geo = new THREE.CylinderGeometry(rEnd, rStart, distance, 8);
const mesh = new THREE.Mesh(geo, goldMaterial);
const mid = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
mesh.position.copy(mid);
mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), new THREE.Vector3().subVectors(end, start).normalize());
mesh.castShadow = true;
return mesh;
}
const addClaw = (group, posVector, baseVector, size) => {
const cGeo = new THREE.ConeGeometry(0.2 * size, 1.0 * size, 8);
const cMesh = new THREE.Mesh(cGeo, goldMaterial);
cMesh.position.copy(posVector);
const dir = new THREE.Vector3().subVectors(posVector, baseVector).normalize();
cMesh.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), dir);
cMesh.position.add(dir.multiplyScalar(0.5 * size));
cMesh.castShadow = true;
group.add(cMesh);
};
const membraneMat = new THREE.MeshStandardMaterial({
color: (vData.wingType === 'wyvern') ? 0x907000 : vData.color,
metalness: vData.metalness,
roughness: vData.roughness + 0.1,
side: THREE.DoubleSide
});
function buildWingSide(isLeft) {
const wingGroup = new THREE.Group();
const wingShape = new THREE.Shape();
let points = {};
if (vData.wingType === 'wyvern') {
// ハリウッド版
points.P0 = new THREE.Vector2(0, 0);
points.P1 = new THREE.Vector2(12, 15);
points.P2 = new THREE.Vector2(35, 35);
points.P3 = new THREE.Vector2(45, 10);
points.P4 = new THREE.Vector2(35, -5);
points.P5 = new THREE.Vector2(18, -12);
wingShape.moveTo(points.P0.x, points.P0.y);
wingShape.quadraticCurveTo(5, 10, points.P1.x, points.P1.y);
wingShape.quadraticCurveTo(20, 30, points.P2.x, points.P2.y);
wingShape.quadraticCurveTo(35, 25, points.P3.x, points.P3.y);
wingShape.quadraticCurveTo(35, 5, points.P4.x, points.P4.y);
wingShape.quadraticCurveTo(25, -5, points.P5.x, points.P5.y);
wingShape.quadraticCurveTo(8, -8, points.P0.x, points.P0.y);
} else if (vData.wingType === 'fan_showa') {
// 初代版(上辺が丸い扇型)
points.P0 = new THREE.Vector2(0, 0);
points.P1 = new THREE.Vector2(5, 25);
points.P2 = new THREE.Vector2(20, 30);
points.P3 = new THREE.Vector2(35, 25);
points.P4 = new THREE.Vector2(35, 5);
points.P5 = new THREE.Vector2(20, -5);
wingShape.moveTo(points.P0.x, points.P0.y);
wingShape.quadraticCurveTo(2, 10, points.P1.x, points.P1.y);
wingShape.quadraticCurveTo(10, 30, points.P2.x, points.P2.y);
wingShape.quadraticCurveTo(25, 30, points.P3.x, points.P3.y);
wingShape.quadraticCurveTo(38, 15, points.P4.x, points.P4.y);
wingShape.quadraticCurveTo(30, 0, points.P5.x, points.P5.y);
wingShape.quadraticCurveTo(10, -5, points.P0.x, points.P0.y);
} else {
// 平成版(直線的で鋭く高く広がる扇型)
points.P0 = new THREE.Vector2(0, 0);
points.P1 = new THREE.Vector2(10, 35);
points.P2 = new THREE.Vector2(28, 42);
points.P3 = new THREE.Vector2(45, 25);
points.P4 = new THREE.Vector2(38, 5);
points.P5 = new THREE.Vector2(20, -8);
wingShape.moveTo(points.P0.x, points.P0.y);
wingShape.quadraticCurveTo(5, 18, points.P1.x, points.P1.y);
wingShape.quadraticCurveTo(18, 40, points.P2.x, points.P2.y);
wingShape.quadraticCurveTo(38, 35, points.P3.x, points.P3.y);
wingShape.quadraticCurveTo(42, 15, points.P4.x, points.P4.y);
wingShape.quadraticCurveTo(30, -2, points.P5.x, points.P5.y);
wingShape.quadraticCurveTo(10, -4, points.P0.x, points.P0.y);
}
const wingGeo = new THREE.ShapeGeometry(wingShape);
const pos = wingGeo.attributes.position;
for (let i = 0; i < pos.count; i++) {
const x = pos.getX(i);
const y = pos.getY(i);
const refX = vData.wingType === 'wyvern' ? points.P1.x : 0;
const refY = vData.wingType === 'wyvern' ? points.P1.y : 0;
const dist = Math.sqrt((x-refX)**2 + (y-refY)**2);
pos.setZ(i, Math.sin(dist * 0.1) * 2.0);
}
wingGeo.computeVertexNormals();
const membrane = new THREE.Mesh(wingGeo, membraneMat);
membrane.castShadow = true;
wingGroup.add(membrane);
const getZ = (x, y) => {
const refX = vData.wingType === 'wyvern' ? points.P1.x : 0;
const refY = vData.wingType === 'wyvern' ? points.P1.y : 0;
return Math.sin(Math.sqrt((x-refX)**2 + (y-refY)**2) * 0.1) * 2.0;
};
const v0 = new THREE.Vector3(points.P0.x, points.P0.y, getZ(points.P0.x, points.P0.y));
const v1 = new THREE.Vector3(points.P1.x, points.P1.y, getZ(points.P1.x, points.P1.y));
const v2 = new THREE.Vector3(points.P2.x, points.P2.y, getZ(points.P2.x, points.P2.y));
const v3 = new THREE.Vector3(points.P3.x, points.P3.y, getZ(points.P3.x, points.P3.y));
const v4 = new THREE.Vector3(points.P4.x, points.P4.y, getZ(points.P4.x, points.P4.y));
const v5 = new THREE.Vector3(points.P5.x, points.P5.y, getZ(points.P5.x, points.P5.y));
if (vData.wingType === 'wyvern') {
wingGroup.add(createStraightBone(v0, v1, 1.2, 0.8));
wingGroup.add(createStraightBone(v1, v2, 0.8, 0.1));
wingGroup.add(createStraightBone(v1, v3, 0.6, 0.1));
wingGroup.add(createStraightBone(v1, v4, 0.6, 0.1));
wingGroup.add(createStraightBone(v1, v5, 0.6, 0.1));
addClaw(wingGroup, v1, v0, 2.5);
addClaw(wingGroup, v2, v1, 1.5);
addClaw(wingGroup, v3, v1, 1.0);
addClaw(wingGroup, v4, v1, 1.0);
addClaw(wingGroup, v5, v1, 1.0);
} else {
wingGroup.add(createStraightBone(v0, v1, 1.0, 0.2));
wingGroup.add(createStraightBone(v0, v2, 1.0, 0.2));
wingGroup.add(createStraightBone(v0, v3, 1.0, 0.2));
wingGroup.add(createStraightBone(v0, v4, 1.0, 0.2));
wingGroup.add(createStraightBone(v0, v5, 1.0, 0.2));
addClaw(wingGroup, v1, v0, 1.0);
addClaw(wingGroup, v2, v0, 1.0);
addClaw(wingGroup, v3, v0, 1.0);
addClaw(wingGroup, v4, v0, 1.0);
addClaw(wingGroup, v5, v0, 1.0);
}
if (isLeft) {
wingGroup.rotation.y = Math.PI;
wingGroup.scale.z = -1;
}
wingGroup.scale.multiplyScalar(vData.wingScale);
return wingGroup;
}
leftWingPivot = new THREE.Group();
// 翼が背中でクロスするのを防ぐため、付け根を外側に広げ、少し後ろへ離す
leftWingPivot.position.set(-8, isStanding ? 8 : 2, isStanding ? -6 : -2);
leftWingPivot.userData.baseRot = isStanding ? {
x: Math.PI / 12,
y: Math.PI / 8, // 後ろへの倒しを浅くして交差を回避
z: vData.wingType === 'fan' ? -Math.PI / 12 : -Math.PI / 10 // バンザイを抑え、真横に近く張り出すように修正
} : {
x: Math.PI / 12,
y: Math.PI / 6,
z: -Math.PI / 6
};
leftWingPivot.rotation.set(
leftWingPivot.userData.baseRot.x,
leftWingPivot.userData.baseRot.y,
leftWingPivot.userData.baseRot.z
);
const leftWingMesh = buildWingSide(true);
leftWingPivot.add(leftWingMesh);
bodyGroup.add(leftWingPivot);
rightWingPivot = new THREE.Group();
rightWingPivot.position.set(8, isStanding ? 8 : 2, isStanding ? -6 : -2); // 右翼も同様に広げる
rightWingPivot.userData.baseRot = isStanding ? {
x: Math.PI / 12,
y: -Math.PI / 8,
z: vData.wingType === 'fan' ? Math.PI / 12 : Math.PI / 10 // バンザイを抑え、真横に近く張り出すように修正
} : {
x: Math.PI / 12,
y: -Math.PI / 6,
z: Math.PI / 6
};
rightWingPivot.rotation.set(
rightWingPivot.userData.baseRot.x,
rightWingPivot.userData.baseRot.y,
rightWingPivot.userData.baseRot.z
);
const rightWingMesh = buildWingSide(false);
rightWingPivot.add(rightWingMesh);
bodyGroup.add(rightWingPivot);
}
// 簡易的なゴジラを生成する関数
function createGodzilla() {
const godzillaGroup = new THREE.Group();
// ゴジラ特有の黒灰色の皮膚と、白っぽい背びれのマテリアル
const gMat = new THREE.MeshStandardMaterial({ color: 0x2a2a2a, roughness: 0.9, metalness: 0.1 });
const spineMat = new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.8, metalness: 0.2 });
// 胴体 (前傾姿勢) - CapsuleGeometryの代わりにSphereのスケールを使用
const bodyGeo = new THREE.SphereGeometry(5, 16, 16);
const body = new THREE.Mesh(bodyGeo, gMat);
body.scale.set(1, 2.2, 1); // カプセル形状をシミュレート
body.position.set(0, 12, 0);
body.rotation.x = Math.PI / 6;
body.castShadow = true;
godzillaGroup.add(body);
// 頭部
const headGeo = new THREE.BoxGeometry(4, 4, 6);
const head = new THREE.Mesh(headGeo, gMat);
head.position.set(0, 20, 4);
head.castShadow = true;
godzillaGroup.add(head);
// 腕 (小さめ) - Sphereのスケールを使用
const armGeo = new THREE.SphereGeometry(1.5, 8, 8);
const armL = new THREE.Mesh(armGeo, gMat);
armL.scale.set(1, 2.5, 1);
armL.position.set(-4, 15, 3);
armL.rotation.x = Math.PI / 2;
armL.rotation.z = Math.PI / 6;
armL.castShadow = true;
godzillaGroup.add(armL);
const armR = new THREE.Mesh(armGeo, gMat);
armR.scale.set(1, 2.5, 1);
armR.position.set(4, 15, 3);
armR.rotation.x = Math.PI / 2;
armR.rotation.z = -Math.PI / 6;
armR.castShadow = true;
godzillaGroup.add(armR);
// 太い足 - Sphereのスケールを使用
const legGeo = new THREE.SphereGeometry(3.5, 16, 16);
const legL = new THREE.Mesh(legGeo, gMat);
legL.scale.set(1, 1.8, 1);
legL.position.set(-4, 5, -2);
legL.castShadow = true;
godzillaGroup.add(legL);
const legR = new THREE.Mesh(legGeo, gMat);
legR.scale.set(1, 1.8, 1);
legR.position.set(4, 5, -2);
legR.castShadow = true;
godzillaGroup.add(legR);
// 長く太い尻尾 (地面を這うように)
const tailCount = 20;
for(let i = 0; i < tailCount; i++) {
const r = 4.5 * (1 - i / tailCount);
const tGeo = new THREE.SphereGeometry(r, 16, 16);
const tMesh = new THREE.Mesh(tGeo, gMat);
// 後ろへ伸ばしつつ、地面に着いたら這わせる
const yPos = 8 - i * 1.5;
tMesh.position.set(0, Math.max(yPos, r), -6 - i * 3);
tMesh.castShadow = true;
godzillaGroup.add(tMesh);
// 尻尾にも背びれを付ける
if (i < 12 && i % 2 === 0) {
const spineGeo = new THREE.ConeGeometry(r*0.4, r*1.2, 4);
const spine = new THREE.Mesh(spineGeo, spineMat);
spine.position.set(0, tMesh.position.y + r*0.8, tMesh.position.z);
spine.rotation.x = -Math.PI / 4;
spine.castShadow = true;
godzillaGroup.add(spine);
}
}
// 背びれ (背中)
for(let i = 0; i < 6; i++) {
const size = 1.5 - Math.abs(i - 2) * 0.2; // 中央を大きく
const spineGeo = new THREE.ConeGeometry(size * 1.5, size * 4, 4);
const spine = new THREE.Mesh(spineGeo, spineMat);
spine.position.set(0, 19 - i * 2.5, -i * 2.0);
spine.rotation.x = -Math.PI / 3;
spine.castShadow = true;
godzillaGroup.add(spine);
}
// 全体のスケールを少し大きくして迫力を出す
godzillaGroup.scale.set(1.5, 1.5, 1.5);
return godzillaGroup;
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
const elapsedTime = clock.getElapsedTime();
const vData = VERSIONS[currentVersion];
const isStanding = vData.pose === 'standing';
if (leftWingPivot && rightWingPivot && leftWingPivot.userData.baseRot) {
const flapSpeed = isStanding ? 1.0 : 2.5; // 直立時はゆっくり威嚇
const flapPhase = Math.sin(elapsedTime * flapSpeed);
const flapTwist = Math.cos(elapsedTime * flapSpeed);
const flapRangeZ = isStanding ? 0.15 : 0.4;
const flapRangeY = isStanding ? 0.05 : 0.15;
const flapRangeX = isStanding ? 0.05 : 0.15;
leftWingPivot.rotation.z = leftWingPivot.userData.baseRot.z - flapPhase * flapRangeZ;
leftWingPivot.rotation.y = leftWingPivot.userData.baseRot.y + flapPhase * flapRangeY;
leftWingPivot.rotation.x = leftWingPivot.userData.baseRot.x + flapTwist * flapRangeX;
rightWingPivot.rotation.z = rightWingPivot.userData.baseRot.z + flapPhase * flapRangeZ;
rightWingPivot.rotation.y = rightWingPivot.userData.baseRot.y - flapPhase * flapRangeY;
rightWingPivot.rotation.x = rightWingPivot.userData.baseRot.x + flapTwist * flapRangeX;
}
if (neckGroups && neckGroups.length > 0) {
neckGroups.forEach(neck => {
const data = neck.userData;
const time = elapsedTime * data.speed + data.phase;
const swayX = Math.sin(time * 0.8) * 3.0;
const swayY = Math.cos(time * 1.1) * 2.0;
const swayZ = Math.sin(time * 0.9) * 2.0;
const currentControl = data.baseLocalControl.clone().add(new THREE.Vector3(swayX, swayY, swayZ));
const headSwayX = Math.sin(time * 1.2) * 4.0;
const headSwayY = Math.cos(time * 1.5) * 3.0;
const headSwayZ = Math.sin(time * 1.3) * 4.0;
const currentEnd = data.baseLocalEnd.clone().add(new THREE.Vector3(headSwayX, headSwayY, headSwayZ));
const curve = new THREE.QuadraticBezierCurve3(new THREE.Vector3(0,0,0), currentControl, currentEnd);
const waveFreq = 10.0;
const waveAmp = 1.5;
const waveSpeed = 6.0;
const up = new THREE.Vector3(0, 1, 0);
const points = [];
const count = data.segments.length - 1;
for (let i = 0; i <= count; i++) {
const t = data.segments[i].t;
const basePos = curve.getPoint(t);
const tangent = curve.getTangent(t).normalize();
let normal = new THREE.Vector3().crossVectors(tangent, up).normalize();
if (normal.lengthSq() < 0.001) normal.set(1, 0, 0);
const wave = Math.sin(time * waveSpeed - t * waveFreq);
const offset = normal.multiplyScalar(wave * waveAmp * Math.pow(t, 1.2));
points.push(basePos.add(offset));
}
for (let i = 0; i <= count; i++) {
const seg = data.segments[i];
seg.mesh.position.copy(points[i]);
if (i < count) {
const dir = new THREE.Vector3().subVectors(points[i+1], points[i]).normalize();
seg.mesh.quaternion.setFromUnitVectors(up, dir);
} else {
seg.mesh.quaternion.copy(data.segments[i-1].mesh.quaternion);
}
}
data.headGroup.position.copy(points[count]);
const lastTangent = new THREE.Vector3().subVectors(points[count], points[count-1]).normalize();
const lookTargetLocal = points[count].clone().add(lastTangent);
const lookTargetWorld = neck.localToWorld(lookTargetLocal);
data.headGroup.lookAt(lookTargetWorld);
data.headGroup.rotateZ(Math.sin(time * 2.0) * 0.1);
data.headGroup.rotateX(Math.sin(time * 3.0) * 0.1);
});
}
if (tailGroups && tailGroups.length > 0) {
tailGroups.forEach(tail => {
const data = tail.userData;
const time = elapsedTime * data.speed + data.phase;
const swayX = Math.sin(time * 0.7) * 4.0;
const swayY = Math.cos(time * 0.9) * 2.0;
const currentControl = data.baseLocalControl.clone().add(new THREE.Vector3(swayX, swayY, 0));
const endSwayX = Math.sin(time * 1.1) * 8.0;
const endSwayY = Math.cos(time * 1.3) * 4.0;
const currentEnd = data.baseLocalEnd.clone().add(new THREE.Vector3(endSwayX, endSwayY, 0));
const curve = new THREE.QuadraticBezierCurve3(new THREE.Vector3(0,0,0), currentControl, currentEnd);
const waveFreq = 8.0;
const waveAmp = 2.5;
const waveSpeed = 5.0;
const up = new THREE.Vector3(0, 1, 0);
const points = [];
const count = data.segments.length - 1;
for (let i = 0; i <= count; i++) {
const t = i / count;
const basePos = curve.getPoint(t);
const tangent = curve.getTangent(t).normalize();
let normal = new THREE.Vector3().crossVectors(tangent, up).normalize();
if (normal.lengthSq() < 0.001) normal.set(1, 0, 0);
const wave = Math.sin(time * waveSpeed - t * waveFreq);
const offset = normal.multiplyScalar(wave * waveAmp * Math.pow(t, 1.5));
points.push(basePos.add(offset));
}
for (let i = 0; i <= count; i++) {
const seg = data.segments[i];
seg.group.position.copy(points[i]);
if (i < count) {
const dir = new THREE.Vector3().subVectors(points[i+1], points[i]).normalize();
const right = new THREE.Vector3().crossVectors(up, dir).normalize();
const actualUp = new THREE.Vector3().crossVectors(dir, right).normalize();
const matrix = new THREE.Matrix4().makeBasis(right, actualUp, dir);
seg.group.quaternion.setFromRotationMatrix(matrix);
} else {
seg.group.quaternion.copy(data.segments[i-1].group.quaternion);
}
}
data.tipMace.position.copy(points[count]);
data.tipMace.quaternion.copy(data.segments[count].group.quaternion);
});
}
if (bodyGroup) {
if (isStanding) {
// 直立時はソフビフィギュアのように浮遊しない
bodyGroup.position.y = -5;
bodyGroup.rotation.z = 0;
} else {
bodyGroup.position.y = Math.sin(elapsedTime * 1.5) * 2.0;
bodyGroup.rotation.z = Math.sin(elapsedTime * 0.8) * 0.05;
}
}
controls.update();
renderer.render(scene, camera);
}
</script>
</body>
</html>
キングギドラが初代、ハリウッド、平成、3匹がいます。 どれもかっこいいのが特徴です!