<!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;
padding: 0;
overflow: hidden;
background-color: #050000;
font-family: sans-serif;
}
#canvas-container {
width: 100vw;
height: 100vh;
display: block;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #ff3333;
font-size: 24px;
font-weight: bold;
text-shadow: 0 0 10px #ff0000;
pointer-events: none;
transition: opacity 0.5s;
z-index: 10;
}
#ui {
position: absolute;
bottom: 20px;
left: 20px;
color: rgba(255, 255, 255, 0.7);
text-shadow: 1px 1px 2px black;
z-index: 10;
}
#ui h2 { margin: 0 0 5px 0; font-size: 20px; }
#ui p { margin: 0 0 10px 0; font-size: 12px; }
#form-selector {
background: rgba(20, 0, 0, 0.8);
color: #ffaa55;
border: 1px solid #ff3333;
padding: 8px 12px;
font-size: 16px;
border-radius: 4px;
outline: none;
cursor: pointer;
box-shadow: 0 0 10px rgba(255, 0, 0, 0.3);
}
#form-selector:hover { background: rgba(40, 0, 0, 0.9); }
</style>
</head>
<body>
<div id="loading">Generating Monster...</div>
<div id="canvas-container"></div>
<div id="ui">
<h2>デストロイア (進化系統)</h2>
<p>ドラッグ:回転 / スクロール:縮尺</p>
<select id="form-selector" onchange="changeForm(this.value)">
<option value="perfect">完全体 (Perfect Form)</option>
<option value="flying">飛行体 (Flying Form)</option>
<option value="aggregate">集合体 (Aggregate Form)</option>
<option value="micro">幼体/微小体 (Micro Form)</option>
</select>
</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.addEventListener('DOMContentLoaded', () => {
// --- 1. 基本設定 ---
const container = document.getElementById('canvas-container');
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x0a0000, 0.012);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 15, 45);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.target.set(0, 5, 0);
controls.maxPolarAngle = Math.PI / 2 + 0.1;
// --- 2. ライティング ---
scene.add(new THREE.AmbientLight(0x402020, 1.2));
const dirLight = new THREE.DirectionalLight(0xffaaaa, 1.5);
dirLight.position.set(15, 25, 10);
dirLight.castShadow = true;
dirLight.shadow.camera.top = 25; dirLight.shadow.camera.bottom = -25;
dirLight.shadow.camera.left = -25; dirLight.shadow.camera.right = 25;
scene.add(dirLight);
const pointLight = new THREE.PointLight(0xff3300, 2.5, 60);
pointLight.position.set(0, -5, 10);
scene.add(pointLight);
// --- 3. マテリアル共通定義 ---
const colors = {
darkRed: 0x2a0505, armorRed: 0x4a0a0a, wingMembrane: 0xaa2222,
bellyBone: 0xd2b48c, boneColor: 0xeeddcc, eyeYellow: 0xffcc00
};
const matBody = new THREE.MeshStandardMaterial({ color: colors.darkRed, roughness: 0.9, flatShading: true });
const matArmor = new THREE.MeshStandardMaterial({ color: colors.armorRed, roughness: 1.0, flatShading: true });
const matWing = new THREE.MeshStandardMaterial({ color: colors.wingMembrane, roughness: 0.6, side: THREE.DoubleSide, flatShading: true });
const matBelly = new THREE.MeshStandardMaterial({ color: colors.bellyBone, roughness: 0.8, flatShading: true });
const matBone = new THREE.MeshStandardMaterial({ color: colors.boneColor, roughness: 0.5, flatShading: true });
const matEye = new THREE.MeshBasicMaterial({ color: colors.eyeYellow });
// 状態管理
let currentModel = null;
let updateActions = []; // アニメーション関数配列
let time = 0;
// --- 4. 共通パーツ生成関数 ---
// 【頭部 (完全体・集合体・飛行体共通)】
function buildHead(hasLight = false) {
const headGroup = new THREE.Group();
const skull = new THREE.Mesh(new THREE.SphereGeometry(1.6, 8, 8), matArmor);
skull.position.set(0, 0.5, 0); skull.castShadow = true; headGroup.add(skull);
const snout = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 1.5, 2.5, 4), matArmor);
snout.rotation.set(Math.PI / 2, Math.PI / 4, 0); snout.position.set(0, 0.2, 1.6); snout.castShadow = true; headGroup.add(snout);
const jaw = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 1.4, 2.5, 4), matArmor);
jaw.rotation.set(Math.PI / 2 + Math.PI / 8, Math.PI / 4, 0); jaw.position.set(0, -0.6, 1.4); jaw.castShadow = true; headGroup.add(jaw);
const mainHorn = new THREE.Mesh(new THREE.ConeGeometry(0.5, 4.0, 4), matBone);
mainHorn.position.set(0, 1.8, 1.2); mainHorn.rotation.x = Math.PI / 6; mainHorn.castShadow = true; headGroup.add(mainHorn);
for (let i = -1; i <= 1; i += 2) {
const upperFrill = new THREE.Mesh(new THREE.ConeGeometry(0.8, 3.5, 4), matArmor);
upperFrill.position.set(i * 1.4, 1.8, -0.8); upperFrill.rotation.set(-Math.PI / 6, 0, i * -Math.PI / 5); headGroup.add(upperFrill);
const midFrill = new THREE.Mesh(new THREE.ConeGeometry(0.7, 2.8, 4), matArmor);
midFrill.position.set(i * 1.8, 0.5, -0.8); midFrill.rotation.set(-Math.PI / 6, 0, i * -Math.PI / 2.5); headGroup.add(midFrill);
const cheekTusk = new THREE.Mesh(new THREE.ConeGeometry(0.2, 1.5, 4), matBone);
cheekTusk.position.set(i * 1.4, -0.2, 0.8); cheekTusk.rotation.set(Math.PI / 6, 0, i * -Math.PI / 2.5); headGroup.add(cheekTusk);
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.2, 8, 8), matEye);
eye.position.set(i * 0.7, 0.8, 1.6); headGroup.add(eye);
const brow = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.4, 0.6), matArmor);
brow.position.set(i * 0.7, 1.0, 1.7); brow.rotation.set(0, i * Math.PI / 6, i * -Math.PI / 6); headGroup.add(brow);
}
for(let i=-1; i<=1; i+=2) {
for(let j=0; j<3; j++) {
const fangT = new THREE.Mesh(new THREE.ConeGeometry(0.1, 0.5, 4), matBone);
fangT.position.set(i * (0.3 + j*0.15), -0.2, 2.5 - j*0.4); fangT.rotation.x = Math.PI; headGroup.add(fangT);
const fangB = new THREE.Mesh(new THREE.ConeGeometry(0.1, 0.5, 4), matBone);
fangB.position.set(i * (0.2 + j*0.15), 0.3, 1.2 - j*0.4); jaw.add(fangB);
}
}
let bLight = null;
if (hasLight) {
bLight = new THREE.PointLight(colors.eyeYellow, 0, 5);
bLight.position.set(0, -0.5, 1.5); headGroup.add(bLight);
}
return { root: headGroup, light: bLight };
}
// 【尻尾 (全形態共通ベース)】
function buildTail(count, radiusStart, length) {
const tailRoot = new THREE.Group();
let currentParent = tailRoot;
const segments = [];
for (let i = 0; i < count; i++) {
const segGroup = new THREE.Group();
const radius = radiusStart * (1 - i / count) + 0.3;
const segment = new THREE.Mesh(new THREE.CylinderGeometry(radius + 0.2, radius, length, 8), i % 2 === 0 ? matArmor : matBody);
segment.rotation.x = Math.PI / 2; segment.position.z = -length / 2; segment.castShadow = true;
if (i < count - 1) {
const spike = new THREE.Mesh(new THREE.ConeGeometry(radius * 0.4, radius * 1.5, 4), matArmor);
spike.position.set(0, radius, -length / 2); segGroup.add(spike);
if(i % 2 === 0) {
for(let j=-1; j<=1; j+=2) {
const sideSpike = new THREE.Mesh(new THREE.ConeGeometry(radius*0.2, radius*1.0, 4), matArmor);
sideSpike.position.set(j * radius, 0, -length / 2); sideSpike.rotation.z = j * -Math.PI / 2; segGroup.add(sideSpike);
}
}
}
segGroup.add(segment);
if (i === count - 1) {
const bladeGeo = new THREE.ConeGeometry(0.8, 4, 4);
const mainBlade = new THREE.Mesh(bladeGeo, matArmor);
mainBlade.position.set(0, 0, -length - 1.5); mainBlade.rotation.x = Math.PI / 2; mainBlade.scale.z = 0.3; segGroup.add(mainBlade);
for(let j=-1; j<=1; j+=2) {
const sideBlade = new THREE.Mesh(new THREE.ConeGeometry(0.5, 3.5, 4), matArmor);
sideBlade.position.set(j * 0.8, 0, -length - 1); sideBlade.rotation.set(Math.PI / 2, j * Math.PI / 6, 0); sideBlade.scale.z = 0.3; segGroup.add(sideBlade);
}
}
segGroup.position.z = i === 0 ? 0 : -length + 0.3;
currentParent.add(segGroup); segments.push(segGroup); currentParent = segGroup;
}
return { root: tailRoot, segments: segments };
}
// 【翼 (完全体・飛行体共通)】
function buildWings(scale = 1.0) {
const wingsRoot = new THREE.Group();
const wingObjects = [];
function addBone(parent, sx, sy, ex, ey, thick, hasClaw) {
const dx = ex - sx, dy = ey - sy;
const len = Math.sqrt(dx*dx + dy*dy), ang = Math.atan2(dy, dx);
const bone = new THREE.Mesh(new THREE.CylinderGeometry(thick, thick * 0.4, len, 6), matArmor);
bone.position.set(sx + dx/2, sy + dy/2, 0); bone.rotation.z = ang - Math.PI/2; parent.add(bone);
if (hasClaw) {
const claw = new THREE.Mesh(new THREE.ConeGeometry(0.3, 1.8, 4), matBone);
claw.position.set(ex + Math.cos(ang)*0.8, ey + Math.sin(ang)*0.8, 0); claw.rotation.z = ang - Math.PI/2; parent.add(claw);
}
}
for(let sign of [-1, 1]) {
const wingGroup = new THREE.Group();
// 主翼
const mwRoot = new THREE.Group(); mwRoot.position.set(sign * 2.5, 7, -2);
const mwShape = new THREE.Shape();
mwShape.moveTo(0, 0); mwShape.lineTo(sign * 5, 4); mwShape.lineTo(sign * 14, 12);
mwShape.quadraticCurveTo(sign * 13, 6, sign * 17, 3); mwShape.quadraticCurveTo(sign * 12, 0, sign * 12, -4);
mwShape.quadraticCurveTo(sign * 7, -2, sign * 5, -2); mwShape.lineTo(0, 0);
const mwMesh = new THREE.Mesh(new THREE.ExtrudeGeometry(mwShape, { depth: 0.05, bevelEnabled: false }), matWing);
mwMesh.position.z = -0.025; mwMesh.castShadow = true; mwRoot.add(mwMesh);
addBone(mwRoot, 0, 0, sign*5, 4, 0.5, false); addBone(mwRoot, sign*5, 4, sign*14, 12, 0.35, true);
addBone(mwRoot, sign*5, 4, sign*17, 3, 0.3, true); addBone(mwRoot, sign*5, 4, sign*12, -4, 0.3, true); addBone(mwRoot, sign*5, 4, sign*5, -2, 0.3, false);
wingGroup.add(mwRoot); wingObjects.push({ root: mwRoot, isLeft: sign === -1, speed: 1.0 });
// 副翼
const swRoot = new THREE.Group(); swRoot.position.set(sign * 2.5, 3, -3);
const swShape = new THREE.Shape();
swShape.moveTo(0, 0); swShape.lineTo(sign * 3, 1); swShape.lineTo(sign * 8, 3);
swShape.quadraticCurveTo(sign * 7, 0, sign * 9, -2); swShape.quadraticCurveTo(sign * 5, -2, sign * 4, -4); swShape.lineTo(0, 0);
const swMesh = new THREE.Mesh(new THREE.ExtrudeGeometry(swShape, { depth: 0.05, bevelEnabled: false }), matWing);
swMesh.position.z = -0.025; swMesh.castShadow = true; swRoot.add(swMesh);
addBone(swRoot, 0, 0, sign*3, 1, 0.3, false); addBone(swRoot, sign*3, 1, sign*8, 3, 0.2, true);
addBone(swRoot, sign*3, 1, sign*9, -2, 0.2, false); addBone(swRoot, sign*3, 1, sign*4, -4, 0.2, false);
wingGroup.add(swRoot); wingObjects.push({ root: swRoot, isLeft: sign === -1, speed: 1.2 });
wingsRoot.add(wingGroup);
}
wingsRoot.scale.set(scale, scale, scale);
return { root: wingsRoot, wings: wingObjects };
}
// --- 5. 形態別構築関数 ---
// 【幼体/微小体】
function createMicroForm() {
const swarmGroup = new THREE.Group();
const animators = [];
// 1体の幼体を生成するローカル関数
function buildSingleMicro(x, z, timeOffset) {
const group = new THREE.Group();
group.position.set(x, 1.0, z);
// 胴体 (縦長で直立姿勢)
const bodyGeo = new THREE.CylinderGeometry(1.8, 2.2, 4.5, 8);
const body = new THREE.Mesh(bodyGeo, matBody);
body.position.set(0, 2.5, 0);
body.rotation.x = Math.PI / 12; // 少し胸を張る
body.castShadow = true;
group.add(body);
// 背中と側面のトゲ
for(let i=0; i<4; i++) {
const backSpike = new THREE.Mesh(new THREE.ConeGeometry(0.6, 2.0, 4), matArmor);
backSpike.position.set(0, 4 - i * 1.2, -1.5);
backSpike.rotation.x = -Math.PI / 4;
group.add(backSpike);
for (let sign of [-1, 1]) {
const sideSpike = new THREE.Mesh(new THREE.ConeGeometry(0.5, 1.5, 4), matArmor);
sideSpike.position.set(sign * 1.8, 3.5 - i * 1.0, -0.5);
sideSpike.rotation.z = sign * -Math.PI / 3;
sideSpike.rotation.x = -Math.PI / 6;
group.add(sideSpike);
}
}
// 胸部(花びらパーツ)
const bellyGroup = new THREE.Group();
bellyGroup.position.set(0, 2.2, 1.8);
bellyGroup.rotation.x = Math.PI / 8;
const bellyCenterMesh = new THREE.Mesh(new THREE.SphereGeometry(1.2, 8, 8), matBelly);
bellyCenterMesh.scale.set(1, 1, 0.4);
bellyGroup.add(bellyCenterMesh);
const petalGeo = new THREE.ConeGeometry(0.6, 2.0, 4);
petalGeo.translate(0, 1.0, 0);
for(let i=0; i<8; i++) {
const petal = new THREE.Mesh(petalGeo, matBelly);
petal.rotation.set(-Math.PI / 12, 0, (Math.PI * 2 / 8) * i);
petal.scale.z = 0.3;
bellyGroup.add(petal);
}
group.add(bellyGroup);
// 頭部
const headGroup = new THREE.Group();
headGroup.position.set(0, 5.0, 0.5);
const skull = new THREE.Mesh(new THREE.SphereGeometry(1.4, 8, 8), matArmor);
headGroup.add(skull);
const snout = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 1.2, 1.8, 4), matArmor);
snout.rotation.set(Math.PI / 2, Math.PI / 4, 0);
snout.position.set(0, -0.2, 1.2);
headGroup.add(snout);
// 王冠フリル (幼体用)
for (let i = -1; i <= 1; i += 2) {
const upperFrill = new THREE.Mesh(new THREE.ConeGeometry(0.6, 2.5, 4), matArmor);
upperFrill.position.set(i * 1.2, 1.0, -0.5);
upperFrill.rotation.set(-Math.PI / 6, 0, i * -Math.PI / 4);
headGroup.add(upperFrill);
const midFrill = new THREE.Mesh(new THREE.ConeGeometry(0.5, 1.8, 4), matArmor);
midFrill.position.set(i * 1.4, 0.2, -0.5);
midFrill.rotation.set(-Math.PI / 6, 0, i * -Math.PI / 2);
headGroup.add(midFrill);
}
// 目 (白く発光)
const matWhiteEye = new THREE.MeshBasicMaterial({ color: 0xffffff });
for (let i = -1; i <= 1; i += 2) {
const eye = new THREE.Mesh(new THREE.SphereGeometry(0.25, 8, 8), matWhiteEye);
eye.position.set(i * 0.6, 0.3, 1.2);
headGroup.add(eye);
}
// 口元の白い牙
for (let i = -1; i <= 1; i += 2) {
const fang = new THREE.Mesh(new THREE.ConeGeometry(0.15, 0.8, 4), matBone);
fang.position.set(i * 0.4, -0.6, 1.8);
fang.rotation.x = Math.PI; // 下向き
headGroup.add(fang);
}
group.add(headGroup);
// 下部の2本の牙(腹部の下)
for (let i = -1; i <= 1; i += 2) {
const bottomFang = new THREE.Mesh(new THREE.ConeGeometry(0.4, 2.0, 4), matArmor);
bottomFang.position.set(i * 0.8, 0.5, 1.5);
bottomFang.rotation.set(Math.PI / 6, 0, i * Math.PI / 8);
group.add(bottomFang);
}
// 多脚 (カニ/クモのような脚)
const legs = [];
for (let i = 0; i < 3; i++) {
for (let sign of [-1, 1]) {
const leg = new THREE.Group();
leg.position.set(sign * 1.8, 1.8, 1.5 - i * 1.5);
const upper = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.4, 3.0, 8), matArmor);
upper.position.set(sign * 1.2, 0.5, 0);
upper.rotation.z = sign * -Math.PI / 3;
leg.add(upper);
const lower = new THREE.Mesh(new THREE.ConeGeometry(0.4, 3.0, 4), matArmor);
lower.position.set(sign * 2.5, -0.8, 0);
lower.rotation.z = sign * Math.PI / 6;
leg.add(lower);
const legSpike = new THREE.Mesh(new THREE.ConeGeometry(0.3, 1.0, 4), matArmor);
legSpike.position.set(sign * 1.2, 1.8, 0);
legSpike.rotation.z = sign * -Math.PI / 6;
leg.add(legSpike);
group.add(leg);
legs.push({ root: leg, offset: i + sign });
}
}
// 尻尾
const tailObj = buildTail(8, 1.0, 1.5);
tailObj.root.position.set(0, 1.0, -1.0);
group.add(tailObj.root);
// この個体専用のアニメーション関数
const animateFn = () => {
const t = time + timeOffset; // 個体ごとにタイミングをずらす
group.position.y = 1.0 + Math.sin(t * 3) * 0.1; // 呼吸
legs.forEach(leg => {
leg.root.rotation.x = Math.sin(t * 8 + leg.offset) * 0.1; // ワシャワシャ
leg.root.rotation.z = Math.cos(t * 8 + leg.offset) * 0.05;
});
tailObj.segments.forEach((seg, i) => {
seg.rotation.y = Math.sin(t * 4 - i * 0.5) * 0.2;
});
};
return { root: group, animate: animateFn };
}
// 群れの配置座標(中心、周り、少し離れた場所など)
const positions = [
[0, 0], [-6, 3], [6, 3], [-4, -6], [4, -6], [-10, -1], [10, -1], [0, 8], [0, -10]
];
// 配置座標をもとに複数体を生成して並べる
positions.forEach((pos) => {
// 少しだけランダムに位置を散らす
const x = pos[0] + (Math.random() - 0.5) * 2.0;
const z = pos[1] + (Math.random() - 0.5) * 2.0;
// アニメーションの開始タイミングをランダムにずらす
const timeOffset = Math.random() * 10;
const mf = buildSingleMicro(x, z, timeOffset);
// 個体ごとに少し向いている方向をバラけさせる
mf.root.rotation.y = (Math.random() - 0.5) * 0.8;
swarmGroup.add(mf.root);
animators.push(mf.animate); // 更新リストに追加
});
// ループ更新時に全個体のアニメーションを実行する
updateActions.push(() => {
animators.forEach(anim => anim());
});
return swarmGroup;
}
// 【集合体】
function createAggregateForm() {
const group = new THREE.Group();
group.position.y = 3.5; // 脚が長くなるため重心を高く
// 胴体 (前傾姿勢)
const bodyGeo = new THREE.CylinderGeometry(2.5, 4, 8, 8);
const body = new THREE.Mesh(bodyGeo, matBody);
body.rotation.x = Math.PI / 2 + Math.PI / 6; // 前傾
body.position.set(0, 3, 0);
body.castShadow = true;
group.add(body);
// 腹部 (オレンジ色の花びらパーツ)
const bellyGroup = new THREE.Group();
bellyGroup.position.set(0, 1.0, 3.5);
bellyGroup.rotation.x = Math.PI / 6;
const bellyCenterMesh = new THREE.Mesh(new THREE.SphereGeometry(1.5, 8, 8), matBelly);
bellyCenterMesh.scale.set(1, 1, 0.4);
bellyGroup.add(bellyCenterMesh);
const petalGeo = new THREE.ConeGeometry(0.7, 2.5, 4);
petalGeo.translate(0, 1.2, 0);
for(let i=0; i<8; i++) {
const petal = new THREE.Mesh(petalGeo, matBelly);
petal.rotation.set(-Math.PI / 12, 0, (Math.PI * 2 / 8) * i);
petal.scale.z = 0.3;
bellyGroup.add(petal);
}
group.add(bellyGroup);
// 長い首 (複数の関節で構築)
const neckSegments = [];
const neckRoot = new THREE.Group();
neckRoot.position.set(0, 4.5, 2.5);
neckRoot.rotation.x = -Math.PI / 4; // 上前方へ向かう
group.add(neckRoot);
let currentNeckParent = neckRoot;
const neckCount = 4;
for(let i=0; i<neckCount; i++) {
const neckSeg = new THREE.Group();
const radius = 1.8 - i * 0.15;
const len = 1.5;
const mesh = new THREE.Mesh(new THREE.CylinderGeometry(radius - 0.1, radius, len, 8), matArmor);
mesh.position.y = len / 2;
mesh.castShadow = true;
neckSeg.add(mesh);
// 首の背中のトゲ
const spike = new THREE.Mesh(new THREE.ConeGeometry(0.4, 1.5, 4), matArmor);
spike.position.set(0, len / 2, -radius);
spike.rotation.x = -Math.PI / 4;
neckSeg.add(spike);
neckSeg.position.y = i === 0 ? 0 : 1.4;
currentNeckParent.add(neckSeg);
neckSegments.push(neckSeg);
currentNeckParent = neckSeg;
}
// 頭部 (首の先に接続)
const headObj = buildHead(true);
headObj.root.position.set(0, 1.5, 0.5);
headObj.root.rotation.x = Math.PI / 4 + Math.PI / 8; // 正面下を向くように調整
currentNeckParent.add(headObj.root);
// カマ(前肢)- クロスしないように外側に開いて構える
const arms = [];
for (let sign of [-1, 1]) {
const arm = new THREE.Group();
arm.position.set(sign * 2.5, 4.0, 3.5); // 胴体からの発生位置
// 腕の根元(上腕)
const upper = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.6, 5, 8), matArmor);
upper.position.set(sign * 2.0, 1.0, 1.5); // 外側・斜め前・上へ向かって伸びる
upper.rotation.set(Math.PI / 4, sign * -Math.PI / 8, sign * -Math.PI / 3);
arm.add(upper);
// 鎌の刃部分
const bladeGroup = new THREE.Group();
bladeGroup.position.set(sign * 4.0, 2.0, 3.0); // 腕の先端に接続
// 刃が前方を向きつつ、下に向かって鋭くカーブする角度
bladeGroup.rotation.set(Math.PI / 6, sign * Math.PI / 8, sign * Math.PI / 4);
const bladeGeo = new THREE.ConeGeometry(0.6, 6, 4); // 刃を少し長く
const blade = new THREE.Mesh(bladeGeo, matBone);
blade.position.set(0, -3, 0); // 中心点を上にずらして吊り下げる
blade.rotation.x = Math.PI; // 下向き
bladeGroup.add(blade);
arm.add(bladeGroup);
group.add(arm);
arms.push({ root: arm, sign: sign, blade: bladeGroup });
}
// クモのような多脚 (長く、一度上に上がってから下に降りる関節)
const legs = [];
const legPositions = [
{ z: 2, yRot: Math.PI / 6 }, // 前脚
{ z: -1, yRot: 0 }, // 中脚
{ z: -4, yRot: -Math.PI / 6 } // 後脚
];
legPositions.forEach((pos, i) => {
for (let sign of [-1, 1]) {
const legRoot = new THREE.Group();
legRoot.position.set(sign * 2.5, 3, pos.z);
legRoot.rotation.y = sign * pos.yRot;
// 太もも (上に持ち上がる)
const thighLen = 4.5;
const thigh = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.6, thighLen, 8), matArmor);
thigh.position.set(sign * (thighLen/2) * Math.cos(Math.PI/6), (thighLen/2) * Math.sin(Math.PI/6), 0);
thigh.rotation.z = sign * -Math.PI / 3; // 斜め上へ
legRoot.add(thigh);
// 関節のトゲ
const tSpike = new THREE.Mesh(new THREE.ConeGeometry(0.5, 2.0, 4), matArmor);
tSpike.position.set(sign * (thighLen * Math.cos(Math.PI/6)), thighLen * Math.sin(Math.PI/6) + 0.5, 0);
tSpike.rotation.z = sign * -Math.PI / 8;
legRoot.add(tSpike);
// すね (下に鋭く伸びる)
const shinLen = 6.5;
const shinGrp = new THREE.Group();
shinGrp.position.set(sign * thighLen * Math.cos(Math.PI/6), thighLen * Math.sin(Math.PI/6), 0);
const shin = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.1, shinLen, 8), matBody);
shin.position.set(sign * (shinLen/2) * Math.cos(-Math.PI/2.5), (shinLen/2) * Math.sin(-Math.PI/2.5), 0);
shin.rotation.z = sign * -Math.PI / 6; // 斜め下へ
shinGrp.add(shin);
// 脛の装甲
const sArmor = new THREE.Mesh(new THREE.CylinderGeometry(0.7, 0.3, shinLen*0.8, 4), matArmor);
sArmor.position.copy(shin.position);
sArmor.rotation.copy(shin.rotation);
shinGrp.add(sArmor);
legRoot.add(shinGrp);
group.add(legRoot);
legs.push({ root: legRoot, offset: i + sign });
}
});
// 背部の2本の長い触手(ハサミ付き)
const tentacles = [];
for (let sign of [-1, 1]) {
const tentacle = buildTail(12, 0.9, 2.2);
tentacle.root.position.set(sign * 1.5, 6, -1);
// 斜め上・前方にカーブするように初期回転
tentacle.root.rotation.set(-Math.PI / 6, sign * Math.PI / 4, 0);
// 各セグメントを少しずつ曲げて、サソリの尾のようにアーチを描かせる
tentacle.segments.forEach((seg, idx) => {
seg.rotation.x = Math.PI / 10; // 前に曲がる
});
group.add(tentacle.root);
tentacles.push({ obj: tentacle, sign: sign });
}
// 尻尾(下の方から後ろへ)
const tailObj = buildTail(10, 1.2, 2.5);
tailObj.root.position.set(0, 2, -3);
group.add(tailObj.root);
updateActions.push(() => {
group.position.y = 3.5 + Math.sin(time * 2) * 0.2; // 全体の上下動
if(headObj.light) headObj.light.intensity = (Math.sin(time * 3) + 1) * 0.5 + 0.5;
// 首の不気味なうねり
neckSegments.forEach((seg, i) => {
seg.rotation.x = Math.sin(time * 2 - i * 0.5) * 0.05;
seg.rotation.z = Math.sin(time * 1.5 - i * 0.5) * 0.02;
});
// カマの動き (クロスしないよう控えめな動きに)
arms.forEach(arm => {
arm.root.rotation.x = Math.sin(time * 3) * 0.05;
arm.root.rotation.y = Math.cos(time * 2) * 0.05 * arm.sign;
arm.blade.rotation.x = Math.sin(time * 3 + arm.sign) * 0.05;
});
// クモ脚のワシャワシャ歩行
legs.forEach(leg => {
leg.root.rotation.x = Math.sin(time * 6 + leg.offset) * 0.1;
leg.root.position.y = 3 + Math.sin(time * 6 + leg.offset + Math.PI/2) * 0.4;
});
// 尻尾
tailObj.segments.forEach((seg, i) => {
seg.rotation.y = Math.sin(time * 2 - i * 0.3) * 0.2;
});
// 背部触手のうねり
tentacles.forEach(t => {
t.obj.segments.forEach((seg, i) => {
// 基本の曲がり具合(アーチ維持)+うねりアニメーション
seg.rotation.x = Math.PI / 10 + Math.sin(time * 3 - i * 0.4) * 0.05;
seg.rotation.y = Math.cos(time * 2 - i * 0.3) * 0.1 * t.sign;
});
});
});
return group;
}
// 【飛行体】
function createFlyingForm() {
const group = new THREE.Group();
group.position.y = 8.0; // 空中
// 細長い胴体(飛行姿勢)
const body = new THREE.Mesh(new THREE.CylinderGeometry(2.5, 2.0, 10, 8), matBody);
body.rotation.x = Math.PI / 2; body.castShadow = true; group.add(body);
// 頭部(前方に)
const headObj = buildHead(true);
headObj.root.position.set(0, 0, 6); headObj.root.rotation.x = Math.PI / 12; group.add(headObj.root);
// 翼(水平に広げる)
const wingsObj = buildWings(1.2);
wingsObj.root.position.set(0, 1, 2);
wingsObj.wings.forEach(w => w.root.rotation.x = 0); // 前傾させない
group.add(wingsObj.root);
// 垂れ下がる脚
for (let sign of [-1, 1]) {
const leg = new THREE.Group(); leg.position.set(sign * 2, -1, -2);
const thigh = new THREE.Mesh(new THREE.CylinderGeometry(1, 0.8, 4, 8), matArmor);
thigh.position.set(0, -1.5, -1); thigh.rotation.x = -Math.PI / 4; leg.add(thigh);
const shin = new THREE.Mesh(new THREE.ConeGeometry(0.5, 3, 8), matBone);
shin.position.set(0, -3.5, -2.5); shin.rotation.x = -Math.PI / 6; leg.add(shin);
group.add(leg);
}
// まっすぐな尻尾
const tailObj = buildTail(12, 1.2, 2.8);
tailObj.root.position.set(0, 0, -5); group.add(tailObj.root);
updateActions.push(() => {
group.position.y = 8.0 + Math.sin(time * 2) * 1.0; // 浮遊
group.rotation.x = Math.sin(time * 1.5) * 0.05;
if(headObj.light) headObj.light.intensity = (Math.sin(time * 3) + 1) * 0.5 + 0.5;
wingsObj.wings.forEach(w => {
w.root.rotation.z = Math.sin(time * 4 * w.speed) * 0.3 * (w.isLeft ? -1 : 1); // 大きく羽ばたく
});
tailObj.segments.forEach((seg, i) => {
seg.rotation.x = Math.sin(time * 3 - i * 0.4) * 0.1; // 上下にうねる
seg.rotation.y = Math.cos(time * 2 - i * 0.3) * 0.05;
});
});
return group;
}
// 【完全体】 (前回のコードベースを共通関数で再構築)
function createPerfectForm() {
const group = new THREE.Group();
// 胴体
const bodyGroup = new THREE.Group();
const torso = new THREE.Mesh(new THREE.CylinderGeometry(3, 5, 9, 8), matBody);
torso.position.y = 5.5; torso.castShadow = true; bodyGroup.add(torso);
const backArmor = new THREE.Mesh(new THREE.BoxGeometry(5, 8, 4), matArmor);
backArmor.position.set(0, 5.5, -1.5); backArmor.rotation.x = -Math.PI / 12; bodyGroup.add(backArmor);
const bellyGroup = new THREE.Group(); bellyGroup.position.set(0, 5, 3.5); bellyGroup.rotation.x = Math.PI / 8;
// --- 修正箇所:Object.assignでscaleを上書きするのをやめ、個別に設定 ---
const bellyCenterMesh = new THREE.Mesh(new THREE.SphereGeometry(1.5, 8, 8), matBelly);
bellyCenterMesh.scale.set(1, 1, 0.4);
bellyGroup.add(bellyCenterMesh);
// ----------------------------------------------------------------------
const petalGeo = new THREE.ConeGeometry(0.7, 2.5, 4); petalGeo.translate(0, 1.2, 0);
for(let i=0; i<8; i++) {
const petal = new THREE.Mesh(petalGeo, matBelly);
petal.rotation.set(-Math.PI / 12, 0, (Math.PI * 2 / 8) * i); petal.scale.z = 0.3; bellyGroup.add(petal);
}
bodyGroup.add(bellyGroup);
group.add(bodyGroup);
// 頭部
const headObj = buildHead(true);
headObj.root.position.set(0, 10.5, 1.5); group.add(headObj.root);
// 腕・脚 (二足歩行用)
for(let sign of [-1, 1]) {
// 腕
const arm = new THREE.Group();
const shoulder = new THREE.Mesh(new THREE.SphereGeometry(1.6, 8, 8), matArmor); shoulder.position.set(sign*3.8, 7.5, 0); arm.add(shoulder);
const ua = new THREE.Mesh(new THREE.CylinderGeometry(0.9, 0.7, 3.5, 8), matBody); ua.position.set(sign*4.8, 5.5, 0.5); ua.rotation.set(Math.PI/6, 0, sign*Math.PI/6); arm.add(ua);
const la = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 1.0, 3.5, 8), matArmor); la.position.set(sign*5.2, 3.5, 2.5); la.rotation.set(-Math.PI/4, 0, sign*Math.PI/12); arm.add(la);
for(let i=-1; i<=1; i++) {
const claw = new THREE.Mesh(new THREE.ConeGeometry(0.3, 1.8, 4), matBone); claw.position.set(sign*5.2+(i*0.4), 1.8, 3.5); claw.rotation.set(-Math.PI/2, 0, sign*(i*0.2)); arm.add(claw);
}
group.add(arm);
// 脚
const leg = new THREE.Group();
const thigh = new THREE.Mesh(new THREE.CylinderGeometry(2.8, 3.2, 5.5, 8), matArmor); thigh.position.set(sign*3.5, 4.5, -1); thigh.rotation.set(Math.PI/8, 0, sign*-Math.PI/12); leg.add(thigh);
const shin = new THREE.Mesh(new THREE.CylinderGeometry(2.5, 2.0, 4.5, 8), matBody); shin.position.set(sign*4, 2.0, 1.0); shin.rotation.x = -Math.PI/12; leg.add(shin);
const foot = new THREE.Mesh(new THREE.BoxGeometry(3.5, 1.5, 4.5), matArmor); foot.position.set(sign*4.2, 0.75, 2.0); leg.add(foot);
for(let i=-1; i<=1; i++) {
const toe = new THREE.Mesh(new THREE.ConeGeometry(0.6, 2.0, 4), matBone); toe.position.set(sign*4.2+(i*1.2), 0.5, 4.0); toe.rotation.set(-Math.PI/2, 0, sign*(i*-0.2)); leg.add(toe);
}
group.add(leg);
}
// 翼と尻尾
const wingsObj = buildWings(1.0);
wingsObj.wings.forEach(w => { w.root.rotation.x = -Math.PI / 10; w.root.rotation.y = (w.isLeft ? -1 : 1) * Math.PI / 8; });
group.add(wingsObj.root);
const tailObj = buildTail(14, 2.2, 2.8);
tailObj.root.position.set(0, 3, -3); group.add(tailObj.root);
updateActions.push(() => {
group.position.y = Math.sin(time * 0.5) * 0.2; // 呼吸
if(headObj.light) headObj.light.intensity = (Math.sin(time * 3) + 1) * 0.5 + 0.5;
wingsObj.wings.forEach(w => {
const sign = w.isLeft ? -1 : 1;
w.root.rotation.y = (sign * Math.PI / 8) + Math.sin(time * 2 * w.speed) * 0.1 * sign;
w.root.rotation.z = Math.sin(time * 2 * w.speed) * 0.05 * sign;
});
tailObj.segments.forEach((seg, i) => {
seg.rotation.y = Math.sin(time * 1.5 - i * 0.4) * 0.15;
seg.rotation.x = Math.cos(time * 1 - i * 0.3) * 0.05;
});
});
return group;
}
// --- 6. フォーム切り替えと初期化 ---
window.changeForm = function(formName) {
if (currentModel) { scene.remove(currentModel); }
updateActions = []; // アニメーションリセット
switch (formName) {
case 'micro': currentModel = createMicroForm(); break;
case 'aggregate': currentModel = createAggregateForm(); break;
case 'flying': currentModel = createFlyingForm(); break;
case 'perfect': default: currentModel = createPerfectForm(); break;
}
scene.add(currentModel);
};
// 地面作成
const groundGeo = new THREE.PlaneGeometry(100, 100, 32, 32);
const pos = groundGeo.attributes.position;
for(let i=0; i<pos.count; i++) pos.setZ(i, (Math.random() - 0.5) * 1.5);
groundGeo.computeVertexNormals();
const ground = new THREE.Mesh(groundGeo, new THREE.MeshStandardMaterial({ color: 0x110505, roughness: 0.9, flatShading: true }));
ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground);
document.getElementById('loading').style.opacity = '0';
// リサイズ対応
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// --- 7. アニメーションループ ---
function animate() {
requestAnimationFrame(animate);
time += 0.05;
updateActions.forEach(action => action(time)); // 各形態の専用アニメーションを実行
controls.update();
renderer.render(scene, camera);
}
// 初回表示
changeForm('perfect');
animate();
});
</script>
</body>
</html>
僕の1番のお気に入りは完全体