← back to Dw War Room

src/showroom/ShowroomEnvironment.ts

94 lines

// ShowroomEnvironment.ts — HDR environment map for photorealistic IBL
import * as THREE from 'three';

/**
 * Generate a high-quality procedural environment map for gallery/showroom IBL.
 * Creates a warm gallery interior environment rather than the cold command-center gradient.
 *
 * This procedural approach avoids needing an external HDRI file download.
 * It simulates a well-lit interior with warm ceiling lights and neutral walls.
 */
export function createShowroomEnvMap(renderer: THREE.WebGLRenderer): THREE.Texture {
  const pmrem = new THREE.PMREMGenerator(renderer);
  pmrem.compileEquirectangularShader();

  const envScene = new THREE.Scene();

  // Sky dome — warm dark interior
  const skyGeo = new THREE.SphereGeometry(50, 32, 16);
  const skyMat = new THREE.MeshBasicMaterial({
    color: 0x1a1815,
    side: THREE.BackSide,
  });
  envScene.add(new THREE.Mesh(skyGeo, skyMat));

  // Ceiling highlight — warm white overhead panels (gallery lighting ~3200K)
  const ceilGeo = new THREE.SphereGeometry(40, 16, 8, 0, Math.PI * 2, 0, Math.PI * 0.2);
  const ceilMat = new THREE.MeshBasicMaterial({
    color: 0xf5e6d0,
    side: THREE.BackSide,
  });
  const ceil = new THREE.Mesh(ceilGeo, ceilMat);
  ceil.rotation.x = Math.PI;
  envScene.add(ceil);

  // Floor reflection — dark polished concrete
  const floorGeo = new THREE.SphereGeometry(40, 16, 8, 0, Math.PI * 2, Math.PI * 0.75, Math.PI * 0.25);
  const floorMat = new THREE.MeshBasicMaterial({
    color: 0x0d0c0a,
    side: THREE.BackSide,
  });
  envScene.add(new THREE.Mesh(floorGeo, floorMat));

  // Wall bands — warm brick tones from 4 sides
  const wallMaterials: THREE.MeshBasicMaterial[] = [];
  for (let i = 0; i < 4; i++) {
    const wallGeo = new THREE.PlaneGeometry(30, 15);
    const wallMat = new THREE.MeshBasicMaterial({
      color: 0x3a2820,
      side: THREE.DoubleSide,
    });
    wallMaterials.push(wallMat);
    const wall = new THREE.Mesh(wallGeo, wallMat);
    const angle = (i / 4) * Math.PI * 2;
    wall.position.set(Math.cos(angle) * 35, 5, Math.sin(angle) * 35);
    wall.lookAt(0, 5, 0);
    envScene.add(wall);
  }

  // Track light spots — bright warm points at ceiling level
  const spotMaterials: THREE.MeshBasicMaterial[] = [];
  for (let i = 0; i < 6; i++) {
    const spotGeo = new THREE.SphereGeometry(2, 8, 8);
    const spotMat = new THREE.MeshBasicMaterial({ color: 0xffe8c0 });
    spotMaterials.push(spotMat);
    const spot = new THREE.Mesh(spotGeo, spotMat);
    const angle = (i / 6) * Math.PI * 2;
    spot.position.set(Math.cos(angle) * 15, 18, Math.sin(angle) * 15);
    envScene.add(spot);
  }

  const envRenderTarget = pmrem.fromScene(envScene as any, 0.02);
  const texture = envRenderTarget.texture;

  // Cleanup — dispose PMREM render target, generator, and all env scene materials/geometries
  pmrem.dispose();
  envRenderTarget.dispose();
  skyGeo.dispose();
  skyMat.dispose();
  ceilGeo.dispose();
  ceilMat.dispose();
  floorGeo.dispose();
  floorMat.dispose();
  wallMaterials.forEach(m => m.dispose());
  spotMaterials.forEach(m => m.dispose());
  // Dispose wall and spot geometries via traversal
  envScene.traverse(child => {
    if (child instanceof THREE.Mesh && child.geometry) {
      child.geometry.dispose();
    }
  });

  return texture;
}