minicpm5-2b-webgpu / src /scene.js
ProCreations's picture
Improve readability, unify KV precision, and handle Safari startup failures
c218615 verified
Raw History Blame Contribute Delete
8.77 kB
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';
export function createLaunchScene(element){
let renderer;
try {renderer=new THREE.WebGLRenderer({alpha:true,antialias:true,powerPreference:'low-power'});}catch{return {setMode(){},setProgress(){},dispose(){}};}
renderer.setPixelRatio(Math.min(devicePixelRatio,1.6));renderer.setClearColor(0x090b13,0);renderer.outputColorSpace=THREE.SRGBColorSpace;renderer.toneMapping=THREE.ACESFilmicToneMapping;
element.appendChild(renderer.domElement);renderer.domElement.setAttribute('aria-hidden','true');
const scene=new THREE.Scene(),camera=new THREE.PerspectiveCamera(38,1,.1,100);camera.position.set(0,.3,8.4);
const composer=new EffectComposer(renderer);composer.addPass(new RenderPass(scene,camera));const bloom=new UnrealBloomPass(new THREE.Vector2(512,512),.85,.75,.72);composer.addPass(bloom);
scene.add(new THREE.AmbientLight(0x899bff,2.5));
for(const [color,intensity,pos]of [[0xc8deff,15,[3,5,4]],[0x6357ff,18,[-3,0,2]],[0x57d7ff,10,[1,-3,3]]]){const l=new THREE.PointLight(color,intensity,20);l.position.set(...pos);scene.add(l);}
const craft=new THREE.Group();craft.rotation.z=-.48;craft.rotation.x=.12;scene.add(craft);
const metal=new THREE.MeshPhysicalMaterial({color:0xbecaf1,metalness:.9,roughness:.23,clearcoat:1});
const blue=new THREE.MeshPhysicalMaterial({color:0x4249d9,metalness:.65,roughness:.25,clearcoat:1});
const dark=new THREE.MeshPhysicalMaterial({color:0x101935,metalness:.8,roughness:.2});
const glow=new THREE.MeshBasicMaterial({color:0x8aacff});
const profile=[new THREE.Vector2(.32,-.9),new THREE.Vector2(.45,-.66),new THREE.Vector2(.46,.45),new THREE.Vector2(.39,.95),new THREE.Vector2(.22,1.32),new THREE.Vector2(0,1.57)];
const hull=new THREE.Mesh(new THREE.LatheGeometry(profile,64),metal);craft.add(hull);
const nose=new THREE.Mesh(new THREE.LatheGeometry([new THREE.Vector2(.39,.95),new THREE.Vector2(.22,1.32),new THREE.Vector2(0,1.58)],64),blue);craft.add(nose);
const ring=(radius,tube,mat,y)=>{const m=new THREE.Mesh(new THREE.TorusGeometry(radius,tube,12,80),mat);m.rotation.x=Math.PI/2;m.position.y=y;craft.add(m);return m;};
ring(.455,.018,blue,.42);ring(.444,.012,glow,-.42);ring(.345,.07,dark,-.88);
const windowFrame=new THREE.Mesh(new THREE.TorusGeometry(.195,.045,16,48),dark);windowFrame.position.set(0,.33,.44);craft.add(windowFrame);
const windowGlass=new THREE.Mesh(new THREE.SphereGeometry(.175,32,24),new THREE.MeshPhysicalMaterial({color:0x6fafff,emissive:0x2463da,emissiveIntensity:1.1,metalness:.65,roughness:.12}));windowGlass.scale.z=.25;windowGlass.position.set(0,.33,.465);craft.add(windowGlass);
for(let i=0;i<3;i++){
const shape=new THREE.Shape();shape.moveTo(.32,-.35);shape.lineTo(.95,-1.02);shape.quadraticCurveTo(.98,-1.2,.79,-1.08);shape.lineTo(.31,-.8);shape.closePath();
const fin=new THREE.Mesh(new THREE.ExtrudeGeometry(shape,{depth:.075,bevelEnabled:true,bevelSegments:2,steps:1,bevelSize:.025,bevelThickness:.025}),blue);fin.rotation.y=i*Math.PI*2/3;craft.add(fin);
}
const exhaust=new THREE.Group();exhaust.position.y=-.96;craft.add(exhaust);
const flameMat=new THREE.ShaderMaterial({transparent:true,depthWrite:false,blending:THREE.AdditiveBlending,side:THREE.DoubleSide,uniforms:{time:{value:0}},vertexShader:`varying vec2 vUv;void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);}`,fragmentShader:`varying vec2 vUv;uniform float time;void main(){float f=pow(1.-vUv.y,1.4);float a=f*(.5+.2*sin(vUv.y*35.-time*14.));vec3 c=mix(vec3(.27,.21,1.),vec3(.7,.95,1.),f);gl_FragColor=vec4(c*1.8,a*.65);}`});
const flame=new THREE.Mesh(new THREE.ConeGeometry(.29,1.9,40,1,true),flameMat);flame.rotation.z=Math.PI;flame.position.y=-.9;exhaust.add(flame);
const orbital=new THREE.Group();orbital.rotation.x=.38;orbital.rotation.z=-.25;scene.add(orbital);
for(let i=0;i<3;i++){
const points=[];for(let j=0;j<=180;j++){const a=j/180*Math.PI*2;points.push(new THREE.Vector3(Math.cos(a)*(2.0+i*.29),Math.sin(a)*(2.0+i*.29),0));}
const orbit=new THREE.Line(new THREE.BufferGeometry().setFromPoints(points),new THREE.LineBasicMaterial({color:i===1?0x7376ff:0x4c659a,transparent:true,opacity:i===1?.26:.15}));orbit.rotation.y=.8+i*.36;orbit.rotation.x=.3+i*.38;orbital.add(orbit);
}
const nodeGeo=new THREE.IcosahedronGeometry(.027,1),nodes=[];
for(let i=0;i<42;i++){
const m=new THREE.Mesh(nodeGeo,new THREE.MeshBasicMaterial({color:i%3===0?0x90ddff:0x686bff,transparent:true,opacity:.65}));const a=i/42*Math.PI*2;const r=2.25+.2*Math.sin(i*2.3);m.position.set(Math.cos(a)*r,Math.sin(a)*r,Math.sin(a*3)*.7);orbital.add(m);nodes.push(m);
}
const starCount=210,coords=new Float32Array(starCount*3);for(let i=0;i<coords.length;i+=3){coords[i]=(Math.random()-.5)*12;coords[i+1]=(Math.random()-.5)*10;coords[i+2]=-2-Math.random()*4;}
const geo=new THREE.BufferGeometry();geo.setAttribute('position',new THREE.BufferAttribute(coords,3));scene.add(new THREE.Points(geo,new THREE.PointsMaterial({color:0x929ed5,size:.013,transparent:true,opacity:.5})));
const seedCoords=coords.slice(),streakCoords=new Float32Array(starCount*6);
const streakGeo=new THREE.BufferGeometry();streakGeo.setAttribute('position',new THREE.BufferAttribute(streakCoords,3));
const streakMaterial=new THREE.LineBasicMaterial({color:0x818dff,transparent:true,opacity:0,blending:THREE.AdditiveBlending});scene.add(new THREE.LineSegments(streakGeo,streakMaterial));
const engineLight=new THREE.PointLight(0x7587ff,3,5);engineLight.position.set(0,-1.3,.5);craft.add(engineLight);
let mode='idle',progress=0,raf=0,last=0,visible=true,thrust=.25,travel=0,launchStart=0;
const reduced=matchMedia('(prefers-reduced-motion: reduce)');
const resize=()=>{const w=element.clientWidth,h=element.clientHeight;if(!w||!h)return;renderer.setSize(w,h);composer.setSize(w,h);camera.aspect=w/h;camera.updateProjectionMatrix();};const observer=new ResizeObserver(resize);observer.observe(element);resize();
const intersection=new IntersectionObserver(entries=>{visible=entries[0].isIntersecting;});intersection.observe(element);
const pointer={x:0,y:0};element.addEventListener('pointermove',e=>{const b=element.getBoundingClientRect();pointer.x=(e.clientX-b.left)/b.width-.5;pointer.y=(e.clientY-b.top)/b.height-.5;});element.addEventListener('pointerleave',()=>{pointer.x=0;pointer.y=0;});
function draw(t){raf=requestAnimationFrame(draw);if(!visible||document.hidden||mode==='paused'||t-last<(mode==='compile'?125:33))return;const dt=Math.min(.08,(t-last)/1000);last=t;const time=reduced.matches?0:t*.001;
const launching=mode==='launch',loading=mode==='loading'||mode==='compile';
const target=launching?5:loading?.8+progress*2.6:.28;
thrust+=(target-thrust)*(1-Math.exp(-dt*2.5));if(!reduced.matches)travel+=dt*(.2+thrust*2.5);
const age=launching&&!reduced.matches?(t-launchStart)/1000:0,flight=age*age*8;
craft.position.set(Math.sin(time*.55)*.13+flight*.45,Math.sin(time*.85)*.2+(loading?progress*.2:0)+flight,0);
craft.rotation.y=Math.sin(time*.48)*.65+pointer.x*.2;craft.rotation.x=.1+Math.sin(time*.7)*.09+pointer.y*.12;
craft.rotation.z=-.4+Math.sin(time*.42)*.07+(loading?Math.sin(time*24)*.008*thrust:0);
orbital.rotation.y=time*(loading?.28:.12);orbital.rotation.z=-.25+Math.sin(time*.16)*.16;
flameMat.uniforms.time.value=time;exhaust.scale.y=.7+thrust*.45+Math.sin(time*(loading?22:5))*.08;exhaust.scale.x=exhaust.scale.z=.9+thrust*.07;engineLight.intensity=3+thrust*5;
for(let i=0;i<starCount;i++){const k=i*3,j=i*6;const y=((seedCoords[k+1]-travel+5)%10+10)%10-5;coords[k]=seedCoords[k]+(y-seedCoords[k+1])*.18;coords[k+1]=y;
streakCoords[j]=coords[k];streakCoords[j+1]=y;streakCoords[j+2]=coords[k+2];streakCoords[j+3]=coords[k]+thrust*.045;streakCoords[j+4]=y+thrust*.16;streakCoords[j+5]=coords[k+2];}
geo.attributes.position.needsUpdate=true;streakGeo.attributes.position.needsUpdate=true;streakMaterial.opacity=reduced.matches?0:Math.min(.6,Math.max(0,thrust-.35)*.16);
nodes.forEach((n,i)=>{n.material.opacity=mode==='loading'?(i/42<progress?1:.15):.45+.25*Math.sin(time+i);});
if(mode==='compile')renderer.render(scene,camera);else composer.render();
}
raf=requestAnimationFrame(draw);
return {setMode(v){if(v==='launch'&&mode!=='launch')launchStart=performance.now();mode=v;},setProgress(v){progress=v;},dispose(){cancelAnimationFrame(raf);observer.disconnect();intersection.disconnect();scene.traverse(o=>{o.geometry?.dispose();if(o.material){for(const m of Array.isArray(o.material)?o.material:[o.material])m.dispose();}});composer.dispose();renderer.dispose();}};
}