feat: add skeleton-watch and pelican-cycling for Gemini 3.1 Pro × Gemini Web × Deep Think (#30)
Co-authored-by: 卢盼 <lupan@lupandeMac-Studio.local>
This commit is contained in:
1 parent
ada382b2dc
commit
ffdd06626f
3 files changed
+1096
No files matched your search
@@ -12,6 +12,20 @@
|
||||
"en": "One-shot by Gemini 3.1 Pro (Deep Think) in Gemini Web, unmodified."
|
||||
},
|
||||
"contributor": "lupan-lu"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Gemini Web 中以 Gemini 3.1 Pro(Deep Think)一次生成,未修改。",
|
||||
"en": "One-shot by Gemini 3.1 Pro (Deep Think) in Gemini Web, unmodified."
|
||||
},
|
||||
"contributor": "lupan-lu"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Gemini Web 中以 Gemini 3.1 Pro(Deep Think)一次生成,未修改。",
|
||||
"en": "One-shot by Gemini 3.1 Pro (Deep Think) in Gemini Web, unmodified."
|
||||
},
|
||||
"contributor": "lupan-lu"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800" width="100%" height="100%">
|
||||
<defs>
|
||||
<!-- 天空与太阳渐变 -->
|
||||
<linearGradient id="skyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2a75d3" />
|
||||
<stop offset="45%" stop-color="#64a6ed" />
|
||||
<stop offset="75%" stop-color="#ffdf8e" />
|
||||
<stop offset="100%" stop-color="#ffaa5a" />
|
||||
</linearGradient>
|
||||
|
||||
<radialGradient id="sunGrad" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#ffffff" />
|
||||
<stop offset="30%" stop-color="#ffeb99" />
|
||||
<stop offset="100%" stop-color="#ffaa5a" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
|
||||
<radialGradient id="vignette" cx="50%" cy="50%" r="75%">
|
||||
<stop offset="50%" stop-color="#000" stop-opacity="0" />
|
||||
<stop offset="100%" stop-color="#000" stop-opacity="0.45" />
|
||||
</radialGradient>
|
||||
|
||||
<!-- 海洋渐变 -->
|
||||
<linearGradient id="oceanGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0288D1" />
|
||||
<stop offset="40%" stop-color="#0097A7" />
|
||||
<stop offset="100%" stop-color="#00BCD4" />
|
||||
</linearGradient>
|
||||
<linearGradient id="waveGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#4DD0E1" stop-opacity="0.7" />
|
||||
<stop offset="100%" stop-color="#0288D1" stop-opacity="0.3" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 沙滩渐变 -->
|
||||
<linearGradient id="sandGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#E6C280" />
|
||||
<stop offset="40%" stop-color="#D4A352" />
|
||||
<stop offset="100%" stop-color="#B88035" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 鹈鹕羽毛与身体纹理渐变 -->
|
||||
<radialGradient id="pelicanBody" cx="35%" cy="30%" r="75%">
|
||||
<stop offset="0%" stop-color="#ffffff" />
|
||||
<stop offset="55%" stop-color="#e0e5ec" />
|
||||
<stop offset="85%" stop-color="#a3b1c6" />
|
||||
<stop offset="100%" stop-color="#75869e" />
|
||||
</radialGradient>
|
||||
|
||||
<linearGradient id="neckGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#ffffff" />
|
||||
<stop offset="100%" stop-color="#cbd5e1" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="beakTop" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#FDE047" />
|
||||
<stop offset="60%" stop-color="#F59E0B" />
|
||||
<stop offset="100%" stop-color="#EA580C" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="pouch" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FCD34D" />
|
||||
<stop offset="35%" stop-color="#F97316" />
|
||||
<stop offset="100%" stop-color="#BE123C" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 自行车车架与金属镀铬渐变 -->
|
||||
<linearGradient id="bikeColor" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2DD4BF" />
|
||||
<stop offset="50%" stop-color="#0F766E" />
|
||||
<stop offset="100%" stop-color="#042F2E" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="chrome" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#FFFFFF" />
|
||||
<stop offset="25%" stop-color="#94A3B8" />
|
||||
<stop offset="50%" stop-color="#1E293B" />
|
||||
<stop offset="75%" stop-color="#CBD5E1" />
|
||||
<stop offset="100%" stop-color="#FFFFFF" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 滤镜效果 -->
|
||||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="15" result="blur" />
|
||||
<feComposite in="SourceGraphic" in2="blur" operator="over" />
|
||||
</filter>
|
||||
|
||||
<filter id="shadow" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feDropShadow dx="8" dy="15" stdDeviation="10" flood-color="#000000" flood-opacity="0.35" />
|
||||
</filter>
|
||||
|
||||
<filter id="lightShadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="3" dy="5" stdDeviation="4" flood-color="#000000" flood-opacity="0.3" />
|
||||
</filter>
|
||||
|
||||
<filter id="blurShadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="20" />
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<style>
|
||||
/* CSS 动画:为画面注入“生动”的气息 */
|
||||
@keyframes spin { 100% { transform: rotate(360deg); } }
|
||||
@keyframes drift { 100% { transform: translateX(-1400px); } }
|
||||
@keyframes wave { 0% { transform: translateX(0px); } 100% { transform: translateX(-60px); } }
|
||||
@keyframes bounce { 0% { transform: translateY(0px); } 100% { transform: translateY(5px); } }
|
||||
@keyframes flutter { 0% { transform: rotate(0deg) scaleY(1); } 100% { transform: rotate(8deg) scaleY(1.05); } }
|
||||
@keyframes flap { 0% { transform: rotate(-5deg); } 100% { transform: rotate(5deg); } }
|
||||
@keyframes wobble { 0% { transform: rotate(0deg) scale(1); } 100% { transform: rotate(2deg) scale(1.02); } }
|
||||
@keyframes sand { 0% { transform: translate(0px, 0px) scale(1); opacity: 1; } 100% { transform: translate(-100px, -40px) scale(0.3); opacity: 0; } }
|
||||
@keyframes speed { 0% { transform: translateX(300px); opacity: 0; } 15% { opacity: 0.6; } 85% { opacity: 0.6; } 100% { transform: translateX(-1400px); opacity: 0; } }
|
||||
@keyframes bird-fly { 0% { transform: translateY(0px); } 50% { transform: translateY(-4px); } 100% { transform: translateY(0px); } }
|
||||
|
||||
.wheel { transform-origin: 0px 0px; animation: spin 0.6s linear infinite; }
|
||||
.cloud { animation: drift linear infinite; }
|
||||
.wave { animation: wave 3.5s ease-in-out infinite alternate; }
|
||||
.bounce { animation: bounce 0.4s ease-in-out infinite alternate; }
|
||||
.scarf { transform-origin: 700px 230px; animation: flutter 0.15s ease-in-out infinite alternate; }
|
||||
.wing { transform-origin: 640px 280px; animation: flap 0.3s ease-in-out infinite alternate; }
|
||||
.pouch-anim { transform-origin: 800px 165px; animation: wobble 0.2s ease-in-out infinite alternate; }
|
||||
.sand { animation: sand 0.5s linear infinite; }
|
||||
.speed { animation: speed linear infinite; }
|
||||
.bird { animation: bird-fly 0.7s infinite; }
|
||||
</style>
|
||||
|
||||
<!-- ================= 背景层 ================= -->
|
||||
<!-- 天空 -->
|
||||
<rect width="1200" height="800" fill="url(#skyGrad)" />
|
||||
|
||||
<!-- 太阳与光晕 -->
|
||||
<circle cx="1000" cy="300" r="140" fill="url(#sunGrad)" filter="url(#glow)" />
|
||||
<circle cx="1000" cy="300" r="55" fill="#FFF" filter="url(#glow)" />
|
||||
|
||||
<!-- 速度/风线 (背景) -->
|
||||
<g stroke="#FFFFFF" stroke-linecap="round" opacity="0.4">
|
||||
<line x1="800" y1="200" x2="1100" y2="200" stroke-width="4" class="speed" style="animation-delay: 0s; animation-duration: 2s;" />
|
||||
<line x1="300" y1="120" x2="550" y2="120" stroke-width="3" class="speed" style="animation-delay: 0.5s; animation-duration: 2.5s;" />
|
||||
<line x1="150" y1="350" x2="400" y2="350" stroke-width="2" class="speed" style="animation-delay: 1.2s; animation-duration: 1.8s;" />
|
||||
</g>
|
||||
|
||||
<!-- 远处的飞鸟 -->
|
||||
<g fill="none" stroke="#1E293B" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.5">
|
||||
<path class="bird" style="animation-delay: 0s;" d="M 300 150 Q 315 130 330 150 Q 345 130 360 150" />
|
||||
<path class="bird" style="animation-delay: -0.3s;" d="M 380 120 Q 390 105 400 120 Q 410 105 420 120" />
|
||||
<path class="bird" style="animation-delay: -0.6s;" d="M 250 180 Q 260 165 270 180 Q 280 165 290 180" />
|
||||
</g>
|
||||
|
||||
<!-- 漂浮的云朵 -->
|
||||
<g fill="#FFFFFF" opacity="0.8">
|
||||
<path class="cloud" style="animation-duration: 45s;" d="M 120 180 Q 150 150, 190 170 Q 230 140, 280 180 Q 320 170, 340 210 L 90 210 Q 70 180, 120 180 Z" />
|
||||
<path class="cloud" style="animation-duration: 35s; animation-delay: -15s;" d="M 750 120 Q 790 90, 830 120 Q 890 90, 930 140 Q 970 130, 990 170 L 710 170 Q 700 140, 750 120 Z" transform="scale(0.8) translate(300, 0)" />
|
||||
<path class="cloud" style="animation-duration: 55s; animation-delay: -30s;" d="M 450 250 Q 480 230, 510 250 Q 550 220, 590 260 Q 620 250, 640 280 L 420 280 Q 410 260, 450 250 Z" opacity="0.6" />
|
||||
</g>
|
||||
|
||||
<!-- ================= 中景层 (海洋) ================= -->
|
||||
<rect x="0" y="450" width="1200" height="150" fill="url(#oceanGrad)" />
|
||||
|
||||
<g fill="url(#waveGrad)">
|
||||
<path class="wave" d="M 0 460 Q 150 440, 300 470 T 600 460 T 900 470 T 1200 460 L 1200 600 L 0 600 Z" />
|
||||
<path class="wave" style="animation-delay: -1.5s; animation-duration: 4s;" d="M 0 480 Q 200 500, 400 470 T 800 480 T 1200 490 L 1200 600 L 0 600 Z" fill="#0097A7" opacity="0.8"/>
|
||||
</g>
|
||||
|
||||
<!-- 海浪泡沫 -->
|
||||
<path class="wave" d="M 0 540 Q 250 520, 500 550 T 1000 540 T 1200 550 L 1200 600 L 0 600 Z" fill="#E0F2FE" opacity="0.85" />
|
||||
<path class="wave" style="animation-delay: -2s;" d="M 0 560 Q 300 545, 600 565 T 1200 555 L 1200 600 L 0 600 Z" fill="#F8FAFC" opacity="0.9" />
|
||||
|
||||
<!-- ================= 前景层 (沙滩与阴影) ================= -->
|
||||
<path d="M 0 570 Q 300 540, 600 570 T 1200 550 L 1200 800 L 0 800 Z" fill="url(#sandGrad)" />
|
||||
|
||||
<!-- 湿润沙滩的高光反射 -->
|
||||
<path d="M 0 575 Q 300 545, 600 575 T 1200 555 L 1200 580 T 600 595 Q 300 565, 0 590 Z" fill="#FFF3E0" opacity="0.3" />
|
||||
|
||||
<!-- 地面总体投影 -->
|
||||
<g fill="#A17430" opacity="0.55" filter="url(#blurShadow)">
|
||||
<ellipse cx="600" cy="720" rx="360" ry="25" />
|
||||
<ellipse cx="350" cy="715" rx="80" ry="15" />
|
||||
<ellipse cx="850" cy="715" rx="80" ry="15" />
|
||||
</g>
|
||||
|
||||
<!-- ================= 自行车与鹈鹕主体 ================= -->
|
||||
|
||||
<!-- 后轮 (带旋转动画和运动模糊) -->
|
||||
<g transform="translate(350, 600)" filter="url(#lightShadow)">
|
||||
<g class="wheel">
|
||||
<!-- 轮胎 -->
|
||||
<circle cx="0" cy="0" r="110" fill="none" stroke="#1c1c1c" stroke-width="20" />
|
||||
<circle cx="0" cy="0" r="120" fill="none" stroke="#000" stroke-width="2" stroke-dasharray="6 6" />
|
||||
<circle cx="0" cy="0" r="98" fill="none" stroke="#F8FAFC" stroke-width="6" />
|
||||
<!-- 轮辋 -->
|
||||
<circle cx="0" cy="0" r="92" fill="none" stroke="url(#chrome)" stroke-width="8" />
|
||||
<!-- 辐条 (高速旋转的视觉效果) -->
|
||||
<g stroke="#94A3B8" stroke-width="2" opacity="0.8">
|
||||
<line x1="0" y1="-92" x2="0" y2="92" />
|
||||
<line x1="-92" y1="0" x2="92" y2="0" />
|
||||
<line x1="-65" y1="-65" x2="65" y2="65" />
|
||||
<line x1="-65" y1="65" x2="65" y2="-65" />
|
||||
<line x1="-35" y1="-85" x2="35" y2="85" />
|
||||
<line x1="-85" y1="-35" x2="85" y2="35" />
|
||||
<line x1="-35" y1="85" x2="35" y2="-85" />
|
||||
<line x1="-85" y1="35" x2="85" y2="-35" />
|
||||
</g>
|
||||
<!-- 运动模糊层 -->
|
||||
<circle cx="0" cy="0" r="60" fill="none" stroke="#E2E8F0" stroke-width="120" stroke-dasharray="10 30" opacity="0.1" />
|
||||
<!-- 轮毂 -->
|
||||
<circle cx="0" cy="0" r="14" fill="url(#chrome)" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 左踏板与曲柄 (处于背景静止滑行状态) -->
|
||||
<line x1="550" y1="600" x2="505" y2="600" stroke="#94A3B8" stroke-width="8" stroke-linecap="round" />
|
||||
<rect x="490" y="595" width="30" height="10" rx="3" fill="#1E293B" />
|
||||
|
||||
<!-- 鹈鹕左腿 (背景) -->
|
||||
<g>
|
||||
<path d="M 520 480 L 515 590 L 495 595 L 500 490 Z" fill="#EA580C" />
|
||||
<path d="M 495 595 L 535 605 L 545 615 L 510 620 L 485 605 Z" fill="#EA580C" />
|
||||
</g>
|
||||
|
||||
<!-- 车架及配件 -->
|
||||
<g filter="url(#shadow)">
|
||||
<!-- 后挡泥板 -->
|
||||
<path d="M 245 600 A 115 115 0 0 1 455 550" fill="none" stroke="url(#chrome)" stroke-width="12" stroke-linecap="round" />
|
||||
<line x1="350" y1="600" x2="280" y2="520" stroke="url(#chrome)" stroke-width="4" />
|
||||
|
||||
<!-- 翠蓝色主车架 -->
|
||||
<g stroke="url(#bikeColor)" stroke-width="16" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="350" y1="600" x2="550" y2="600" />
|
||||
<line x1="350" y1="600" x2="500" y2="420" />
|
||||
<line x1="550" y1="600" x2="480" y2="360" />
|
||||
<line x1="550" y1="600" x2="820" y2="480" />
|
||||
<path d="M 500 420 Q 650 450, 800 380" fill="none" />
|
||||
<line x1="800" y1="380" x2="820" y2="480" stroke-width="20" />
|
||||
<path d="M 820 480 Q 840 550, 850 600" fill="none" stroke-width="14" />
|
||||
</g>
|
||||
|
||||
<!-- 车把立管与左侧把手 -->
|
||||
<line x1="800" y1="380" x2="790" y2="340" stroke="url(#chrome)" stroke-width="14" stroke-linecap="round" />
|
||||
<path d="M 790 340 Q 760 320, 730 300" fill="none" stroke="url(#chrome)" stroke-width="12" stroke-linecap="round" />
|
||||
<line x1="730" y1="300" x2="715" y2="285" stroke="#334155" stroke-width="16" stroke-linecap="round" />
|
||||
|
||||
<!-- 鹈鹕的左翼 (在背景握住左侧把手) -->
|
||||
<path d="M 640 270 Q 680 240, 720 290 Q 670 300, 630 290 Z" fill="#94A3B8" />
|
||||
<circle cx="720" cy="290" r="10" fill="#94A3B8" />
|
||||
|
||||
<!-- 复古真皮车座 -->
|
||||
<path d="M 475 360 Q 515 340, 545 360 Q 555 375, 540 380 L 465 380 Z" fill="#3F2E27" />
|
||||
<path d="M 470 380 L 535 380 L 525 390 L 480 390 Z" fill="#291D17" />
|
||||
<circle cx="485" cy="395" r="4" fill="none" stroke="url(#chrome)" stroke-width="3" />
|
||||
<circle cx="505" cy="395" r="4" fill="none" stroke="url(#chrome)" stroke-width="3" />
|
||||
|
||||
<!-- 传动系统 (链条与齿轮) -->
|
||||
<circle cx="350" cy="600" r="18" fill="#1E293B" />
|
||||
<circle cx="550" cy="600" r="35" fill="none" stroke="#455A64" stroke-width="8" />
|
||||
<circle cx="550" cy="600" r="25" fill="none" stroke="#94A3B8" stroke-width="4" stroke-dasharray="8 6" />
|
||||
<!-- 链条 -->
|
||||
<path d="M 350 582 L 550 565" stroke="#333" stroke-width="4" stroke-dasharray="6 4" />
|
||||
<path d="M 350 618 L 550 635" stroke="#333" stroke-width="4" stroke-dasharray="6 4" />
|
||||
<circle cx="550" cy="600" r="12" fill="url(#chrome)" />
|
||||
</g>
|
||||
|
||||
<!-- 右侧踏板与曲柄 (前景) -->
|
||||
<g filter="url(#shadow)">
|
||||
<line x1="550" y1="600" x2="595" y2="600" stroke="#E2E8F0" stroke-width="10" stroke-linecap="round" />
|
||||
<rect x="580" y="595" width="30" height="10" rx="3" fill="#0F172A" />
|
||||
</g>
|
||||
|
||||
|
||||
<!-- ================= 核心角色:生动的鹈鹕 (加入弹跳与飘动特效) ================= -->
|
||||
<!-- 通过 bounce 类模拟在沙滩骑行时的颠簸感 -->
|
||||
<g class="bounce">
|
||||
<!-- 尾巴羽毛 -->
|
||||
<g filter="url(#shadow)">
|
||||
<path d="M 450 360 L 320 390 L 350 340 Z" fill="#E2E8F0" stroke="#CBD5E1" stroke-width="2"/>
|
||||
<path d="M 440 380 L 310 420 L 370 370 Z" fill="#F1F5F9" stroke="#CBD5E1" stroke-width="2"/>
|
||||
</g>
|
||||
|
||||
<!-- 逼真的身躯与脖子 -->
|
||||
<g filter="url(#shadow)">
|
||||
<path d="M 480 340 C 450 220, 600 200, 680 250 C 730 280, 700 380, 600 390 C 520 400, 490 380, 480 340 Z" fill="url(#pelicanBody)" />
|
||||
<path d="M 660 260 Q 720 200, 780 150 L 820 180 Q 750 250, 680 300 Z" fill="url(#neckGrad)" />
|
||||
|
||||
<!-- 羽毛层次与高光遮罩 -->
|
||||
<path d="M 500 320 Q 550 350, 520 380" fill="none" stroke="#FFFFFF" stroke-width="5" stroke-linecap="round" opacity="0.7"/>
|
||||
<path d="M 540 300 Q 600 340, 560 380" fill="none" stroke="#FFFFFF" stroke-width="5" stroke-linecap="round" opacity="0.6"/>
|
||||
<path d="M 580 310 Q 640 340, 600 380" fill="none" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" opacity="0.4"/>
|
||||
</g>
|
||||
|
||||
<!-- 头部、发冠与飞行员护目镜 -->
|
||||
<g filter="url(#shadow)">
|
||||
<!-- 迎风竖起的羽冠 -->
|
||||
<path d="M 800 150 Q 760 100, 730 130 Q 760 140, 780 160 Z" fill="#FFFFFF" />
|
||||
<path d="M 790 140 Q 740 80, 710 110 Q 750 130, 770 150 Z" fill="#E2E8F0" />
|
||||
<circle cx="805" cy="155" r="28" fill="#FFFFFF" />
|
||||
|
||||
<!-- 锐利的眼睛 -->
|
||||
<circle cx="810" cy="145" r="10" fill="#FDE047" stroke="#EA580C" stroke-width="1.5" />
|
||||
<circle cx="812" cy="145" r="5" fill="#09090B" />
|
||||
<circle cx="814" cy="143" r="2.5" fill="#FFFFFF" />
|
||||
|
||||
<!-- 推上额头的飞行员防风镜 -->
|
||||
<path d="M 780 150 Q 805 130, 830 150" fill="none" stroke="#1E293B" stroke-width="6" />
|
||||
<ellipse cx="800" cy="120" rx="14" ry="20" fill="#0F172A" stroke="#94A3B8" stroke-width="4" transform="rotate(15 800 120)" />
|
||||
<path d="M 795 110 L 805 125" stroke="#FFFFFF" stroke-width="3" opacity="0.6" transform="rotate(15 800 120)" />
|
||||
<ellipse cx="830" cy="125" rx="14" ry="20" fill="#0F172A" stroke="#94A3B8" stroke-width="4" transform="rotate(25 830 125)" />
|
||||
<path d="M 825 115 L 835 130" stroke="#FFFFFF" stroke-width="3" opacity="0.6" transform="rotate(25 830 125)" />
|
||||
</g>
|
||||
|
||||
<!-- 标志性的大嘴与随风抖动的喉囊 -->
|
||||
<g filter="url(#shadow)">
|
||||
<!-- 上喙 -->
|
||||
<path d="M 800 150 Q 950 140, 1100 180 Q 1120 190, 1105 195 Q 950 160, 800 165 Z" fill="url(#beakTop)" stroke="#B45309" stroke-width="1.5" />
|
||||
<path d="M 1105 195 Q 1115 205, 1100 210 Q 1105 205, 1105 195 Z" fill="#C2410C" />
|
||||
|
||||
<!-- 喉囊 (配合 CSS 产生抖动效果) -->
|
||||
<g class="pouch-anim">
|
||||
<path d="M 800 165 Q 950 160, 1100 190 Q 1050 350, 900 300 C 800 250, 780 200, 800 165 Z" fill="url(#pouch)" opacity="0.95" stroke="#9F1239" stroke-width="2" />
|
||||
<!-- 喉囊毛细血管/张力纹理 -->
|
||||
<path d="M 820 200 Q 900 250, 1000 240" fill="none" stroke="#E11D48" stroke-width="3" opacity="0.4" />
|
||||
<path d="M 810 230 Q 880 280, 950 270" fill="none" stroke="#E11D48" stroke-width="2" opacity="0.3" />
|
||||
<path d="M 840 260 Q 890 300, 930 290" fill="none" stroke="#E11D48" stroke-width="2" opacity="0.25" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 红色拉风围巾 -->
|
||||
<g filter="url(#shadow)">
|
||||
<path d="M 700 230 C 720 250, 750 250, 780 230 C 770 210, 720 200, 700 230 Z" fill="#DC2626" />
|
||||
<g class="scarf">
|
||||
<path d="M 720 240 C 600 260, 500 150, 350 200 C 450 250, 550 320, 700 250 Z" fill="#EF4444" />
|
||||
<path d="M 650 240 L 630 255" stroke="#FFFFFF" stroke-width="8" />
|
||||
<path d="M 580 215 L 560 235" stroke="#FFFFFF" stroke-width="8" />
|
||||
<path d="M 510 190 L 490 210" stroke="#FFFFFF" stroke-width="6" />
|
||||
<path d="M 440 185 L 430 205" stroke="#FFFFFF" stroke-width="5" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 右翼 (延伸并握住车把,带有动态拍打效果) -->
|
||||
<g class="wing" filter="url(#shadow)">
|
||||
<!-- 翅膀基本形状 -->
|
||||
<path d="M 640 280 Q 750 250, 830 310 Q 750 330, 660 320 Z" fill="#FFFFFF" />
|
||||
<path d="M 750 280 Q 650 380, 550 350 Q 650 320, 700 270 Z" fill="#CBD5E1" />
|
||||
<!-- 分明的飞羽层次 -->
|
||||
<path d="M 780 295 Q 680 420, 550 380 Q 680 340, 720 285 Z" fill="#0F172A" />
|
||||
<path d="M 810 310 Q 720 450, 580 420 Q 720 370, 760 300 Z" fill="#334155" />
|
||||
<!-- 包裹车把的“手” -->
|
||||
<circle cx="830" cy="310" r="14" fill="#E2E8F0" />
|
||||
<path d="M 825 300 Q 840 310, 825 320" fill="none" stroke="#94A3B8" stroke-width="3" />
|
||||
</g>
|
||||
</g> <!-- 弹跳组结束 -->
|
||||
|
||||
<!-- 右腿 (前景,位于弹跳组之外以保持踩踏在踏板上的稳定性) -->
|
||||
<g filter="url(#shadow)">
|
||||
<!-- 肌肉感的大腿 -->
|
||||
<path d="M 600 370 Q 640 450, 610 500 Q 570 450, 560 380 Z" fill="url(#pelicanBody)" />
|
||||
<!-- 带鳞片的细长腿 -->
|
||||
<path d="M 590 480 L 605 590 L 585 595 L 570 490 Z" fill="#F97316" stroke="#C2410C" stroke-width="2" />
|
||||
<!-- 紧扣踏板的脚蹼 -->
|
||||
<path d="M 585 595 L 630 610 L 640 625 L 600 625 L 575 605 Z" fill="#F97316" stroke="#C2410C" stroke-width="2" stroke-linejoin="round" />
|
||||
</g>
|
||||
|
||||
|
||||
<!-- ================= 前车架及挂件细节 ================= -->
|
||||
<g filter="url(#shadow)">
|
||||
<!-- 右侧车把及皮质把套 -->
|
||||
<path d="M 790 340 Q 820 320, 840 310" fill="none" stroke="url(#chrome)" stroke-width="12" stroke-linecap="round" />
|
||||
<line x1="840" y1="310" x2="855" y2="300" stroke="#334155" stroke-width="16" stroke-linecap="round" />
|
||||
|
||||
<!-- 前挡泥板 -->
|
||||
<path d="M 745 600 A 115 115 0 0 1 955 550" fill="none" stroke="url(#chrome)" stroke-width="12" stroke-linecap="round" />
|
||||
<line x1="850" y1="600" x2="780" y2="520" stroke="url(#chrome)" stroke-width="4" />
|
||||
|
||||
<!-- 充满生活气息的柳条筐与新鲜捕获的鱼 -->
|
||||
<g transform="translate(835, 340)">
|
||||
<!-- 跃出筐外的鱼 -->
|
||||
<g transform="translate(20, -10) rotate(15)">
|
||||
<path d="M 0 0 Q 15 -20, 30 -5 Q 15 10, 0 0 Z" fill="#38BDF8" />
|
||||
<path d="M 30 -5 L 42 -15 L 42 5 Z" fill="#0284C7" />
|
||||
<circle cx="8" cy="-3" r="2.5" fill="#FFFFFF" />
|
||||
<circle cx="8" cy="-3" r="1" fill="#0F172A" />
|
||||
</g>
|
||||
<g transform="translate(5, -5) rotate(45)">
|
||||
<path d="M 0 0 Q 15 -20, 30 -5 Q 15 10, 0 0 Z" fill="#7DD3FC" />
|
||||
<path d="M 30 -5 L 42 -15 L 42 5 Z" fill="#0284C7" />
|
||||
<circle cx="8" cy="-3" r="2.5" fill="#FFFFFF" />
|
||||
<circle cx="8" cy="-3" r="1" fill="#0F172A" />
|
||||
</g>
|
||||
<!-- 柳条筐主体 -->
|
||||
<path d="M 0 0 L 45 0 L 35 55 L 5 55 Z" fill="#D4A373" stroke="#9A7B4F" stroke-width="3" stroke-linejoin="round" />
|
||||
<!-- 细致的编织纹理 (横向) -->
|
||||
<line x1="5" y1="12" x2="42" y2="12" stroke="#9A7B4F" stroke-width="2.5"/>
|
||||
<line x1="6" y1="24" x2="39" y2="24" stroke="#9A7B4F" stroke-width="2.5"/>
|
||||
<line x1="7" y1="36" x2="37" y2="36" stroke="#9A7B4F" stroke-width="2.5"/>
|
||||
<line x1="8" y1="48" x2="34" y2="48" stroke="#9A7B4F" stroke-width="2.5"/>
|
||||
<!-- 细致的编织纹理 (纵向) -->
|
||||
<line x1="12" y1="0" x2="15" y2="55" stroke="#9A7B4F" stroke-width="2.5"/>
|
||||
<line x1="22" y1="0" x2="20" y2="55" stroke="#9A7B4F" stroke-width="2.5"/>
|
||||
<line x1="32" y1="0" x2="25" y2="55" stroke="#9A7B4F" stroke-width="2.5"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- 前轮 (带旋转与模糊特效) -->
|
||||
<g transform="translate(850, 600)" filter="url(#lightShadow)">
|
||||
<g class="wheel">
|
||||
<circle cx="0" cy="0" r="110" fill="none" stroke="#1c1c1c" stroke-width="20" />
|
||||
<circle cx="0" cy="0" r="120" fill="none" stroke="#000" stroke-width="2" stroke-dasharray="6 6" />
|
||||
<circle cx="0" cy="0" r="98" fill="none" stroke="#F8FAFC" stroke-width="6" />
|
||||
<circle cx="0" cy="0" r="92" fill="none" stroke="url(#chrome)" stroke-width="8" />
|
||||
<!-- 辐条 -->
|
||||
<g stroke="#94A3B8" stroke-width="2" opacity="0.8">
|
||||
<line x1="0" y1="-92" x2="0" y2="92" />
|
||||
<line x1="-92" y1="0" x2="92" y2="0" />
|
||||
<line x1="-65" y1="-65" x2="65" y2="65" />
|
||||
<line x1="-65" y1="65" x2="65" y2="-65" />
|
||||
<line x1="-35" y1="-85" x2="35" y2="85" />
|
||||
<line x1="-85" y1="-35" x2="85" y2="35" />
|
||||
<line x1="-35" y1="85" x2="35" y2="-85" />
|
||||
<line x1="-85" y1="35" x2="85" y2="-35" />
|
||||
</g>
|
||||
<!-- 极速行驶的光环特效层 -->
|
||||
<circle cx="0" cy="0" r="60" fill="none" stroke="#E2E8F0" stroke-width="120" stroke-dasharray="10 30" opacity="0.1" />
|
||||
<circle cx="0" cy="0" r="14" fill="url(#chrome)" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
||||
<!-- ================= 动感细节与环境装饰 ================= -->
|
||||
|
||||
<!-- 车轮卷起的飞沙 (动画) -->
|
||||
<g fill="#D4A352" opacity="0.9">
|
||||
<!-- 后轮沙尘 -->
|
||||
<g class="sand" style="animation-delay: 0s;"><circle cx="280" cy="700" r="6" /></g>
|
||||
<g class="sand" style="animation-delay: -0.15s;"><circle cx="250" cy="680" r="4.5" /></g>
|
||||
<g class="sand" style="animation-delay: -0.3s;"><circle cx="230" cy="715" r="7" /></g>
|
||||
<g class="sand" style="animation-delay: -0.45s;"><circle cx="200" cy="670" r="3" /></g>
|
||||
<g class="sand" style="animation-delay: -0.6s;"><circle cx="260" cy="660" r="5" /></g>
|
||||
<!-- 前轮沙尘 -->
|
||||
<g class="sand" style="animation-delay: -0.2s;"><circle cx="780" cy="700" r="5" /></g>
|
||||
<g class="sand" style="animation-delay: -0.5s;"><circle cx="750" cy="710" r="4" /></g>
|
||||
</g>
|
||||
|
||||
<!-- 前景速度线 / 风影 -->
|
||||
<g stroke="#FFFFFF" stroke-linecap="round" opacity="0.5">
|
||||
<line x1="800" y1="400" x2="1100" y2="400" stroke-width="5" class="speed" style="animation-delay: 0.3s; animation-duration: 1.5s;" />
|
||||
<line x1="200" y1="500" x2="500" y2="500" stroke-width="3" class="speed" style="animation-delay: 1s; animation-duration: 2.2s;" />
|
||||
</g>
|
||||
|
||||
<!-- 欢呼助威的小螃蟹 -->
|
||||
<g transform="translate(180, 720)" filter="url(#lightShadow)">
|
||||
<!-- 挥舞的八条腿 -->
|
||||
<path d="M -15 0 Q -25 -10 -30 5" fill="none" stroke="#E11D48" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M -10 5 Q -20 0 -25 15" fill="none" stroke="#E11D48" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M 15 0 Q 25 -10 30 5" fill="none" stroke="#E11D48" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M 10 5 Q 20 0 25 15" fill="none" stroke="#E11D48" stroke-width="3" stroke-linecap="round"/>
|
||||
<!-- 举起的大钳子 -->
|
||||
<path d="M -10 -5 Q -25 -25 -10 -30" fill="none" stroke="#E11D48" stroke-width="4" stroke-linecap="round"/>
|
||||
<circle cx="-10" cy="-30" r="6" fill="#E11D48"/>
|
||||
<path d="M -10 -30 L -18 -40 L -3 -35 Z" fill="#E11D48"/>
|
||||
|
||||
<path d="M 10 -5 Q 25 -25 10 -30" fill="none" stroke="#E11D48" stroke-width="4" stroke-linecap="round"/>
|
||||
<circle cx="10" cy="-30" r="6" fill="#E11D48"/>
|
||||
<path d="M 10 -30 L 18 -40 L 3 -35 Z" fill="#E11D48"/>
|
||||
|
||||
<!-- 螃蟹身体和眼睛 -->
|
||||
<ellipse cx="0" cy="0" rx="18" ry="12" fill="#BE123C" />
|
||||
<circle cx="-6" cy="-10" r="2.5" fill="#FFFFFF" />
|
||||
<circle cx="6" cy="-10" r="2.5" fill="#FFFFFF" />
|
||||
<circle cx="-6" cy="-10" r="1.5" fill="#000000" />
|
||||
<circle cx="6" cy="-10" r="1.5" fill="#000000" />
|
||||
</g>
|
||||
|
||||
<!-- 艳丽的海星 -->
|
||||
<g transform="translate(1020, 700) rotate(20) scale(0.9)" filter="url(#lightShadow)">
|
||||
<path d="M 0 -20 L 5 -5 L 20 -5 L 8 5 L 12 20 L 0 10 L -12 20 L -8 5 L -20 -5 L -5 -5 Z" fill="#F43F5E" stroke="#9F1239" stroke-width="2" stroke-linejoin="round" />
|
||||
<circle cx="0" cy="0" r="2.5" fill="#FFF" />
|
||||
<circle cx="0" cy="-9" r="1.5" fill="#FFF" opacity="0.8"/>
|
||||
<circle cx="9" cy="-2" r="1.5" fill="#FFF" opacity="0.8"/>
|
||||
<circle cx="-9" cy="-2" r="1.5" fill="#FFF" opacity="0.8"/>
|
||||
<circle cx="6" cy="9" r="1.5" fill="#FFF" opacity="0.8"/>
|
||||
<circle cx="-6" cy="9" r="1.5" fill="#FFF" opacity="0.8"/>
|
||||
</g>
|
||||
|
||||
<!-- 散落的贝壳 -->
|
||||
<path d="M 80 650 Q 90 640, 100 650 C 95 655, 85 655, 80 650 Z" fill="#F8FAFC" stroke="#CBD5E1" stroke-width="1.5" />
|
||||
<path d="M 900 680 Q 910 670, 915 685 C 905 690, 895 685, 900 680 Z" fill="#FFEDD5" stroke="#FDBA74" stroke-width="1.5" />
|
||||
|
||||
<!-- ================= 电影级全局滤镜层 ================= -->
|
||||
<!-- 暗角效果 (增强聚焦感) -->
|
||||
<rect width="1200" height="800" fill="url(#vignette)" style="pointer-events:none;" />
|
||||
<!-- 落日暖色叠加 -->
|
||||
<rect width="1200" height="800" fill="#FF9800" opacity="0.08" style="mix-blend-mode: color-burn; pointer-events:none;" />
|
||||
<!-- 海风冷色叠加 -->
|
||||
<rect width="1200" height="800" fill="#2DD4BF" opacity="0.04" style="mix-blend-mode: overlay; pointer-events:none;" />
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,591 @@
|
||||
<!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>
|
||||
Reference in new issue
Block a user