Files
nagi-bench/outputs/gemini-3-1-pro/gemini-web-deepthink/skeleton-watch.html
T

591 lines
33 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>3D 奢华交互式镂空机械腕表 | Haute Horlogerie</title>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600&family=Montserrat:wght@300;400;500&display=swap" rel="stylesheet">
<style>
body, html {
margin: 0; padding: 0; width: 100vw; height: 100vh; overflow: hidden;
background: radial-gradient(circle at center, #1b1d24 0%, #050508 100%);
font-family: 'Montserrat', sans-serif;
color: #ffffff;
user-select: none;
}
#canvas-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
/* UI 覆盖层 */
#ui-layer {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
z-index: 10; pointer-events: none;
}
header, .controls, #info-panel, .instructions { pointer-events: auto; }
header { position: absolute; top: 40px; left: 40px; }
h1 {
font-family: 'Cinzel', serif; font-size: 3rem; margin: 0; font-weight: 400;
letter-spacing: 6px; color: #fff; text-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
h1 span { color: #d4af37; }
.subtitle { font-size: 0.9rem; letter-spacing: 5px; color: #a0a0a5; text-transform: uppercase; margin-top: 8px; }
.controls { position: absolute; bottom: 40px; right: 40px; }
button {
background: rgba(20, 20, 25, 0.6); border: 1px solid rgba(212, 175, 55, 0.5);
color: #d4af37; padding: 14px 30px; font-family: 'Montserrat', sans-serif; font-size: 0.85rem;
letter-spacing: 2px; text-transform: uppercase; cursor: pointer; border-radius: 2px;
backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); transition: all 0.4s ease;
}
button:hover, button.active { background: #d4af37; color: #050508; box-shadow: 0 0 20px rgba(212, 175, 55, 0.4); }
/* 右侧信息面板 */
#info-panel {
position: absolute; right: 40px; top: 50%; transform: translateY(-50%) translateX(20px);
width: 320px; background: rgba(12, 12, 16, 0.75);
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,0.05); border-left: 2px solid #d4af37;
padding: 30px; opacity: 0; pointer-events: none; transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
box-shadow: 0 20px 40px rgba(0,0,0,0.5);
}
#info-panel.visible { opacity: 1; transform: translateY(-50%) translateX(0); pointer-events: auto; }
#info-title {
font-family: 'Cinzel', serif; font-size: 1.4rem; color: #d4af37; margin: 0 0 15px 0;
padding-bottom: 10px; border-bottom: 1px solid rgba(212, 175, 55, 0.2); letter-spacing: 2px;
}
#info-desc { font-size: 0.9rem; line-height: 1.6; color: #d0d0d5; margin: 0; font-weight: 300; }
.instructions { position: absolute; bottom: 40px; left: 40px; font-size: 0.75rem; color: #888; letter-spacing: 2px; text-transform: uppercase; }
/* 鼠标悬停提示 */
#tooltip {
position: absolute; background: rgba(10, 10, 14, 0.9); border: 1px solid #d4af37;
color: #fff; padding: 8px 15px; font-size: 0.8rem; letter-spacing: 1px; border-radius: 2px;
pointer-events: none; opacity: 0; transition: opacity 0.2s; transform: translate(15px, 15px);
z-index: 100; box-shadow: 0 5px 15px rgba(0,0,0,0.5); white-space: nowrap;
}
/* 加载动画 */
#loader {
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #050508;
z-index: 999; display: flex; flex-direction: column; justify-content: center; align-items: center;
transition: opacity 1s ease;
}
.spinner {
width: 45px; height: 45px; border: 2px solid rgba(212, 175, 55, 0.2);
border-top: 2px solid #d4af37; border-radius: 50%;
animation: spin 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite; margin-bottom: 20px;
}
@keyframes spin { 100% { transform: rotate(360deg); } }
.loader-text { font-family: 'Cinzel', serif; color: #d4af37; letter-spacing: 4px; font-size: 1.1rem; }
</style>
<!-- Three.js ESM 导入 -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
</head>
<body>
<div id="loader">
<div class="spinner"></div>
<div class="loader-text">Assembling Calibre...</div>
</div>
<div id="canvas-container"></div>
<div id="tooltip"></div>
<div id="ui-layer">
<header>
<h1>Aethelgard <span>Skeleton</span></h1>
<div class="subtitle">Grand Complication · Calibre 89</div>
</header>
<div id="info-panel">
<h2 id="info-title">零件名称</h2>
<p id="info-desc">零件描述详情...</p>
</div>
<div class="instructions">
拖拽旋转视角 | 滚轮缩放 | 点击高亮鉴赏机芯组件
</div>
<div class="controls">
<button id="btn-explode">解构视图 (Explode View)</button>
</div>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
// ==========================================
// 1. 初始化场景、相机与渲染器
// ==========================================
const container = document.getElementById('canvas-container');
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(12, -22, 28);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
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.3;
container.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxDistance = 60;
controls.minDistance = 12;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.5;
// ==========================================
// 2. 高级光照与后期泛光特效 (Bloom)
// ==========================================
const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
const mainLight = new THREE.DirectionalLight(0xfff5e6, 3);
mainLight.position.set(15, 20, 15);
mainLight.castShadow = true;
mainLight.shadow.mapSize.width = 2048;
mainLight.shadow.mapSize.height = 2048;
mainLight.shadow.bias = -0.0001;
scene.add(mainLight);
const fillLight = new THREE.DirectionalLight(0xaaccff, 1.5);
fillLight.position.set(-15, -10, 10);
scene.add(fillLight);
scene.add(new THREE.AmbientLight(0xffffff, 0.2));
// 泛光:仅作用于自发光极强的材质 (夜光涂层与金属极亮高光)
const renderScene = new RenderPass(scene, camera);
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
bloomPass.threshold = 1.0;
bloomPass.strength = 1.5;
const outputPass = new OutputPass();
const composer = new EffectComposer(renderer);
composer.addPass(renderScene);
composer.addPass(bloomPass);
composer.addPass(outputPass);
// ==========================================
// 3. 顶级 PBR 材质库与珍珠纹生成器
// ==========================================
function createPerlageTexture() {
const canvas = document.createElement('canvas');
canvas.width = 1024; canvas.height = 1024;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#888';
ctx.fillRect(0, 0, 1024, 1024);
const radius = 25;
for (let y = -radius; y < 1024 + radius; y += radius * 0.7) {
for (let x = -radius; x < 1024 + radius; x += radius * 0.7) {
const oX = (Math.random() - 0.5) * 4, oY = (Math.random() - 0.5) * 4;
const grad = ctx.createRadialGradient(x+oX, y+oY, 0, x+oX, y+oY, radius);
grad.addColorStop(0, 'rgba(255,255,255,0.6)');
grad.addColorStop(0.5, 'rgba(150,150,150,0.2)');
grad.addColorStop(1, 'rgba(50,50,50,0.4)');
ctx.beginPath(); ctx.arc(x+oX, y+oY, radius, 0, Math.PI * 2);
ctx.fillStyle = grad; ctx.fill();
}
}
const tex = new THREE.CanvasTexture(canvas);
tex.wrapS = tex.wrapT = THREE.RepeatWrapping; return tex;
}
const mats = {
gold: new THREE.MeshPhysicalMaterial({ color: 0xd4af37, metalness: 1.0, roughness: 0.15, clearcoat: 0.6 }),
roseGold: new THREE.MeshPhysicalMaterial({ color: 0xc87c64, metalness: 1.0, roughness: 0.15, clearcoat: 0.6 }),
steel: new THREE.MeshPhysicalMaterial({ color: 0xe0e5e9, metalness: 1.0, roughness: 0.2, clearcoat: 0.2 }),
bridge: new THREE.MeshPhysicalMaterial({ color: 0xc0c0c0, metalness: 1.0, roughness: 0.3, bumpMap: createPerlageTexture(), bumpScale: 0.015, clearcoat: 0.2 }),
darkMetal: new THREE.MeshPhysicalMaterial({ color: 0x1f1f1f, metalness: 0.9, roughness: 0.4 }),
matteBlack: new THREE.MeshStandardMaterial({ color: 0x111111, metalness: 0.3, roughness: 0.9 }),
ruby: new THREE.MeshPhysicalMaterial({ color: 0xd90036, metalness: 0.1, roughness: 0.05, transmission: 0.9, transparent: true, ior: 1.76, thickness: 0.5 }),
sapphire: new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0, roughness: 0, transmission: 1.0, transparent: true, ior: 1.77, thickness: 0.5 }),
lume: new THREE.MeshStandardMaterial({ color: 0xd4ffb8, emissive: 0x22ff66, emissiveIntensity: 2.5 }),
bluedSteel: new THREE.MeshPhysicalMaterial({ color: 0x0f3b76, metalness: 1.0, roughness: 0.15, clearcoat: 0.8 })
};
// ==========================================
// 4. 程序化几何体工厂函数
// ==========================================
function createGear(radius, teeth, holeRadius, spokes, material, depth=0.1) {
const shape = new THREE.Shape();
const innerR = radius * 0.88;
for (let i = 0; i < teeth; i++) {
const a1 = (i/teeth)*Math.PI*2, a2 = ((i+0.25)/teeth)*Math.PI*2;
const a3 = ((i+0.75)/teeth)*Math.PI*2, a4 = ((i+1)/teeth)*Math.PI*2;
if(i===0) shape.moveTo(Math.cos(a1)*innerR, Math.sin(a1)*innerR); else shape.lineTo(Math.cos(a1)*innerR, Math.sin(a1)*innerR);
shape.lineTo(Math.cos(a2)*radius, Math.sin(a2)*radius);
shape.lineTo(Math.cos(a3)*radius, Math.sin(a3)*radius);
shape.lineTo(Math.cos(a4)*innerR, Math.sin(a4)*innerR);
}
if (spokes > 0) {
const spokeW = Math.PI/spokes * 0.2;
for (let i = 0; i < spokes; i++) {
const angle = (i/spokes)*Math.PI*2, hole = new THREE.Path();
const s = angle + spokeW, e = angle + Math.PI*2/spokes - spokeW;
hole.absarc(0, 0, innerR * 0.85, s, e, false);
hole.lineTo(Math.cos(e)*(holeRadius+0.2), Math.sin(e)*(holeRadius+0.2));
hole.absarc(0, 0, holeRadius + 0.2, e, s, true);
hole.lineTo(Math.cos(s)*(innerR*0.85), Math.sin(s)*(innerR*0.85));
shape.holes.push(hole);
}
}
const c = new THREE.Path(); c.absarc(0, 0, holeRadius, 0, Math.PI * 2, true); shape.holes.push(c);
const mesh = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { depth, bevelEnabled: true, bevelSize: 0.015, bevelThickness: 0.015 }), material);
mesh.geometry.center(); mesh.castShadow = true; mesh.receiveShadow = true;
const pinion = new THREE.Mesh(new THREE.CylinderGeometry(holeRadius*0.9, holeRadius*0.9, depth*3, 16), mats.steel);
pinion.rotation.x = Math.PI / 2; mesh.add(pinion);
return mesh;
}
function createBridge(shapePts, holes, depth, material) {
const shape = new THREE.Shape();
shapePts.forEach((p, i) => {
if(i===0) shape.moveTo(p.x, p.y); else if(p.cx !== undefined) shape.quadraticCurveTo(p.cx, p.cy, p.x, p.y); else shape.lineTo(p.x, p.y);
});
holes.forEach(h => { const hp = new THREE.Path(); hp.absarc(h.x, h.y, h.r, 0, Math.PI*2, true); shape.holes.push(hp); });
const mesh = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { depth, bevelEnabled: true, bevelSize: 0.04, bevelThickness: 0.04 }), material);
mesh.castShadow = true; mesh.receiveShadow = true; return mesh;
}
function addJewelScrew(group, x, y, z) {
const chaton = new THREE.Mesh(new THREE.TorusGeometry(0.2, 0.06, 16, 32), mats.gold);
const ruby = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.18, 0.1, 16), mats.ruby);
ruby.rotation.x = Math.PI/2; chaton.position.set(x, y, z); ruby.position.set(x, y, z);
group.add(chaton, ruby);
[Math.PI/4, Math.PI*1.25].forEach(a => {
const screw = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.15, 16), mats.bluedSteel);
screw.rotation.x = Math.PI/2; screw.position.set(x + Math.cos(a)*0.45, y + Math.sin(a)*0.45, z);
const slot = new THREE.Mesh(new THREE.BoxGeometry(0.24, 0.04, 0.16), mats.darkMetal);
slot.position.y = 0.07; screw.add(slot); screw.rotation.z = Math.random() * Math.PI; group.add(screw);
});
}
// ==========================================
// 5. 层级管理与机芯组装
// ==========================================
const layers = {
crystal: { group: new THREE.Group(), bZ: 1.5, eZ: 14 },
bezel: { group: new THREE.Group(), bZ: 0.5, eZ: 9 },
hands: { group: new THREE.Group(), bZ: 0.5, eZ: 5 },
dial: { group: new THREE.Group(), bZ: 0.4, eZ: 3 },
bridges: { group: new THREE.Group(), bZ: 0.3, eZ: 1 },
gears: { group: new THREE.Group(), bZ: 0, eZ: -1 },
baseplate: { group: new THREE.Group(), bZ: -0.4, eZ: -4 },
case: { group: new THREE.Group(), bZ: 0, eZ: -8 },
caseback: { group: new THREE.Group(), bZ: -0.8, eZ: -12 }
};
const watchAssembly = new THREE.Group();
Object.values(layers).forEach(l => { l.group.position.z = l.bZ; watchAssembly.add(l.group); });
watchAssembly.rotation.x = -Math.PI / 6; watchAssembly.rotation.y = -Math.PI / 12;
scene.add(watchAssembly);
const interactables = [];
function registerPart(group, name, desc) {
group.userData = { name, desc };
group.traverse(c => {
if (c.isMesh && c.material && c.material !== mats.sapphire) {
c.material = c.material.clone();
c.userData.origEmissive = c.material.emissive ? c.material.emissive.getHex() : 0x000000;
c.userData.parentGroup = group;
interactables.push(c);
}
});
}
// [A. 表壳外装]
const gCase = new THREE.Group();
const caseMesh = new THREE.Mesh(new THREE.TorusGeometry(10.5, 0.6, 64, 128), mats.roseGold);
const crownGrp = new THREE.Group();
const crownCore = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.8, 1.5, 32), mats.roseGold);
const crownGrip = new THREE.Mesh(new THREE.CylinderGeometry(0.85, 0.85, 1.2, 24), mats.darkMetal);
crownGrp.add(crownCore, crownGrip); crownGrp.rotation.z = -Math.PI/2; crownGrp.position.set(10.8, 0, 0);
gCase.add(caseMesh, crownGrp);
const lugShape = new THREE.Shape(); lugShape.moveTo(0,0); lugShape.lineTo(2.5, 4.5); lugShape.lineTo(1.0, 5.0); lugShape.lineTo(-1.0, 5.0); lugShape.lineTo(-2.5, 4.5);
const lugGeo = new THREE.ExtrudeGeometry(lugShape, { depth: 1.0, bevelEnabled: true, bevelSize: 0.1, bevelThickness: 0.1 }); lugGeo.translate(0, 0, -0.5);
[Math.PI/4, 3*Math.PI/4, 5*Math.PI/4, 7*Math.PI/4].forEach(a => {
const lug = new THREE.Mesh(lugGeo, mats.roseGold);
lug.position.set(Math.cos(a)*10, Math.sin(a)*10, -0.2); lug.rotation.z = a - Math.PI/2; gCase.add(lug);
});
layers.case.group.add(gCase);
registerPart(gCase, "18K 玫瑰金表壳 (Rose Gold Case)", "42mm 极致手工抛光表壳,配备符合人体工学的流线型表耳与防滑坑纹表冠。");
const gBezel = new THREE.Group();
const bezelMesh = new THREE.Mesh(new THREE.TorusGeometry(10.0, 0.4, 64, 128), mats.steel);
gBezel.add(bezelMesh); layers.bezel.group.add(gBezel);
registerPart(gBezel, "精钢镶边表圈 (Steel Bezel)", "拉丝打磨工艺的精钢表圈,紧紧锁住内部蓝宝石镜面。");
const gCrystal = new THREE.Group();
const frontGlass = new THREE.Mesh(new THREE.CylinderGeometry(10.2, 10.2, 0.2, 64), mats.sapphire); frontGlass.rotation.x = Math.PI/2;
gCrystal.add(frontGlass); layers.crystal.group.add(gCrystal);
registerPart(gCrystal, "穹顶蓝宝石水晶 (Sapphire Crystal)", "莫氏硬度9级,双面防眩光涂层,确保无死角地欣赏机芯律动。");
const gCaseback = new THREE.Group();
const backGlass = new THREE.Mesh(new THREE.CylinderGeometry(10.2, 10.2, 0.2, 64), mats.sapphire); backGlass.rotation.x = Math.PI/2;
const backBezel = new THREE.Mesh(new THREE.TorusGeometry(10.0, 0.4, 64, 128), mats.steel);
gCaseback.add(backGlass, backBezel); layers.caseback.group.add(gCaseback);
registerPart(gCaseback, "透视蓝宝石底盖 (Exhibition Caseback)", "背部同样采用高纯度蓝宝石,展露机芯背面的精绝倒角与打磨工艺。");
// [B. 表盘与刻度]
const gDial = new THREE.Group();
const dialRing = new THREE.Mesh(new THREE.RingGeometry(9.0, 10.0, 64), mats.matteBlack);
gDial.add(dialRing);
for(let i=0; i<12; i++) {
const a = i * Math.PI / 6, isQ = i%3===0;
const marker = new THREE.Mesh(new THREE.BoxGeometry(0.3, isQ ? 1.5 : 0.8, 0.15), mats.roseGold);
marker.position.set(Math.cos(a)*9.4, Math.sin(a)*9.4, 0.1); marker.rotation.z = -a + Math.PI/2;
const lume = new THREE.Mesh(new THREE.BoxGeometry(0.15, isQ ? 1.2 : 0.6, 0.18), mats.lume); marker.add(lume);
gDial.add(marker);
}
const subDialRing = new THREE.Mesh(new THREE.RingGeometry(1.9, 2.1, 64), mats.matteBlack);
subDialRing.position.set(0, -4.5, 0.02); gDial.add(subDialRing);
for(let i=0; i<12; i++) {
const a = i * Math.PI / 6;
const m = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.3, 0.05), mats.roseGold);
m.position.set(Math.cos(a)*2.0, -4.5 + Math.sin(a)*2.0, 0.05); m.rotation.z = -a + Math.PI/2; gDial.add(m);
}
layers.dial.group.add(gDial);
registerPart(gDial, "镂空测速表盘 (Skeletonized Dial)", "悬浮式暗黑外圈,搭配 18K 金立体时标与 Super-LumiNova 夜光涂层,六点位设独立小秒盘。");
// [C. 核心传动齿轮系]
const barrelGrp = new THREE.Group(); barrelGrp.position.set(-2.5, 3.12, 0);
const barrelGear = createGear(3.5, 48, 0.3, 0, mats.gold, 0.2);
const barrelCover = new THREE.Mesh(new THREE.CylinderGeometry(3.2, 3.2, 0.22, 64), mats.steel); barrelCover.rotation.x = Math.PI/2;
barrelGrp.add(barrelGear, barrelCover); layers.gears.group.add(barrelGrp);
registerPart(barrelGrp, "发条盒 (Mainspring Barrel)", "机芯动力源泉,内卷巨型游丝发条,提供长达 72 小时的恒定动力储备。");
const centerGrp = new THREE.Group();
const centerGear = createGear(2.8, 40, 0.2, 5, mats.gold, 0.1);
centerGrp.add(centerGear); layers.gears.group.add(centerGrp);
registerPart(centerGrp, "二轮 / 中心轮 (Center Wheel)", "处于表盘正中心,每小时稳健旋转一圈,直接承接动力并驱动分针运行。");
const thirdGrp = new THREE.Group(); thirdGrp.position.set(2.2, -2.5, 0);
const thirdGear = createGear(2.2, 30, 0.15, 5, mats.gold, 0.1);
thirdGrp.add(thirdGear); layers.gears.group.add(thirdGrp);
registerPart(thirdGrp, "三轮 (Third Wheel)", "传动链的关键中继站,将中心轮巨大的扭矩平稳过滤传递给擒纵系统。");
const fourthGrp = new THREE.Group(); fourthGrp.position.set(0, -4.5, 0);
const fourthGear = createGear(2.0, 30, 0.15, 5, mats.gold, 0.1);
fourthGrp.add(fourthGear); layers.gears.group.add(fourthGrp);
registerPart(fourthGrp, "四轮 / 秒轮 (Fourth Wheel)", "每分钟精准旋转一圈,轴心延长连接正面的小秒针,展现机械流逝的魅力。");
// [D. 擒纵机构]
const escapeGrp = new THREE.Group(); escapeGrp.position.set(-2.2, -3.5, 0);
const escapeShape = new THREE.Shape();
for(let i=0; i<15; i++) {
const a = i/15 * Math.PI*2, a2 = (i+0.1)/15 * Math.PI*2, a3 = (i+0.25)/15 * Math.PI*2;
if(i===0) escapeShape.moveTo(Math.cos(a)*0.6, Math.sin(a)*0.6); else escapeShape.lineTo(Math.cos(a)*0.6, Math.sin(a)*0.6);
escapeShape.lineTo(Math.cos(a2)*1.2, Math.sin(a2)*1.2); escapeShape.lineTo(Math.cos(a3)*1.2, Math.sin(a3)*1.2); escapeShape.lineTo(Math.cos(a3)*0.6, Math.sin(a3)*0.6);
}
const escHole = new THREE.Path(); escHole.absarc(0,0,0.2,0,Math.PI*2,true); escapeShape.holes.push(escHole);
const escapeMesh = new THREE.Mesh(new THREE.ExtrudeGeometry(escapeShape, { depth: 0.1, bevelEnabled: true, bevelSize: 0.01, bevelThickness: 0.01 }), mats.steel);
escapeMesh.geometry.center(); escapeGrp.add(escapeMesh); layers.gears.group.add(escapeGrp);
registerPart(escapeGrp, "星形擒纵轮 (Escape Wheel)", "将齿轮连续的旋转运动切割成规律的脉冲,发出迷人的“滴答”声。");
const balanceGrp = new THREE.Group(); balanceGrp.position.set(-4.2, -1.5, 0);
const balRing = new THREE.Mesh(new THREE.TorusGeometry(2.6, 0.15, 16, 64), mats.roseGold);
const balCross = new THREE.Mesh(new THREE.BoxGeometry(5.2, 0.2, 0.1), mats.roseGold);
balanceGrp.add(balRing, balCross);
for(let i=0; i<16; i++) {
const a = i/16 * Math.PI*2;
const w = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.25, 12), mats.steel);
w.rotation.z = a - Math.PI/2; w.position.set(Math.cos(a)*2.6, Math.sin(a)*2.6, 0); balanceGrp.add(w);
}
const curvePts = [];
for(let i=0; i<200; i++) {
const t = i/200, a = t * Math.PI * 2 * 12, r = 0.2 + t * 1.8;
curvePts.push(new THREE.Vector3(Math.cos(a)*r, Math.sin(a)*r, 0));
}
const springMesh = new THREE.Mesh(new THREE.TubeGeometry(new THREE.CatmullRomCurve3(curvePts), 200, 0.015, 8, false), mats.bluedSteel);
springMesh.position.z = 0.2; balanceGrp.add(springMesh);
const balHitBox = new THREE.Mesh(new THREE.CylinderGeometry(2.8, 2.8, 0.6), new THREE.MeshBasicMaterial({visible:false}));
balHitBox.rotation.x = Math.PI/2; balanceGrp.add(balHitBox);
layers.gears.group.add(balanceGrp);
registerPart(balanceGrp, "摆轮与防磁游丝 (Balance Wheel & Hairspring)", "如同心脏般呼吸。蓝钢游丝控制着摆轮以 28,800 VpH (4Hz) 的极高频率恒定振荡,决定走时精度。");
// [E. 板桥基座]
const gBridges = new THREE.Group();
const b1 = createBridge([{x: -6, y: 1}, {x: -6, y: 7}, {x: 0, y: 7}, {cx: 2, cy: 5, x: 1, y: 2}], [{x: -2.5, y: 3.12, r: 0}], 0.15, mats.bridge);
addJewelScrew(b1, -2.5, 3.12, 0.15); addJewelScrew(b1, -4.5, 5.5, 0.15);
const b2 = createBridge([{x: -1, y: 1}, {cx: 4, cy: 2, x: 4, y: -2}, {cx: 4, cy: -5, x: 1, y: -4}, {cx: 0, cy: -2, x: -1, y: 1}], [], 0.15, mats.bridge);
addJewelScrew(b2, 0, 0, 0.15); addJewelScrew(b2, 2.2, -2.5, 0.15);
const b3 = createBridge([{x: -8, y: -0.5}, {x: -7, y: -4}, {x: -5, y: -3}, {cx: -3.5, cy: -0.5, x: -5, y: -0.5}], [], 0.15, mats.bridge);
addJewelScrew(b3, -4.2, -1.5, 0.15);
gBridges.add(b1, b2, b3); layers.bridges.group.add(gBridges);
registerPart(gBridges, "夹板与红宝石 (Bridges & Jewel Bearings)", "镀铑处理表面覆以迷人的珍珠纹打磨。黄金套筒内镶嵌着人造红宝石轴承,用于降低齿轮轴心的摩擦阻力。");
const gBaseplate = new THREE.Group();
const bpShape = new THREE.Shape(); bpShape.absarc(0, 0, 9.5, 0, Math.PI*2, false);
[{x:-2.5, y:3.12, r:3.0}, {x:0, y:0, r:2.2}, {x:2.2, y:-2.5, r:1.8}, {x:0, y:-4.5, r:1.6}, {x:-4.2, y:-1.5, r:2.5}].forEach(h => {
const hp = new THREE.Path(); hp.absarc(h.x, h.y, h.r, 0, Math.PI*2, true); bpShape.holes.push(hp);
});
const bpMesh = new THREE.Mesh(new THREE.ExtrudeGeometry(bpShape, { depth: 0.3, bevelEnabled: true, bevelSize: 0.05, bevelThickness: 0.05 }), mats.darkMetal);
gBaseplate.add(bpMesh); layers.baseplate.group.add(gBaseplate);
registerPart(gBaseplate, "倒角镂空主夹板 (Skeleton Mainplate)", "机芯的大地。经 CNC 深度镂空切削,辅以深灰色喷砂涂层,边缘纯手工倒角抛光,极具几何工业美学。");
// [F. 指针系统]
const gHands = new THREE.Group();
function createHand(len, w, lumeRatio, mat) {
const shape = new THREE.Shape();
shape.moveTo(0, -w); shape.lineTo(w, 0); shape.lineTo(w*0.2, len); shape.lineTo(0, len + w); shape.lineTo(-w*0.2, len); shape.lineTo(-w, 0);
const mesh = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { depth: 0.05, bevelEnabled: true, bevelSize: 0.015, bevelThickness: 0.015 }), mat);
if(lumeRatio > 0) {
const lShape = new THREE.Shape();
lShape.moveTo(0, w); lShape.lineTo(w*0.4, w*1.5); lShape.lineTo(w*0.1, len*lumeRatio); lShape.lineTo(0, len*lumeRatio + w*0.5); lShape.lineTo(-w*0.1, len*lumeRatio); lShape.lineTo(-w*0.4, w*1.5);
mesh.add(new THREE.Mesh(new THREE.ExtrudeGeometry(lShape, { depth: 0.06, bevelEnabled: false }), mats.lume));
}
const cap = new THREE.Mesh(new THREE.CylinderGeometry(w*1.2, w*1.2, 0.1, 32), mats.steel); cap.rotation.x = Math.PI/2; mesh.add(cap);
return mesh;
}
const hourHand = createHand(4.5, 0.4, 0.8, mats.roseGold); hourHand.position.z = 0.0;
const minHand = createHand(7.5, 0.3, 0.8, mats.roseGold); minHand.position.z = 0.15;
const secHandGrp = new THREE.Group(); secHandGrp.position.set(0, -4.5, 0);
const secHand = createHand(1.8, 0.1, 0, mats.bluedSteel); secHand.position.z = 0.3;
const tail = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 0.05, 32), mats.bluedSteel); tail.rotation.x = Math.PI/2; tail.position.y = -0.6; secHand.add(tail);
secHandGrp.add(secHand); gHands.add(hourHand, minHand, secHandGrp); layers.hands.group.add(gHands);
registerPart(gHands, "剑形夜光指针 (Dauphine Hands)", "手工极致抛光的剑形太妃针,凹槽内填充高强度储光材料。六点位附有蓝钢小秒针。");
// ==========================================
// 6. 交互逻辑与解构视图控制
// ==========================================
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let hoveredObj = null, isExploded = false;
document.getElementById('btn-explode').addEventListener('click', (e) => {
isExploded = !isExploded;
e.target.innerText = isExploded ? "组合机芯 (Assemble)" : "解构视图 (Explode View)";
controls.autoRotate = !isExploded; // 爆炸后停止自动旋转以供细节鉴赏
});
window.addEventListener('mousemove', (e) => {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
const tooltip = document.getElementById('tooltip');
tooltip.style.left = (e.clientX + 15) + 'px';
tooltip.style.top = (e.clientY + 15) + 'px';
});
window.addEventListener('click', () => {
const panel = document.getElementById('info-panel');
if(hoveredObj) {
document.getElementById('info-title').innerText = hoveredObj.userData.name;
document.getElementById('info-desc').innerText = hoveredObj.userData.desc;
panel.classList.add('visible');
} else {
panel.classList.remove('visible');
}
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
});
// ==========================================
// 7. 动画与渲染循环 (Animation Loop)
// ==========================================
const clock = new THREE.Clock();
setTimeout(() => {
const loader = document.getElementById('loader');
loader.style.opacity = '0';
setTimeout(() => loader.remove(), 1000);
}, 1500);
function animate() {
requestAnimationFrame(animate);
const time = clock.getElapsedTime();
// A. 机械模拟与真实时间同步
balanceGrp.rotation.z = Math.sin(time * 8 * Math.PI) * 1.2;
const ticks = Math.floor(time * 8);
escapeGrp.rotation.z = -ticks * (Math.PI / 15);
const now = new Date();
const s = now.getSeconds() + now.getMilliseconds() / 1000;
const m = now.getMinutes() + s / 60;
const h = (now.getHours() % 12) + m / 60;
// 指针运动
secHandGrp.rotation.z = -s * Math.PI / 30;
minHand.rotation.z = -m * Math.PI / 30;
hourHand.rotation.z = -h * Math.PI / 6;
// 齿轮联动对应指针
fourthGrp.rotation.z = -s * Math.PI / 30;
thirdGrp.rotation.z = s * Math.PI / 30 * 1.5;
centerGrp.rotation.z = -m * Math.PI / 30;
barrelGrp.rotation.z = m * Math.PI / 30 * 0.2;
// B. 爆炸解构视图平滑位移 (Lerp)
Object.values(layers).forEach(layer => {
const targetZ = isExploded ? layer.eZ : layer.bZ;
layer.group.position.z += (targetZ - layer.group.position.z) * 0.08;
});
// C. 射线拾取高亮检测
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(interactables, false);
let target = intersects.length > 0 ? intersects[0].object.userData.parentGroup : null;
const tooltip = document.getElementById('tooltip');
if (target !== hoveredObj) {
if (hoveredObj) {
hoveredObj.traverse(c => {
if (c.isMesh && c.userData.origEmissive !== undefined && c.material !== mats.lume) {
c.material.emissive.setHex(c.userData.origEmissive);
}
});
}
hoveredObj = target;
if (hoveredObj) {
document.body.style.cursor = 'pointer';
hoveredObj.traverse(c => {
if (c.isMesh && c.userData.origEmissive !== undefined && c.material !== mats.lume) {
c.material.emissive.setHex(0x3a3a2a); // 奢华金属暗金高亮色
}
});
tooltip.innerText = hoveredObj.userData.name;
tooltip.style.opacity = 1;
} else {
document.body.style.cursor = 'default';
tooltip.style.opacity = 0;
}
}
controls.update();
composer.render();
}
animate();
</script>
</body>
</html>