// Progressive WebGL presentation layer. The game remains usable if this optional // module or WebGL is unavailable. import * as THREE from 'three'; import {assetUrl} from './asset-resolver.js'; import {OBJLoader} from 'https://huggingface.co/datasets/Vintendopower/nighttrap-media/resolve/main/assets/vendor/OBJLoader.js'; const canvas=document.getElementById('threeConsole'); if(canvas){ const host=canvas.parentElement; const renderer=new THREE.WebGLRenderer({canvas,alpha:true,antialias:true,powerPreference:'low-power'}); renderer.setPixelRatio(Math.min(window.devicePixelRatio||1,1.5)); renderer.outputColorSpace=THREE.SRGBColorSpace; const scene=new THREE.Scene(); const camera=new THREE.PerspectiveCamera(34,1,.1,100); camera.position.set(0,1.8,8.4);camera.lookAt(0,.2,0); scene.add(new THREE.AmbientLight(0x6b8290,.38)); const redLight=new THREE.PointLight(0xff3024,1.3,12);redLight.position.set(-3,2,3);scene.add(redLight); const greenLight=new THREE.PointLight(0x7dffb0,1.1,10);greenLight.position.set(3,1,2);scene.add(greenLight); const consoleGroup=new THREE.Group();scene.add(consoleGroup); const frameMaterial=new THREE.MeshStandardMaterial({color:0x0a1115,metalness:.82,roughness:.3,transparent:true,opacity:.92}); const trimMaterial=new THREE.MeshStandardMaterial({color:0x334650,metalness:.7,roughness:.26,emissive:0x08151b,emissiveIntensity:.8}); const frame=new THREE.Mesh(new THREE.BoxGeometry(8.2,4.5,.42),frameMaterial);frame.position.set(0,.1,-.3);consoleGroup.add(frame); const trim=new THREE.Mesh(new THREE.BoxGeometry(7.8,4.1,.12),trimMaterial);trim.position.set(0,.1,-.05);consoleGroup.add(trim); const roomMaterials=[]; for(let i=0;i<8;i++){ const material=new THREE.MeshStandardMaterial({color:0x15252c,emissive:0x143e4a,emissiveIntensity:.25,metalness:.4,roughness:.36}); roomMaterials.push(material); const x=-3.25+(i%4)*2.17,y=-2.0+Math.floor(i/4)*.72; const slot=new THREE.Mesh(new THREE.BoxGeometry(1.72,.46,.08),material);slot.position.set(x,y,.08);consoleGroup.add(slot); } const sweep=new THREE.Mesh(new THREE.BoxGeometry(7.1,.018,.02),new THREE.MeshBasicMaterial({color:0x79ffc4,transparent:true,opacity:.55}));sweep.position.set(0,.2,.2);consoleGroup.add(sweep); const grid=new THREE.GridHelper(14,28,0x2f5360,0x172c35);grid.position.y=-2.35;grid.rotation.x=.03;grid.material.transparent=true;grid.material.opacity=.25;scene.add(grid); const textureLoader=new THREE.TextureLoader(); const mapTexture=textureLoader.load(assetUrl('assets/images/sharedassets1_369.png'),texture=>{texture.colorSpace=THREE.SRGBColorSpace;}); const mapPanel=new THREE.Mesh(new THREE.PlaneGeometry(1.55,.98),new THREE.MeshBasicMaterial({map:mapTexture,transparent:true,opacity:.72})); mapPanel.position.set(3.02,.52,.19);mapPanel.rotation.y=-.08;consoleGroup.add(mapPanel); const buttonTexture=textureLoader.load(assetUrl('assets/images/sharedassets1_425.png'),texture=>{texture.colorSpace=THREE.SRGBColorSpace;}); const buttonPanel=new THREE.Mesh(new THREE.PlaneGeometry(1.2,.64),new THREE.MeshBasicMaterial({map:buttonTexture,transparent:true,opacity:.42})); buttonPanel.position.set(-3.12,.55,.2);buttonPanel.rotation.y=.08;consoleGroup.add(buttonPanel); const noiseTexture=textureLoader.load(assetUrl('assets/images/sharedassets1_409.png'),texture=>{texture.colorSpace=THREE.SRGBColorSpace;texture.wrapS=texture.wrapT=THREE.RepeatWrapping;}); const noiseMaterial=new THREE.MeshBasicMaterial({map:noiseTexture,transparent:true,opacity:.10,depthWrite:false,blending:THREE.AdditiveBlending}); const noisePlane=new THREE.Mesh(new THREE.PlaneGeometry(7.6,4.0),noiseMaterial);noisePlane.position.set(0,.1,.24);consoleGroup.add(noisePlane); const extractedLayouts={}; const objLoader=new OBJLoader(); const layoutVariants={night:assetUrl('assets/models/sharedassets2_12_PlayUp_1994-layout.obj'),sotc:assetUrl('assets/models/sharedassets3_4_PlayUp_1993.obj'),survivor:assetUrl('assets/models/sharedassets4_5_PlayUp_1992.obj')}; function selectLayout(){const variant=eraOverride==='auto'?(currentMode.startsWith('sotc')?'sotc':currentMode.startsWith('survivor')?'survivor':'night'):({1992:'survivor',1993:'sotc',1994:'night'}[eraOverride]||'night');Object.entries(extractedLayouts).forEach(([key,object])=>{object.visible=key===variant;});} Object.entries(layoutVariants).forEach(([key,url])=>objLoader.load(url,object=>{ const bounds=new THREE.Box3().setFromObject(object),size=bounds.getSize(new THREE.Vector3()),center=bounds.getCenter(new THREE.Vector3()); object.position.sub(center);object.scale.setScalar(6.6/Math.max(size.x,size.y,size.z));object.position.set(0,.05,-.18); object.traverse(node=>{if(!node.isMesh)return;node.material=new THREE.MeshStandardMaterial({color:key==='sotc'?0x8b3438:key==='survivor'?0x6670a0:0x4f7486,emissive:key==='sotc'?0x260607:key==='survivor'?0x11182d:0x081a26,emissiveIntensity:.55,metalness:.7,roughness:.38,transparent:true,opacity:.72});node.material.depthWrite=false;}); extractedLayouts[key]=object;scene.add(object);selectLayout(); },undefined,()=>{})); let currentRoom=0,currentMode='night',eraOverride='auto',alert=false,last=performance.now(); function resize(){const w=host.clientWidth,h=host.clientHeight;if(!w||!h)return;renderer.setSize(w,h,false);camera.aspect=w/h;camera.updateProjectionMatrix();} function setMode(mode){currentMode=mode;const sotc=String(mode).startsWith('sotc');frameMaterial.color.setHex(sotc?0x170b0d:0x0a1115);trimMaterial.color.setHex(sotc?0x5e2426:0x334650);redLight.color.setHex(sotc?0xff5b39:0xff3024);selectLayout();} function setEra(value){eraOverride=['auto','1992','1993','1994'].includes(value)?value:'auto';selectLayout();} function setRoom(index){currentRoom=Math.max(0,Math.min(7,Number(index)||0));roomMaterials.forEach((m,i)=>{m.emissiveIntensity=i===currentRoom ? .95 : .16;m.color.setHex(i===currentRoom?0x284b50:0x15252c);});} function setAlert(value){alert=!!value;greenLight.intensity=alert?.35:1.1;redLight.intensity=alert?2.2:1.3;} function animate(now){const dt=Math.min(.05,(now-last)/1000);last=now;sweep.position.y=1.8-((now*.00023)%1)*3.6;sweep.material.opacity=.28+.18*Math.sin(now*.003);if(noiseTexture)noiseTexture.offset.x=(now*.00008)%1,noiseTexture.offset.y=(now*.00011)%1;noiseMaterial.opacity=alert?.18:.10;consoleGroup.rotation.y=Math.sin(now*.00035)*.012;redLight.intensity+=(alert?2.2:1.3-redLight.intensity)*dt*3;renderer.render(scene,camera);requestAnimationFrame(animate);} window.addEventListener('resize',resize);resize();setRoom(0);requestAnimationFrame(animate); window.nightTrapVisuals={setMode,setEra,setRoom,setAlert}; }