[claude] Reflection probes for metallic surfaces in the Batcave area (#246) #345

Merged
Timmy merged 1 commits from claude/issue-246 into main 2026-03-24 05:25:40 +00:00

139
app.js
View File

@@ -1201,6 +1201,133 @@ function updateLightningArcs() {
}
}
// === BATCAVE AREA ===
// Dark metallic workshop terminal positioned back-left of the main glass platform.
// A CubeCamera reflection probe captures the local environment and applies it
// to all high-metalness surfaces in this area for physically-based reflections.
const BATCAVE_ORIGIN = new THREE.Vector3(-10, 0, -8);
const batcaveGroup = new THREE.Group();
batcaveGroup.position.copy(BATCAVE_ORIGIN);
scene.add(batcaveGroup);
// Reflection probe — 128-px cube render target for PBR metallic surfaces
const batcaveProbeTarget = new THREE.WebGLCubeRenderTarget(128, {
type: THREE.HalfFloatType,
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter,
});
const batcaveProbe = new THREE.CubeCamera(0.1, 80, batcaveProbeTarget);
batcaveProbe.position.set(0, 1.2, -1); // centred above the console
batcaveGroup.add(batcaveProbe);
// Brushed-steel floor panels
const batcaveFloorMat = new THREE.MeshStandardMaterial({
color: 0x0d1520,
metalness: 0.92,
roughness: 0.08,
envMapIntensity: 1.4,
});
// Anodised wall panels with faint accent tint
const batcaveWallMat = new THREE.MeshStandardMaterial({
color: 0x0a1828,
metalness: 0.85,
roughness: 0.15,
emissive: new THREE.Color(NEXUS.colors.accent).multiplyScalar(0.03),
envMapIntensity: 1.2,
});
// High-gloss terminal console surface
const batcaveConsoleMat = new THREE.MeshStandardMaterial({
color: 0x060e16,
metalness: 0.95,
roughness: 0.05,
envMapIntensity: 1.6,
});
// All metallic mats that receive the reflection probe envMap
const batcaveMetallicMats = [batcaveFloorMat, batcaveWallMat, batcaveConsoleMat];
// Floor slab
const batcaveFloor = new THREE.Mesh(
new THREE.BoxGeometry(6, 0.08, 6),
batcaveFloorMat
);
batcaveFloor.position.y = -0.04;
batcaveGroup.add(batcaveFloor);
// Back wall
const batcaveBackWall = new THREE.Mesh(
new THREE.BoxGeometry(6, 3, 0.1),
batcaveWallMat
);
batcaveBackWall.position.set(0, 1.5, -3);
batcaveGroup.add(batcaveBackWall);
// Left side wall
const batcaveLeftWall = new THREE.Mesh(
new THREE.BoxGeometry(0.1, 3, 6),
batcaveWallMat
);
batcaveLeftWall.position.set(-3, 1.5, 0);
batcaveGroup.add(batcaveLeftWall);
// Console desk base
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);
// Console screen bezel
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);
// Screen glow face
const batcaveScreenGlow = new THREE.Mesh(
new THREE.PlaneGeometry(2.2, 1.1),
new THREE.MeshBasicMaterial({
color: new THREE.Color(NEXUS.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);
// Workshop point light — cool blue for metallic ambience
const batcaveLight = new THREE.PointLight(NEXUS.colors.accent, 0.9, 14);
batcaveLight.position.set(0, 2.8, -1);
batcaveGroup.add(batcaveLight);
// Ceiling strip emissive bar
const batcaveCeilingStrip = new THREE.Mesh(
new THREE.BoxGeometry(4.2, 0.05, 0.14),
new THREE.MeshStandardMaterial({
color: NEXUS.colors.accent,
emissive: new THREE.Color(NEXUS.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';
});
// Probe state — timestamp of last reflection capture (seconds)
let batcaveProbeLastUpdate = -999;
// === ANIMATION LOOP ===
const clock = new THREE.Clock();
@@ -1244,6 +1371,18 @@ function animate() {
// Subtle pulse on constellation opacity
constellationLines.material.opacity = 0.12 + Math.sin(elapsed * 0.5) * 0.06;
// Batcave reflection probe — refresh every 2 s to capture dynamic scene changes
if (elapsed - batcaveProbeLastUpdate > 2.0) {
batcaveProbeLastUpdate = elapsed;
batcaveGroup.visible = false; // hide self to avoid self-capture artefacts
batcaveProbe.update(renderer, scene);
batcaveGroup.visible = true;
for (const mat of batcaveMetallicMats) {
mat.envMap = batcaveProbeTarget.texture;
mat.needsUpdate = true;
}
}
// Glass platform — ripple edge glow outward from centre
for (const { mat, distFromCenter } of glassEdgeMaterials) {
const phase = elapsed * 1.1 - distFromCenter * 0.18;