[claude] Reflection probes for metallic surfaces in the Batcave area (#246) #345
139
app.js
139
app.js
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user