// modules/panels/batcave.js — Batcave workshop area with reflection probe import * as THREE from 'three'; import { THEME } from '../core/theme.js'; let batcaveGroup, batcaveProbe, batcaveProbeTarget, batcaveLight; let batcaveMetallicMats = []; let batcaveProbeLastUpdate = -999; export function init(scene) { const BATCAVE_ORIGIN = new THREE.Vector3(-10, 0, -8); batcaveGroup = new THREE.Group(); batcaveGroup.position.copy(BATCAVE_ORIGIN); scene.add(batcaveGroup); batcaveProbeTarget = new THREE.WebGLCubeRenderTarget(128, { type: THREE.HalfFloatType, generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter, }); batcaveProbe = new THREE.CubeCamera(0.1, 80, batcaveProbeTarget); batcaveProbe.position.set(0, 1.2, -1); batcaveGroup.add(batcaveProbe); const batcaveFloorMat = new THREE.MeshStandardMaterial({ color: 0x0d1520, metalness: 0.92, roughness: 0.08, envMapIntensity: 1.4, }); const batcaveWallMat = new THREE.MeshStandardMaterial({ color: 0x0a1828, metalness: 0.85, roughness: 0.15, emissive: new THREE.Color(THEME.colors.accent).multiplyScalar(0.03), envMapIntensity: 1.2, }); const batcaveConsoleMat = new THREE.MeshStandardMaterial({ color: 0x060e16, metalness: 0.95, roughness: 0.05, envMapIntensity: 1.6, }); batcaveMetallicMats = [batcaveFloorMat, batcaveWallMat, batcaveConsoleMat]; const batcaveFloor = new THREE.Mesh(new THREE.BoxGeometry(6, 0.08, 6), batcaveFloorMat); batcaveFloor.position.y = -0.04; batcaveGroup.add(batcaveFloor); const batcaveBackWall = new THREE.Mesh(new THREE.BoxGeometry(6, 3, 0.1), batcaveWallMat); batcaveBackWall.position.set(0, 1.5, -3); batcaveGroup.add(batcaveBackWall); const batcaveLeftWall = new THREE.Mesh(new THREE.BoxGeometry(0.1, 3, 6), batcaveWallMat); batcaveLeftWall.position.set(-3, 1.5, 0); batcaveGroup.add(batcaveLeftWall); const batcaveConsoleBase = new THREE.Mesh(new THREE.BoxGeometry(3, 0.7, 1.2), batcaveConsoleMat); batcaveConsoleBase.position.set(0, 0.35, -1.5); batcaveGroup.add(batcaveConsoleBase); const batcaveScreenBezel = new THREE.Mesh(new THREE.BoxGeometry(2.6, 1.4, 0.06), batcaveConsoleMat); batcaveScreenBezel.position.set(0, 1.4, -2.08); batcaveScreenBezel.rotation.x = Math.PI * 0.08; batcaveGroup.add(batcaveScreenBezel); const batcaveScreenGlow = new THREE.Mesh( new THREE.PlaneGeometry(2.2, 1.1), new THREE.MeshBasicMaterial({ color: new THREE.Color(THEME.colors.accent).multiplyScalar(0.65), transparent: true, opacity: 0.82, }) ); batcaveScreenGlow.position.set(0, 1.4, -2.05); batcaveScreenGlow.rotation.x = Math.PI * 0.08; batcaveGroup.add(batcaveScreenGlow); batcaveLight = new THREE.PointLight(THEME.colors.accent, 0.9, 14); batcaveLight.position.set(0, 2.8, -1); batcaveGroup.add(batcaveLight); const batcaveCeilingStrip = new THREE.Mesh( new THREE.BoxGeometry(4.2, 0.05, 0.14), new THREE.MeshStandardMaterial({ color: THEME.colors.accent, emissive: new THREE.Color(THEME.colors.accent), emissiveIntensity: 1.1, }) ); batcaveCeilingStrip.position.set(0, 2.95, -1.2); batcaveGroup.add(batcaveCeilingStrip); batcaveGroup.traverse(obj => { if (obj.isMesh) obj.userData.zoomLabel = 'Batcave'; }); } export function updateProbe(elapsed, renderer, scene) { if (elapsed - batcaveProbeLastUpdate > 2.0) { batcaveProbeLastUpdate = elapsed; batcaveGroup.visible = false; batcaveProbe.update(renderer, scene); batcaveGroup.visible = true; for (const mat of batcaveMetallicMats) { mat.envMap = batcaveProbeTarget.texture; mat.needsUpdate = true; } } }