← back to Dw War Room

src/scene/Environment.ts

184 lines

// ═══════════════════════════════════════════════
// DW War Room 3D — Room Environment
// Architectural shell: floor grid, pillars, ceiling, walls
// ═══════════════════════════════════════════════

import * as THREE from 'three';

export function createEnvironment(scene: THREE.Scene): void {
  addFloorWithGrid(scene);
  addPillars(scene);
  addCeiling(scene);
  addWallSegments(scene);
}

// ─── Floor + Grid ───────────────────────────────
function addFloorWithGrid(scene: THREE.Scene): void {
  // Base floor plane — industrial concrete/polished stone (not pure metallic)
  const floorGeo = new THREE.PlaneGeometry(60, 60);
  const floorMat = new THREE.MeshStandardMaterial({
    color: 0x0b0f14,
    metalness: 0.2,
    roughness: 0.65,
  });
  const floor = new THREE.Mesh(floorGeo, floorMat);
  floor.rotation.x = -Math.PI / 2;
  floor.position.y = -0.01;
  floor.receiveShadow = true;
  scene.add(floor);

  // Grid overlay — subtle cool-grey lines, not glowing neon
  const grid = new THREE.GridHelper(60, 40, 0x1a2530, 0x0d1520);
  grid.position.y = 0.01;
  scene.add(grid);
}

// ─── Octagonal Pillar Ring ───────────────────────
function addPillars(scene: THREE.Scene): void {
  const COUNT = 8;
  const RADIUS = 22;
  const PILLAR_HEIGHT = 10;

  // Brushed aluminum pillar — high metalness, low roughness
  const pillarMat = new THREE.MeshStandardMaterial({
    color: 0x141c24,
    emissive: new THREE.Color(0x0a1018),
    emissiveIntensity: 0.1,
    metalness: 0.8,
    roughness: 0.2,
  });

  // Warm gold torus rings at base and top — corporate accent, not neon
  const ringMat = new THREE.MeshStandardMaterial({
    color: 0xb8860b,
    emissive: new THREE.Color(0xc8900c),
    emissiveIntensity: 0.6,
    metalness: 0.7,
    roughness: 0.3,
    transparent: true,
    opacity: 0.55,
  });

  for (let i = 0; i < COUNT; i++) {
    const angle = (i / COUNT) * Math.PI * 2;
    const x = Math.cos(angle) * RADIUS;
    const z = Math.sin(angle) * RADIUS;

    // Main pillar body — octagonal cross-section (8 radial segments)
    const pillarGeo = new THREE.CylinderGeometry(0.3, 0.3, PILLAR_HEIGHT, 8);
    const pillar = new THREE.Mesh(pillarGeo, pillarMat);
    pillar.position.set(x, PILLAR_HEIGHT / 2, z);
    scene.add(pillar);

    // Base ring
    const baseRingGeo = new THREE.TorusGeometry(0.55, 0.06, 8, 24);
    const baseRing = new THREE.Mesh(baseRingGeo, ringMat);
    baseRing.rotation.x = Math.PI / 2;
    baseRing.position.set(x, 0.12, z);
    scene.add(baseRing);

    // Top ring
    const topRingGeo = new THREE.TorusGeometry(0.55, 0.06, 8, 24);
    const topRing = new THREE.Mesh(topRingGeo, ringMat);
    topRing.rotation.x = Math.PI / 2;
    topRing.position.set(x, PILLAR_HEIGHT - 0.12, z);
    scene.add(topRing);
  }
}

// ─── Ceiling + Recessed Light Slots ─────────────
function addCeiling(scene: THREE.Scene): void {
  const CEILING_Y = 10;

  // Ceiling plane — matte dark panel (not metallic — ceilings are typically matte)
  const ceilGeo = new THREE.PlaneGeometry(50, 50);
  const ceilMat = new THREE.MeshStandardMaterial({
    color: 0x090d12,
    metalness: 0.0,
    roughness: 0.85,
    transparent: true,
    opacity: 0.45,
    side: THREE.BackSide, // render from below
  });
  const ceiling = new THREE.Mesh(ceilGeo, ceilMat);
  ceiling.rotation.x = Math.PI / 2;
  ceiling.position.y = CEILING_Y;
  scene.add(ceiling);

  // 4 recessed light slots — warm white architectural lighting, not neon blue
  const slotMat = new THREE.MeshStandardMaterial({
    color: 0xe8e4d8,
    emissive: new THREE.Color(0xd4c090),
    emissiveIntensity: 0.7,
    metalness: 0.1,
    roughness: 0.5,
    transparent: true,
    opacity: 0.75,
  });

  const slotPositions: [number, number][] = [
    [-8, -8],
    [8, -8],
    [-8, 8],
    [8, 8],
  ];

  slotPositions.forEach(([sx, sz]) => {
    const slotGeo = new THREE.BoxGeometry(6, 0.05, 0.4);
    const slot = new THREE.Mesh(slotGeo, slotMat);
    slot.position.set(sx, CEILING_Y - 0.03, sz);
    scene.add(slot);
  });
}

// ─── Wall Segments ───────────────────────────────
function addWallSegments(scene: THREE.Scene): void {
  const WALL_HEIGHT = 10;
  const WALL_LENGTH = 50;
  const OFFSET = 25;

  // Matte architectural walls — zero metalness, high roughness (concrete/gypsum)
  const wallMat = new THREE.MeshStandardMaterial({
    color: 0x0a0e14,
    emissive: new THREE.Color(0x080c10),
    emissiveIntensity: 0.05,
    metalness: 0.0,
    roughness: 0.75,
    transparent: true,
    opacity: 0.6,
    side: THREE.DoubleSide,
  });

  // Wall configs: [x, z, rotationY]
  const walls: [number, number, number][] = [
    [0, -OFFSET, 0],          // back  (+Z face)
    [0,  OFFSET, Math.PI],    // front (-Z face)
    [-OFFSET, 0, Math.PI / 2],  // left
    [ OFFSET, 0, -Math.PI / 2], // right
  ];

  walls.forEach(([wx, wz, ry]) => {
    const wallGeo = new THREE.PlaneGeometry(WALL_LENGTH, WALL_HEIGHT);
    const wall = new THREE.Mesh(wallGeo, wallMat);
    wall.position.set(wx, WALL_HEIGHT / 2, wz);
    wall.rotation.y = ry;
    scene.add(wall);

    // Warm gold edge trim — architectural base accent, not neon purple
    const trimMat = new THREE.MeshStandardMaterial({
      color: 0xa07828,
      emissive: new THREE.Color(0xb08030),
      emissiveIntensity: 0.4,
      metalness: 0.6,
      roughness: 0.4,
      transparent: true,
      opacity: 0.45,
    });
    const trimGeo = new THREE.BoxGeometry(WALL_LENGTH, 0.06, 0.08);
    const trim = new THREE.Mesh(trimGeo, trimMat);
    trim.position.set(wx, 0.06, wz);
    trim.rotation.y = ry;
    scene.add(trim);
  });
}