/** * Workshop scene — room geometry, lighting, materials. * * A dark stone room with a wooden desk, crystal ball, fireplace glow, * and faint emerald ambient light. This is Timmy's Workshop. */ import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"; const WALL_COLOR = 0x1a1a2e; const FLOOR_COLOR = 0x1a1a1a; const DESK_COLOR = 0x3e2723; const DESK_TOP_COLOR = 0x4e342e; /** * Build the room and add it to the given scene. * Returns { crystalBall } for animation. */ export function buildRoom(scene) { // --- Floor --- const floorGeo = new THREE.PlaneGeometry(8, 8); const floorMat = new THREE.MeshStandardMaterial({ color: FLOOR_COLOR, roughness: 0.9, }); const floor = new THREE.Mesh(floorGeo, floorMat); floor.rotation.x = -Math.PI / 2; floor.receiveShadow = true; scene.add(floor); // --- Back wall --- const wallGeo = new THREE.PlaneGeometry(8, 4); const wallMat = new THREE.MeshStandardMaterial({ color: WALL_COLOR, roughness: 0.95, }); const backWall = new THREE.Mesh(wallGeo, wallMat); backWall.position.set(0, 2, -4); scene.add(backWall); // --- Side walls --- const leftWall = new THREE.Mesh(wallGeo, wallMat); leftWall.position.set(-4, 2, 0); leftWall.rotation.y = Math.PI / 2; scene.add(leftWall); const rightWall = new THREE.Mesh(wallGeo, wallMat); rightWall.position.set(4, 2, 0); rightWall.rotation.y = -Math.PI / 2; scene.add(rightWall); // --- Desk --- // Table top const topGeo = new THREE.BoxGeometry(1.8, 0.08, 0.9); const topMat = new THREE.MeshStandardMaterial({ color: DESK_TOP_COLOR, roughness: 0.6, }); const tableTop = new THREE.Mesh(topGeo, topMat); tableTop.position.set(0, 0.85, -0.3); tableTop.castShadow = true; scene.add(tableTop); // Legs const legGeo = new THREE.BoxGeometry(0.08, 0.85, 0.08); const legMat = new THREE.MeshStandardMaterial({ color: DESK_COLOR, roughness: 0.7, }); const offsets = [ [-0.8, -0.35], [0.8, -0.35], [-0.8, 0.05], [0.8, 0.05], ]; for (const [x, z] of offsets) { const leg = new THREE.Mesh(legGeo, legMat); leg.position.set(x, 0.425, z - 0.3); scene.add(leg); } // --- Scrolls / papers on desk (simple flat boxes) --- const paperGeo = new THREE.BoxGeometry(0.3, 0.005, 0.2); const paperMat = new THREE.MeshStandardMaterial({ color: 0xd4c5a0, roughness: 0.9, }); const paper1 = new THREE.Mesh(paperGeo, paperMat); paper1.position.set(-0.4, 0.895, -0.35); paper1.rotation.y = 0.15; scene.add(paper1); const paper2 = new THREE.Mesh(paperGeo, paperMat); paper2.position.set(0.5, 0.895, -0.2); paper2.rotation.y = -0.3; scene.add(paper2); // --- Crystal ball --- const ballGeo = new THREE.SphereGeometry(0.12, 16, 14); const ballMat = new THREE.MeshPhysicalMaterial({ color: 0x88ccff, roughness: 0.05, metalness: 0.0, transmission: 0.9, thickness: 0.3, transparent: true, opacity: 0.7, }); const crystalBall = new THREE.Mesh(ballGeo, ballMat); crystalBall.position.set(0.15, 1.01, -0.3); scene.add(crystalBall); // Crystal ball base const baseGeo = new THREE.CylinderGeometry(0.08, 0.1, 0.04, 8); const baseMat = new THREE.MeshStandardMaterial({ color: 0x444444, roughness: 0.3, metalness: 0.5, }); const base = new THREE.Mesh(baseGeo, baseMat); base.position.set(0.15, 0.9, -0.3); scene.add(base); // Crystal ball inner glow const innerLight = new THREE.PointLight(0x88ccff, 0.3, 2); innerLight.position.copy(crystalBall.position); scene.add(innerLight); // --- Lighting --- // Fireplace glow (warm, off-screen stage left) const fireLight = new THREE.PointLight(0xff6622, 1.2, 8); fireLight.position.set(-3.5, 1.2, -1.0); fireLight.castShadow = true; fireLight.shadow.mapSize.width = 512; fireLight.shadow.mapSize.height = 512; scene.add(fireLight); // Secondary warm fill const fillLight = new THREE.PointLight(0xff8844, 0.3, 6); fillLight.position.set(-2.0, 0.5, 1.0); scene.add(fillLight); // Emerald ambient const ambient = new THREE.AmbientLight(0x00b450, 0.15); scene.add(ambient); // Faint overhead to keep things readable const overhead = new THREE.PointLight(0x887766, 0.2, 8); overhead.position.set(0, 3.5, 0); scene.add(overhead); return { crystalBall, fireLight }; }