Files
shengsuantech-home/js/space.js
T
leefer d5d4623aa9 feat: 电影级真实感重构 — 真实月面/银河/泛光/撕裂破碎
按用户参考图(破碎月亮)对齐的整套真实感升级:
- 月球: SSS/NASA 2K真实纹理+运行时亮度转凹凸, 顶点撕裂伤口(不规则锯齿轮廓)+发光裂缝(emissive放射纹+断面代码)+伤口辉光脉动
- 破碎: 2块大碎片(顶点扰动)+220块Instanced碎石量级谱(伤口切线拖带,各自漂移翻滚)+5团青灰尘埃云(烟尘流动)
- 太空: 真实星野天球(SSS银河2K)替换程序点星, 保留近景尘埃视差层
- 太阳: 亮度>1的小烈核心(推过泛光阈值)+克制光晕+变形宽银幕横向光纹
- 渲染: ACES电影色调映射+UnrealBloomPass泛光(three.module+8个后处理文件本地化,importmap加载)
- 移动端降级: 关泛光+减粒子
- 页脚: NASA/SSS纹理CC-BY署名
2026-08-02 13:11:16 +08:00

643 lines
26 KiB
JavaScript

/* ============================================================
晟算科技 · 太空场景引擎 v3 —— 电影级真实感
真实月面(NASA/SSS) + 真实星野(银河) + ACES调色 + 泛光
撕裂伤口 + 碎石量级谱 + 尘埃云
模块加载(import map), 模式: journey / orbit
============================================================ */
import * as THREE from "three";
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
if (!window.SPACE_MODE) {
window.SPACE_MODE = document.body.getAttribute("data-space") || "journey";
}
(function () {
"use strict";
var isMobile = window.innerWidth < 768 || "ontouchstart" in window;
var reducedMotion = window.matchMedia("(prefers-reduced-motion: reduced)").matches;
var bootEl = document.getElementById("spaceBoot");
var canvasHost = document.getElementById("spaceCanvas");
var isJourney = window.SPACE_MODE === "journey";
function fallback() {
if (bootEl) bootEl.remove();
document.body.classList.add("space-fallback", "space-ready");
}
var testC = document.createElement("canvas");
if (!(testC.getContext("webgl") || testC.getContext("experimental-webgl"))) { fallback(); return; }
/* ============================================================
工具
============================================================ */
function glowTexture(inner, mid, size) {
var c = document.createElement("canvas");
c.width = c.height = size;
var x = c.getContext("2d");
var g = x.createRadialGradient(size/2, size/2, 0, size/2, size/2, size/2);
g.addColorStop(0, inner);
g.addColorStop(0.25, inner.replace(/[\d.]+\)$/, "0.5)"));
g.addColorStop(0.55, mid.replace(/[\d.]+\)$/, "0.18)"));
g.addColorStop(0.8, mid.replace(/[\d.]+\)$/, "0.05)"));
g.addColorStop(1, "rgba(0,0,0,0)");
x.fillStyle = g;
x.fillRect(0, 0, size, size);
return new THREE.CanvasTexture(c);
}
// Three.js SphereGeometry 的标准 UV 映射
function uvToSphere(u, v, r) {
var phi = 2 * Math.PI * u;
var theta = Math.PI * v;
return new THREE.Vector3(
-r * Math.cos(phi) * Math.sin(theta),
r * Math.cos(theta),
r * Math.sin(phi) * Math.sin(theta)
);
}
// 冲击点: 朝向 +x+z (首页相机可见侧)
var IMPACT = { u: 0.334, v: 0.47 };
/* ============================================================
场景与渲染器(ACES电影调色)
============================================================ */
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(55, innerWidth / innerHeight, 0.1, 3000);
var rig = new THREE.Group();
rig.add(camera);
scene.add(rig);
var renderer;
try {
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "high-performance" });
} catch (e) { fallback(); return; }
renderer.setPixelRatio(Math.min(devicePixelRatio, isMobile ? 1.2 : 1.6));
renderer.setSize(innerWidth, innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping; // 电影级色调映射
renderer.toneMappingExposure = 1.15;
renderer.outputColorSpace = THREE.SRGBColorSpace;
canvasHost.appendChild(renderer.domElement);
/* ---------- 泛光(只有高亮物体发光: 太阳/裂缝/代码粒子) ---------- */
var composer = null, bloomPass = null;
if (!isMobile) {
composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
bloomPass = new UnrealBloomPass(new THREE.Vector2(innerWidth, innerHeight), 0.85, 0.55, 0.78);
composer.addPass(bloomPass);
}
/* ============================================================
资产加载(真实纹理)
============================================================ */
var texLoader = new THREE.TextureLoader();
var loadState = { moon: false, sky: false };
/* ---------- 真实星野天球(银河横贯) ---------- */
var skyMesh = null;
texLoader.load("images/starfield.jpg", function (tex) {
tex.colorSpace = THREE.SRGBColorSpace;
tex.mapping = THREE.EquirectangularReflectionMapping;
var mat = new THREE.MeshBasicMaterial({ map: tex, side: THREE.BackSide, depthWrite: false });
mat.fog = false;
skyMesh = new THREE.Mesh(new THREE.SphereGeometry(1200, 48, 32), mat);
skyMesh.rotation.y = 2.2; // 让银河带处于好看的方位
skyMesh.rotation.z = 0.12;
scene.add(skyMesh);
loadState.sky = true;
}, undefined, function () { loadState.sky = true; });
/* ---------- 近景尘埃(保留视差最快层) ---------- */
function makeDustLayer(count, spread) {
var geo = new THREE.BufferGeometry();
var pos = new Float32Array(count * 3);
var col = new Float32Array(count * 3);
for (var i = 0; i < count; i++) {
pos[i*3] = (Math.random()-0.5) * spread;
pos[i*3+1] = (Math.random()-0.5) * spread;
pos[i*3+2] = (Math.random()-0.5) * spread;
var b = 0.5 + Math.random() * 0.5;
col[i*3] = b * 0.85; col[i*3+1] = b * 0.95; col[i*3+2] = b;
}
geo.setAttribute("position", new THREE.BufferAttribute(pos, 3));
geo.setAttribute("color", new THREE.BufferAttribute(col, 3));
var mat = new THREE.PointsMaterial({
size: 1.1, vertexColors: true, transparent: true, opacity: 0.55,
sizeAttenuation: true, depthWrite: false
});
return new THREE.Points(geo, mat);
}
var nearDust = makeDustLayer(isMobile ? 100 : 220, 200);
rig.add(nearDust);
/* ---------- 太阳: 小而烈 + 泛光 + 横向光纹 ---------- */
var sunGroup = new THREE.Group();
// 亮度>1的核心,推过泛光阈值
var sunCore = new THREE.Mesh(
new THREE.SphereGeometry(5, 24, 24),
new THREE.MeshBasicMaterial({ color: new THREE.Color(2.6, 2.3, 1.9) })
);
sunGroup.add(sunCore);
// 柔和光晕(克制)
var sunHalo = new THREE.Sprite(new THREE.SpriteMaterial({
map: glowTexture("rgba(255,232,190,0.85)", "rgba(255,170,90,0.6)"),
transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending, depthWrite: false
}));
sunHalo.scale.set(46, 46, 1);
sunGroup.add(sunHalo);
// 变形宽银幕横向光纹
var streakC = document.createElement("canvas");
streakC.width = 256; streakC.height = 8;
var sx = streakC.getContext("2d");
var sg = sx.createLinearGradient(0, 0, 256, 0);
sg.addColorStop(0, "rgba(255,215,160,0)");
sg.addColorStop(0.5, "rgba(255,235,200,0.85)");
sg.addColorStop(1, "rgba(255,215,160,0)");
sx.fillStyle = sg; sx.fillRect(0, 0, 256, 8);
var streakTex = new THREE.CanvasTexture(streakC);
var sunStreak = new THREE.Sprite(new THREE.SpriteMaterial({
map: streakTex, transparent: true, opacity: 0.55,
blending: THREE.AdditiveBlending, depthWrite: false
}));
sunStreak.scale.set(90, 1.6, 1);
sunGroup.add(sunStreak);
sunGroup.position.set(-150, 70, -260);
scene.add(sunGroup);
/* ============================================================
月亮: 真实纹理 + 撕裂伤口 + 发光裂缝
============================================================ */
var moonGroup = new THREE.Group();
var MOON_R = 10;
var moonMat = new THREE.MeshStandardMaterial({
color: 0xbfc3c9, // 冷调青灰
roughness: 0.98, metalness: 0.0,
bumpScale: 0.6
});
var moon = null;
// 发光裂缝纹理(emissiveMap): 冲击点+放射裂纹+断面代码
function makeCrackEmissive() {
var W = 1024, H = 512;
var c = document.createElement("canvas");
c.width = W; c.height = H;
var x = c.getContext("2d");
x.fillStyle = "#000"; x.fillRect(0, 0, W, H);
var ix = IMPACT.u * W, iy = IMPACT.v * H;
// 断面发光代码结构
x.save();
x.beginPath(); x.arc(ix, iy, 58, 0, 7); x.clip();
x.strokeStyle = "rgba(90,255,195,0.5)"; x.lineWidth = 1;
for (var g = -58; g <= 58; g += 9) {
x.beginPath(); x.moveTo(ix + g, iy - 58); x.lineTo(ix + g, iy + 58); x.stroke();
x.beginPath(); x.moveTo(ix - 58, iy + g); x.lineTo(ix + 58, iy + g); x.stroke();
}
var chars = "01{}<>/=;#*";
x.font = "9px monospace"; x.textBaseline = "top";
for (var i = 0; i < 55; i++) {
x.fillStyle = "rgba(160,255,215," + (0.4 + Math.random() * 0.6) + ")";
x.fillText(chars[Math.floor(Math.random() * chars.length)], ix - 52 + Math.random() * 104, iy - 52 + Math.random() * 104);
}
x.restore();
// 冲击核心辉光
var core = x.createRadialGradient(ix, iy, 0, ix, iy, 80);
core.addColorStop(0, "rgba(200,255,230,1)");
core.addColorStop(0.3, "rgba(120,255,205,0.5)");
core.addColorStop(1, "rgba(80,230,175,0)");
x.fillStyle = core; x.beginPath(); x.arc(ix, iy, 80, 0, 7); x.fill();
// 放射裂纹(宽淡→窄亮)
x.lineCap = "round"; x.lineJoin = "round";
for (var c2 = 0; c2 < 6; c2++) {
var ang = (c2 / 6) * Math.PI * 2 + (Math.random() - 0.5) * 0.8;
var px = ix, py = iy;
var pts = [[px, py]];
var len = 0.16 + Math.random() * 0.2;
var steps = 6 + Math.floor(Math.random() * 4);
for (var s = 0; s < steps; s++) {
ang += (Math.random() - 0.5) * 0.8;
px += Math.cos(ang) * (len * W / steps);
py += Math.sin(ang) * (len * W / steps) * 0.55;
pts.push([px, py]);
}
[[7, "rgba(70,220,170,0.1)"], [3.5, "rgba(100,240,190,0.22)"], [1.4, "rgba(200,255,235,0.9)"]].forEach(function (ps) {
x.strokeStyle = ps[1]; x.lineWidth = ps[0];
x.beginPath(); x.moveTo(pts[0][0], pts[0][1]);
for (var k = 1; k < pts.length; k++) x.lineTo(pts[k][0], pts[k][1]);
x.stroke();
});
}
return new THREE.CanvasTexture(c);
}
// 顶点撕裂: 把伤口区域向内撕成参差轮廓
function tearGeometry(geo, woundDir) {
var pos = geo.attributes.position;
var v = new THREE.Vector3();
for (var i = 0; i < pos.count; i++) {
v.fromBufferAttribute(pos, i).normalize();
var ang = v.angleTo(woundDir);
if (ang < 0.45) {
var fall = 1 - ang / 0.45;
// 高频噪声 → 锯齿而非圆坑
var n = 0.5 + 0.5 * Math.sin(v.x * 13.1) * Math.sin(v.y * 9.7) * Math.sin(v.z * 11.3)
+ 0.25 * Math.sin(v.x * 31.7 + v.y * 23.9);
n = Math.max(0, Math.min(1, n));
var depth = fall * (0.4 + 2.8 * n * (0.35 + 0.65 * fall));
if (ang > 0.3) depth += (Math.random() - 0.5) * 0.4; // 撕裂缘参差
var newR = MOON_R - Math.max(0, depth);
pos.setXYZ(i, v.x * newR, v.y * newR, v.z * newR);
}
}
geo.computeVertexNormals();
}
var woundDirLocal = uvToSphere(IMPACT.u, IMPACT.v, 1).normalize();
var woundPoint = uvToSphere(IMPACT.u, IMPACT.v, MOON_R * 0.98);
texLoader.load("images/moon_color.jpg", function (colorTex) {
colorTex.colorSpace = THREE.SRGBColorSpace;
moonMat.map = colorTex;
moonMat.emissiveMap = makeCrackEmissive();
moonMat.emissive = new THREE.Color(0x9fffd8);
moonMat.emissiveIntensity = 2.1;
// 亮度转凹凸(NASA位移图下载失败时的运行时方案,效果扎实)
var img = colorTex.image;
var bc = document.createElement("canvas");
bc.width = 1024; bc.height = 512;
var bx = bc.getContext("2d");
bx.drawImage(img, 0, 0, 1024, 512);
var id = bx.getImageData(0, 0, 1024, 512);
var d = id.data;
for (var i = 0; i < d.length; i += 4) {
var lum = d[i] * 0.299 + d[i+1] * 0.587 + d[i+2] * 0.114;
lum = Math.max(0, Math.min(255, (lum - 110) * 1.7 + 100));
d[i] = d[i+1] = d[i+2] = lum;
}
bx.putImageData(id, 0, 0);
moonMat.bumpMap = new THREE.CanvasTexture(bc);
var geo = new THREE.SphereGeometry(MOON_R, isMobile ? 64 : 96, isMobile ? 48 : 72);
tearGeometry(geo, woundDirLocal);
moon = new THREE.Mesh(geo, moonMat);
moonGroup.add(moon);
loadState.moon = true;
}, undefined, function () { loadState.moon = true; });
// 月缘柔光(青灰)
var moonHalo = new THREE.Sprite(new THREE.SpriteMaterial({
map: glowTexture("rgba(175,200,195,0.5)", "rgba(160,190,185,0.4)"),
transparent: true, opacity: 0.35, blending: THREE.AdditiveBlending, depthWrite: false
}));
moonHalo.scale.set(30, 30, 1);
moonGroup.add(moonHalo);
// 伤口溢出辉光(活着的事件)
var woundGlow = new THREE.Sprite(new THREE.SpriteMaterial({
map: glowTexture("rgba(160,255,220,0.9)", "rgba(90,255,195,0.55)"),
transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, depthWrite: false
}));
woundGlow.scale.set(11, 11, 1);
woundGlow.position.copy(woundPoint).multiplyScalar(1.04);
moonGroup.add(woundGlow);
/* ---------- 大碎片(2块,顶点扰动,真月面) ---------- */
var fragments = [];
var fragSpecs = [
{ size: 2.4, scatter: new THREE.Vector3(8.5, 4.5, 4), home: new THREE.Vector3(6.8, 3.6, 2.8), spin: 0.16, period: 14 },
{ size: 1.6, scatter: new THREE.Vector3(-6.5, -5, 5), home: new THREE.Vector3(-5, -3.8, 3.8), spin: -0.24, period: 9 }
];
fragSpecs.forEach(function (spec) {
var geo = new THREE.IcosahedronGeometry(spec.size, 2);
var pa = geo.getAttribute("position");
var vv = new THREE.Vector3();
for (var i = 0; i < pa.count; i++) {
vv.fromBufferAttribute(pa, i).normalize();
var j = 0.7 + Math.random() * 0.55;
pa.setXYZ(i, vv.x * spec.size * j, vv.y * spec.size * j, vv.z * spec.size * j);
}
geo.computeVertexNormals();
var mesh = new THREE.Mesh(geo, moonMat);
mesh.position.copy(spec.scatter);
mesh.rotation.set(Math.random() * 3, Math.random() * 3, Math.random() * 3);
moonGroup.add(mesh);
fragments.push({ mesh: mesh, spec: spec, phase: Math.random() * Math.PI * 2 });
});
/* ---------- 碎石量级谱(InstancedMesh, 从伤口拖出) ---------- */
var DEBRIS_N = isMobile ? 90 : 220;
var debrisData = [];
var debrisMesh = null;
(function buildDebris() {
var rockGeo = new THREE.IcosahedronGeometry(1, 0);
var rp = rockGeo.getAttribute("position");
for (var i = 0; i < rp.count; i++) {
var j = 0.65 + Math.random() * 0.7;
rp.setXYZ(i, rp.getX(i) * j, rp.getY(i) * j, rp.getZ(i) * j);
}
rockGeo.computeVertexNormals();
debrisMesh = new THREE.InstancedMesh(rockGeo, moonMat, DEBRIS_N);
var tangent = new THREE.Vector3().crossVectors(woundDirLocal, new THREE.Vector3(0, 1, 0)).normalize();
var bitan = new THREE.Vector3().crossVectors(woundDirLocal, tangent).normalize();
var dummy = new THREE.Object3D();
for (var d = 0; d < DEBRIS_N; d++) {
var along = (Math.random() - 0.3) * 30; // 沿切线拖带
var out = 1.5 + Math.random() * 9; // 向外飞散距离
var pos = woundPoint.clone()
.add(tangent.clone().multiplyScalar(along))
.add(woundDirLocal.clone().multiplyScalar(out))
.add(bitan.clone().multiplyScalar((Math.random() - 0.5) * 5));
var scale = 0.06 + Math.pow(Math.random(), 2.2) * 0.55; // 大量小颗,少数大颗
var vel = woundDirLocal.clone().multiplyScalar(0.1 + Math.random() * 0.25)
.add(tangent.clone().multiplyScalar((Math.random() - 0.5) * 0.1));
var axis = new THREE.Vector3(Math.random()-0.5, Math.random()-0.5, Math.random()-0.5).normalize();
debrisData.push({
pos: pos, scale: scale, vel: vel, axis: axis,
rotSpeed: (Math.random() - 0.5) * 0.6, phase: Math.random() * Math.PI * 2,
wobble: 0.3 + Math.random() * 0.7, period: 8 + Math.random() * 12
});
dummy.position.copy(pos);
dummy.scale.setScalar(scale);
dummy.updateMatrix();
debrisMesh.setMatrixAt(d, dummy.matrix);
}
debrisMesh.instanceMatrix.needsUpdate = true;
moonGroup.add(debrisMesh);
})();
/* ---------- 尘埃云(烟尘,参考图灵魂) ---------- */
var dustClouds = [];
var tangent2 = new THREE.Vector3().crossVectors(woundDirLocal, new THREE.Vector3(0, 1, 0)).normalize();
for (var dc = 0; dc < (isMobile ? 3 : 5); dc++) {
var dustTex = glowTexture("rgba(150,175,165,0.55)", "rgba(140,170,160,0.35)", 256);
var dust = new THREE.Sprite(new THREE.SpriteMaterial({
map: dustTex, transparent: true, opacity: 0.05 + Math.random() * 0.06,
blending: THREE.NormalBlending, depthWrite: false
}));
var sc2 = 22 + Math.random() * 26;
dust.scale.set(sc2 * 1.6, sc2, 1);
var along2 = 4 + dc * 7 + Math.random() * 4;
dust.position.copy(woundPoint)
.add(tangent2.clone().multiplyScalar(along2))
.add(woundDirLocal.clone().multiplyScalar(2 + Math.random() * 4));
moonGroup.add(dust);
dustClouds.push({ sp: dust, base: dust.position.clone(), phase: Math.random() * Math.PI * 2, along: along2 });
}
// 修复光缝
var seams = [];
var seamTex = glowTexture("rgba(160,255,220,1)", "rgba(90,255,195,0.6)", 64);
fragSpecs.forEach(function (spec) {
var seam = new THREE.Sprite(new THREE.SpriteMaterial({
map: seamTex, color: 0x9fffd8, transparent: true, opacity: 0,
blending: THREE.AdditiveBlending, depthWrite: false
}));
seam.scale.set(spec.size * 3.2, spec.size * 1.2, 1);
seam.position.copy(spec.home).multiplyScalar(0.94);
moonGroup.add(seam);
seams.push(seam);
});
moonGroup.position.set(6, 1, -26);
scene.add(moonGroup);
/* ---------- 代码泄漏粒子 ---------- */
var codeChars = "01{}<>/=;#*$fx";
var charCache = {};
function charTex(ch) {
if (!charCache[ch]) {
var s = 48, c = document.createElement("canvas");
c.width = c.height = s;
var x = c.getContext("2d");
x.font = "bold 30px monospace"; x.textAlign = "center"; x.textBaseline = "middle";
x.shadowColor = "#9fffd8"; x.shadowBlur = 9; x.fillStyle = "#d6ffee";
x.fillText(ch, s / 2, s / 2);
charCache[ch] = new THREE.CanvasTexture(c);
}
return charCache[ch];
}
var LEAK_N = isMobile ? 20 : 40;
var leaks = [];
var leakBase = woundPoint.clone().add(moonGroup.position);
for (var li = 0; li < LEAK_N; li++) {
var sp = new THREE.Sprite(new THREE.SpriteMaterial({
map: charTex(codeChars[Math.floor(Math.random() * codeChars.length)]),
transparent: true, opacity: 0, depthWrite: false, blending: THREE.AdditiveBlending
}));
var sc3 = 0.4 + Math.random() * 0.55;
sp.scale.set(sc3, sc3, 1);
var emit = leakBase.clone().add(new THREE.Vector3((Math.random()-0.5)*3, (Math.random()-0.5)*3, (Math.random()-0.5)*3));
sp.position.copy(emit);
scene.add(sp);
leaks.push({
sp: sp, life: Math.random(), speed: 0.08 + Math.random() * 0.14,
drift: woundDirLocal.clone().multiplyScalar(0.4 + Math.random() * 0.5)
.add(new THREE.Vector3((Math.random()-0.5)*0.4, (Math.random()-0.5)*0.4, (Math.random()-0.5)*0.3)),
origin: emit
});
}
/* ---------- 光照(太阳为唯一主光源) ---------- */
var sunLight = new THREE.DirectionalLight(0xffdcae, 2.6);
sunLight.position.set(-150, 70, -260);
scene.add(sunLight);
var fillCold = new THREE.DirectionalLight(0x5a6fc8, 0.4); // 背光面冷蓝紫
fillCold.position.set(120, -40, 120);
scene.add(fillCold);
scene.add(new THREE.AmbientLight(0x2a3452, 0.55));
var rimLight = new THREE.DirectionalLight(0x8899ff, 0.35);
rimLight.position.set(80, -40, 60);
scene.add(rimLight);
/* ============================================================
相机编排(V6行为: 呼吸漂移 + 鼠标惯性 + 滚动驱动)
============================================================ */
var mouseX = 0, mouseY = 0;
if (!isMobile) {
document.addEventListener("mousemove", function (e) {
mouseX = (e.clientX / innerWidth - 0.5) * 2;
mouseY = (e.clientY / innerHeight - 0.5) * 2;
});
}
function scrollProgress() {
var h = document.documentElement.scrollHeight - innerHeight;
return h > 0 ? Math.min(Math.max(scrollY / h, 0), 1) : 0;
}
function easeInOut(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }
function lerp(a, b, t) { return a + (b - a) * t; }
var warpT = reducedMotion ? 1 : 0;
var clock = new THREE.Clock();
var bootDone = false;
/* ---------- BOOT ---------- */
var bootLines = [
{ t: "> 接入深空观测网络 ………… ", ok: "[OK]" },
{ t: "> 望远镜阵列对焦 …………… ", ok: "[OK]" },
{ t: "> 扫描目标: MOON.SYS ……… ", ok: "[FATAL]" },
{ t: "> 检测到结构性碎裂 ………… ", ok: "[FRAGS]" },
{ t: "> 启动修复协议 ……………… ", ok: "[GO]" }
];
var bootBody = document.getElementById("spaceBootBody");
var bootBar = document.getElementById("spaceBootBar");
function runBoot() {
if (!bootEl || reducedMotion) { finishBoot(); return; }
var i = 0;
(function next() {
if (i < bootLines.length) {
var line = document.createElement("div");
line.className = "terminal-line";
var isFatal = bootLines[i].ok.indexOf("FATAL") >= 0 || bootLines[i].ok.indexOf("FRAG") >= 0;
line.innerHTML = '<span class="terminal-prompt">' + bootLines[i].t + '</span><span class="terminal-text ' + (isFatal ? "log-error" : "ok") + '">' + bootLines[i].ok + "</span>";
if (bootBody) bootBody.appendChild(line);
if (bootBar) bootBar.style.width = ((i + 1) / bootLines.length * 100) + "%";
i++;
setTimeout(next, 340);
} else setTimeout(finishBoot, 420);
})();
}
function finishBoot() {
if (bootEl) {
bootEl.classList.add("hide");
setTimeout(function () { if (bootEl.parentNode) bootEl.parentNode.removeChild(bootEl); }, 900);
}
bootDone = true;
document.body.classList.add("space-ready");
}
runBoot();
setTimeout(finishBoot, 4500);
/* ============================================================
主循环
============================================================ */
var dummy = new THREE.Object3D();
var tmpQ = new THREE.Quaternion();
function animate() {
requestAnimationFrame(animate);
var t = clock.getElapsedTime();
var p = isJourney ? scrollProgress() : 0;
var ep = easeInOut(p);
// warp入场
if (warpT < 1) {
warpT = Math.min(1, warpT + 0.008);
var w = 1 - Math.pow(1 - warpT, 3);
camera.position.z = lerp(220, isJourney ? 46 : 42, w);
if (skyMesh) skyMesh.rotation.y += 0.004 * (1 - warpT);
} else if (isJourney) {
camera.position.z = lerp(46, 24, ep);
camera.position.y = lerp(0, 3, ep);
} else {
camera.position.z = 42;
}
// 呼吸式失重漂移
rig.position.x = Math.sin(t * 0.24) * 0.55;
rig.position.y = Math.cos(t * 0.19) * 0.45;
rig.rotation.z = Math.sin(t * 0.1) * 0.008;
// 鼠标惯性跟随
camera.rotation.y += ((-mouseX * 0.09) - camera.rotation.y) * 0.045;
camera.rotation.x += ((-mouseY * 0.06) - camera.rotation.x) * 0.045;
// 月亮缓慢自转 + 呼吸
if (moon) moon.rotation.y += 0.0006;
moonGroup.position.y = 1 + Math.sin(t * 0.33) * 0.3;
moonGroup.rotation.z = Math.sin(t * 0.11) * 0.015;
// 伤口辉光脉动
woundGlow.material.opacity = 0.65 + Math.sin(t * 1.6) * 0.2;
// 修复进度(V6时序)
var repair = isJourney ? Math.min(Math.max((p - 0.42) / 0.46, 0), 1) * 0.85 : 0.3;
var rt = repair / 0.85;
// 大碎片: 无衰减漂移,各自周期相位
fragments.forEach(function (f) {
var target = f.spec.scatter.clone().lerp(f.spec.home, rt * rt);
var amp = 0.5 * (1 - rt);
var w1 = (t / f.spec.period) * Math.PI * 2 + f.phase;
f.mesh.position.set(
target.x + Math.sin(w1) * amp,
target.y + Math.sin(w1 * 0.83 + 1.2) * amp * 0.8,
target.z + Math.cos(w1 * 0.71) * amp * 0.7
);
f.mesh.rotation.x += 0.0016 * f.spec.spin * (1 - rt * 0.8);
f.mesh.rotation.y += 0.0022 * f.spec.spin * (1 - rt * 0.8);
});
// 碎石场: 各自向外漂移+翻滚+不同步摆动(持续离散中)
if (debrisMesh) {
for (var d = 0; d < DEBRIS_N; d++) {
var dd = debrisData[d];
dd.pos.add(dd.vel.clone().multiplyScalar(0.016 * (1 - rt * 0.6)));
var wob = (t / dd.period) * Math.PI * 2 + dd.phase;
dummy.position.set(
dd.pos.x + Math.sin(wob) * dd.wobble * 0.3,
dd.pos.y + Math.sin(wob * 0.83) * dd.wobble * 0.25,
dd.pos.z + Math.cos(wob * 0.71) * dd.wobble * 0.25
);
tmpQ.setFromAxisAngle(dd.axis, t * dd.rotSpeed + dd.phase);
dummy.quaternion.copy(tmpQ);
dummy.scale.setScalar(dd.scale);
dummy.updateMatrix();
debrisMesh.setMatrixAt(d, dummy.matrix);
}
debrisMesh.instanceMatrix.needsUpdate = true;
}
// 尘埃云缓慢流动
dustClouds.forEach(function (dc2, i) {
dc2.sp.position.x = dc2.base.x + Math.sin(t * 0.1 + dc2.phase) * 1.2 + t * 0.05 * (i % 2 ? 1 : -0.7);
dc2.sp.position.y = dc2.base.y + Math.cos(t * 0.08 + dc2.phase) * 0.9;
dc2.sp.material.opacity = 0.05 + 0.03 * Math.sin(t * 0.23 + dc2.phase) + 0.04;
});
// 光缝
seams.forEach(function (s, i) {
s.material.opacity = rt * 0.9 + Math.sin(t * 2.2 + i * 1.7) * 0.06 * rt;
});
// 代码泄漏
var leakRate = 1 - rt * 0.55;
leaks.forEach(function (L) {
L.life += L.speed * 0.016;
if (L.life > 1) {
L.life = 0;
L.sp.position.copy(L.origin);
L.sp.material.map = charTex(codeChars[Math.floor(Math.random() * codeChars.length)]);
}
L.sp.position.add(L.drift.clone().multiplyScalar(0.016));
L.sp.material.opacity = Math.sin(L.life * Math.PI) * 0.8 * leakRate;
});
// 太阳呼吸 + 光纹微闪
sunHalo.material.opacity = 0.36 + Math.sin(t * 0.7) * 0.06;
sunStreak.material.opacity = 0.45 + Math.sin(t * 1.1) * 0.12;
// 星野极缓旋转 + 近尘埃视差
if (skyMesh) skyMesh.rotation.y += 0.00008;
nearDust.rotation.y += 0.0003;
if (composer) composer.render();
else renderer.render(scene, camera);
}
animate();
addEventListener("resize", function () {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
if (composer) composer.setSize(innerWidth, innerHeight);
});
})();