// --- Room Walls Function --- function createRoomWalls() { const wallTexture = loader.load('./textures/wall.jpg'); wallTexture.wrapS = THREE.RepeatWrapping; wallTexture.wrapT = THREE.RepeatWrapping; // USING MeshPhongMaterial for specular highlights on walls const wallMaterial = new THREE.MeshPhongMaterial({ map: wallTexture, side: THREE.FrontSide, shininess: 5, specular: 0x111111 // Subtle reflection }); // 1. Back Wall (behind the TV) const backWall = new THREE.Mesh(new THREE.PlaneGeometry(roomSize, roomHeight), wallMaterial); backWall.position.set(0, roomHeight / 2, -roomSize / 2); backWall.receiveShadow = true; scene.add(backWall); // 2. Front Wall (behind the camera) const frontWall = new THREE.Mesh(new THREE.PlaneGeometry(roomSize, roomHeight), wallMaterial); frontWall.position.set(0, roomHeight / 2, roomSize / 2); frontWall.rotation.y = Math.PI; frontWall.receiveShadow = true; scene.add(frontWall); // 3. Left Wall const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(roomSize, roomHeight), wallMaterial); leftWall.rotation.y = Math.PI / 2; leftWall.position.set(-roomSize / 2, roomHeight / 2, 0); leftWall.receiveShadow = true; scene.add(leftWall); // 4. Right Wall const rightWall = new THREE.Mesh(new THREE.PlaneGeometry(roomSize, roomHeight), wallMaterial); rightWall.rotation.y = -Math.PI / 2; rightWall.position.set(roomSize / 2, roomHeight / 2, 0); rightWall.receiveShadow = true; scene.add(rightWall); // 5. Ceiling const ceilingGeometry = new THREE.PlaneGeometry(roomSize, roomSize); const ceilingTexture = wallTexture; ceilingTexture.repeat.set(4, 4); // USING MeshPhongMaterial const ceilingMaterial = new THREE.MeshPhongMaterial({ map: ceilingTexture, side: THREE.FrontSide, shininess: 5, specular: 0x111111 }); const ceiling = new THREE.Mesh(ceilingGeometry, ceilingMaterial); ceiling.rotation.x = Math.PI / 2; ceiling.position.set(0, roomHeight, 0); ceiling.receiveShadow = true; scene.add(ceiling); // --- 6. Add a Window to the Back Wall --- const windowWidth = 1.5; const windowHeight = 1.2; const windowGeometry = new THREE.PlaneGeometry(windowWidth, windowHeight); const nightSkyMaterial = new THREE.MeshBasicMaterial({ color: 0x0a1a3a, emissive: 0x0a1a3a, emissiveIntensity: 0.5, side: THREE.FrontSide }); const windowPane = new THREE.Mesh(windowGeometry, nightSkyMaterial); const windowZ = -roomSize / 2 + 0.001; windowPane.position.set(-3.5, roomHeight * 0.5 + 1.5, windowZ); scene.add(windowPane); } function createBookshelf(x, z, rotationY, uniqueSeed) { seed = uniqueSeed; // Reset seed for this specific shelf instance const shelfHeight = 2.2; const shelfDepth = 0.35; const shelfWidth = 1.2; const numShelves = 6; const woodThickness = 0.04; const woodColor = 0x5c4033; // Darker, richer wood const shelfGroup = new THREE.Group(); shelfGroup.position.set(x, 0, z); shelfGroup.rotation.y = rotationY; const woodMaterial = new THREE.MeshPhongMaterial({ color: woodColor, shininess: 30 }); // 1. Build Frame (Hollow box) // Back Panel const backGeo = new THREE.BoxGeometry(shelfWidth, shelfHeight, woodThickness); const backPanel = new THREE.Mesh(backGeo, woodMaterial); backPanel.position.set(0, shelfHeight / 2, -shelfDepth / 2 + woodThickness / 2); backPanel.castShadow = true; backPanel.receiveShadow = true; shelfGroup.add(backPanel); // Side Panels (Left & Right) const sideGeo = new THREE.BoxGeometry(woodThickness, shelfHeight, shelfDepth); const leftSide = new THREE.Mesh(sideGeo, woodMaterial); leftSide.position.set(-shelfWidth / 2 + woodThickness / 2, shelfHeight / 2, 0); leftSide.castShadow = true; leftSide.receiveShadow = true; shelfGroup.add(leftSide); const rightSide = new THREE.Mesh(sideGeo, woodMaterial); rightSide.position.set(shelfWidth / 2 - woodThickness / 2, shelfHeight / 2, 0); rightSide.castShadow = true; rightSide.receiveShadow = true; shelfGroup.add(rightSide); // Top & Bottom Panels const topBottomGeo = new THREE.BoxGeometry(shelfWidth, woodThickness, shelfDepth); const bottomPanel = new THREE.Mesh(topBottomGeo, woodMaterial); bottomPanel.position.set(0, woodThickness / 2, 0); bottomPanel.receiveShadow = true; shelfGroup.add(bottomPanel); const topPanel = new THREE.Mesh(topBottomGeo, woodMaterial); topPanel.position.set(0, shelfHeight - woodThickness / 2, 0); topPanel.castShadow = true; shelfGroup.add(topPanel); landingSurfaces.push(topPanel); // 2. Individual Shelves & Books const internalHeight = shelfHeight - (2 * woodThickness); const shelfSpacing = internalHeight / numShelves; const internalWidth = shelfWidth - (2 * woodThickness); for (let i = 0; i < numShelves; i++) { const currentShelfY = woodThickness + (i * shelfSpacing); // Shelf board (skip for the very bottom one as we have a bottom panel) if (i > 0) { const shelfBoard = new THREE.Mesh( new THREE.BoxGeometry(internalWidth, woodThickness, shelfDepth - woodThickness), // Slightly shallower to fit inside back panel woodMaterial ); shelfBoard.position.set(0, currentShelfY, woodThickness / 2); // Offset forward slightly shelfBoard.castShadow = true; shelfBoard.receiveShadow = true; shelfGroup.add(shelfBoard); } // 3. Procedural Books let currentBookX = -internalWidth / 2 + 0.01; // Start at left inside edge const shelfSurfaceY = currentShelfY + woodThickness / 2; while (currentBookX < internalWidth / 2 - 0.05) { // sizes vary const bookWidth = 0.02 + seededRandom() * 0.05; const bookHeight = (shelfSpacing * 0.6) + seededRandom() * (shelfSpacing * 0.1); const bookDepth = 0.15 + seededRandom() * 0.03; if (currentBookX + bookWidth > internalWidth / 2) break; const bookColor = getRandomColor(); const bookMat = new THREE.MeshPhongMaterial({ color: bookColor, shininess: 60 }); const bookGeo = new THREE.BoxGeometry(bookWidth, bookHeight, bookDepth); const book = new THREE.Mesh(bookGeo, bookMat); // Position: Resting on shelf, pushed towards the back with slight random variation const depthVariation = seededRandom() * 0.05; book.position.set( currentBookX + bookWidth / 2, shelfSurfaceY + bookHeight / 2, -shelfDepth / 2 + woodThickness + bookDepth / 2 + depthVariation ); book.castShadow = true; book.receiveShadow = true; shelfGroup.add(book); currentBookX += bookWidth + 0.002; // Tiny gap between books if (seededRandom() > 0.92) { currentBookX += bookWidth * 3; // random bigger gaps } } } scene.add(shelfGroup); } function createDoor(x, z, rotY) { const doorGroup = new THREE.Group(); doorGroup.position.set(x, 1.1, z); // Centered vertically for a 2.2m door doorGroup.rotation.set(0, rotY, 0); // Door Frame const frameMaterial = new THREE.MeshPhongMaterial({ color: 0x473e3a }); // Dark wood for frame const frameTop = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.1, 0.15), frameMaterial); frameTop.position.set(0, 1.15, 0); frameTop.castShadow = true; doorGroup.add(frameTop); const frameLeft = new THREE.Mesh(new THREE.BoxGeometry(0.1, 2.3, 0.15), frameMaterial); frameLeft.position.set(-0.55, 0.05, 0); frameLeft.castShadow = true; doorGroup.add(frameLeft); const frameRight = new THREE.Mesh(new THREE.BoxGeometry(0.1, 2.3, 0.15), frameMaterial); frameRight.position.set(0.55, 0.05, 0); frameRight.castShadow = true; doorGroup.add(frameRight); // Main Door Panel const doorMaterial = new THREE.MeshPhongMaterial({ color: 0x8b5a2b, shininess: 10 }); // Lighter wood for door const door = new THREE.Mesh(new THREE.BoxGeometry(1.0, 2.2, 0.08), doorMaterial); door.castShadow = true; door.receiveShadow = true; doorGroup.add(door); // Door Knob const knobMaterial = new THREE.MeshPhongMaterial({ color: 0xd4af37, shininess: 100 }); // Gold/Brass const knob = new THREE.Mesh(new THREE.SphereGeometry(0.05, 16, 16), knobMaterial); knob.position.set(0.4, 0, 0.06); // Position on the right side of the door knob.castShadow = true; doorGroup.add(knob); scene.add(doorGroup); } // --- VCR Model Function --- function createVcr() { // Materials const vcrBodyMaterial = new THREE.MeshPhongMaterial({ color: 0x222222, // Dark metallic gray shininess: 70, specular: 0x444444 }); const slotMaterial = new THREE.MeshPhongMaterial({ color: 0x0a0a0a, // Deep black shininess: 5, specular: 0x111111 }); // VCR Body const vcrBodyGeometry = new THREE.BoxGeometry(1.0, 0.2, 0.7); const vcrBody = new THREE.Mesh(vcrBodyGeometry, vcrBodyMaterial); vcrBody.position.y = 0; // Centered vcrBody.castShadow = true; vcrBody.receiveShadow = true; // Cassette Slot / Front Face const slotGeometry = new THREE.BoxGeometry(0.9, 0.05, 0.01); const slotMesh = new THREE.Mesh(slotGeometry, slotMaterial); slotMesh.position.set(0, -0.05, 0.35 + 0.005); slotMesh.castShadow = true; slotMesh.receiveShadow = true; // VCR Display const displayMesh = createVcrDisplay(); displayMesh.position.z = 0.35 + 0.005; displayMesh.position.x = 0.2; // Adjusted X for arrow displayMesh.position.y = 0.03; // VCR Group const vcrGroup = new THREE.Group(); vcrGroup.add(vcrBody, slotMesh, displayMesh); vcrGroup.position.set(0, 0.1, 0); // Position the whole VCR slightly above the floor // Light from the VCR display itself vcrDisplayLight = new THREE.PointLight(0x00ff44, 0.5, 1); vcrDisplayLight.position.set(0.3, 0.03, 0.35 + 0.05); // Move light slightly closer to VCR surface vcrDisplayLight.castShadow = true; vcrDisplayLight.shadow.mapSize.width = 256; vcrDisplayLight.shadow.mapSize.height = 256; vcrGroup.add(vcrDisplayLight); return vcrGroup; } function createTvSet(x, z, rotY) { // --- Materials (MeshPhongMaterial) --- const darkWood = new THREE.MeshPhongMaterial({ color: 0x3d352e, shininess: 10 }); const darkMetal = new THREE.MeshPhongMaterial({ color: 0x6b6b6b, shininess: 80, specular: 0x888888 }); const tvPlastic = new THREE.MeshPhongMaterial({ color: 0x4d4d4d, shininess: 30 }); const tvGroup = new THREE.Group(); // --- TV Table Dimensions & Material --- const woodColor = 0x5a3e36; // Dark brown wood const tableHeight = 0.7; // Height from floor to top surface const tableWidth = 2.0; const tableDepth = 1.0; const legThickness = 0.05; const shelfThickness = 0.03; // Use standard material for realistic shadowing const material = new THREE.MeshStandardMaterial({ color: woodColor, roughness: 0.8, metalness: 0.1 }); // VCR gap dimensions calculation const shelfGap = 0.2; // Height of the VCR opening const shelfY = tableHeight - shelfGap - (shelfThickness / 2); // Y position of the bottom shelf // 2. Table Top const topGeometry = new THREE.BoxGeometry(tableWidth, shelfThickness, tableDepth); const tableTop = new THREE.Mesh(topGeometry, material); tableTop.position.set(0, tableHeight, 0); tableTop.castShadow = true; tableTop.receiveShadow = true; tvGroup.add(tableTop); // 3. VCR Shelf (Middle Shelf) const shelfGeometry = new THREE.BoxGeometry(tableWidth, shelfThickness, tableDepth); const vcrShelf = new THREE.Mesh(shelfGeometry, material); vcrShelf.position.set(0, shelfY, 0); vcrShelf.castShadow = true; vcrShelf.receiveShadow = true; tvGroup.add(vcrShelf); // 4. Side Walls for VCR Compartment (NEW CODE) const wallHeight = shelfGap; // Height is the gap itself const wallThickness = shelfThickness; // Reuse the shelf thickness for the wall width/depth const wallGeometry = new THREE.BoxGeometry(wallThickness, wallHeight, tableDepth); // Calculate the Y center position for the wall const wallYCenter = tableHeight - (shelfThickness / 2) - (wallHeight / 2); // Calculate the X position to be flush with the table sides const wallXPosition = (tableWidth / 2) - (wallThickness / 2); // Left Wall const sideWallLeft = new THREE.Mesh(wallGeometry, material); sideWallLeft.position.set(-wallXPosition, wallYCenter, 0); sideWallLeft.castShadow = true; sideWallLeft.receiveShadow = true; tvGroup.add(sideWallLeft); // Right Wall const sideWallRight = new THREE.Mesh(wallGeometry, material); sideWallRight.position.set(wallXPosition, wallYCenter, 0); sideWallRight.castShadow = true; sideWallRight.receiveShadow = true; tvGroup.add(sideWallRight); // 5. Legs const legHeight = shelfY; // Legs go from the floor (y=0) to the shelf (y=shelfY) const legGeometry = new THREE.BoxGeometry(legThickness, legHeight, legThickness); // Utility function to create and position a leg const createLeg = (x, z) => { const leg = new THREE.Mesh(legGeometry, material); // Position the leg so the center is at half its height leg.position.set(x, legHeight / 2, z); leg.castShadow = true; leg.receiveShadow = true; return leg; }; // Calculate offsets for positioning the legs near the corners const offset = (tableWidth / 2) - (legThickness * 2); const depthOffset = (tableDepth / 2) - (legThickness * 2); // Front Left tvGroup.add(createLeg(-offset, depthOffset)); // Front Right tvGroup.add(createLeg(offset, depthOffset)); // Back Left tvGroup.add(createLeg(-offset, -depthOffset)); // Back Right tvGroup.add(createLeg(offset, -depthOffset)); // --- 2. The TV box --- const cabinetGeometry = new THREE.BoxGeometry(1.75, 1.5, 1.0); const cabinet = new THREE.Mesh(cabinetGeometry, tvPlastic); cabinet.position.y = 1.51; cabinet.castShadow = true; cabinet.receiveShadow = true; tvGroup.add(cabinet); // --- 3. Screen Frame --- const frameGeometry = new THREE.BoxGeometry(1.5, 1.3, 0.1); const frameMaterial = new THREE.MeshPhongMaterial({ color: 0x111111, shininess: 20 }); const frame = new THREE.Mesh(frameGeometry, frameMaterial); frame.position.set(0, 1.5, 0.68); frame.castShadow = true; frame.receiveShadow = true; tvGroup.add(frame); // --- 4. Curved Screen (CRT Effect) --- const screenRadius = 3.0; // Radius for the subtle curve const screenWidth = 1.4; const screenHeight = 1.2; const thetaLength = screenWidth / screenRadius; // Calculate angle needed for the arc // Use CylinderGeometry as a segment const screenGeometry = new THREE.CylinderGeometry( screenRadius, screenRadius, screenHeight, // Cylinder height is the vertical dimension of the screen 32, 1, true, (Math.PI / 2) - (thetaLength / 2), // Start angle to center the arc thetaLength // Arc length (width) ); // Rotate the cylinder segment: // 1. Rotate around X-axis by 90 degrees to lay the height (Y) along Z (depth). //screenGeometry.rotateX(Math.PI / 2); // 2. Rotate around Y-axis by 90 degrees to align the segment's arc across the X-axis (width). screenGeometry.rotateY(-Math.PI/2); const screenMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }); tvScreen = new THREE.Mesh(screenGeometry, screenMaterial); // Position the curved screen tvScreen.position.set(0.0, 1.5, -2.1); tvScreen.material = new THREE.MeshPhongMaterial({ color: 0x0a0a0a, // Deep black shininess: 5, specular: 0x111111 }); tvScreen.material.needsUpdate = true; tvGroup.add(tvScreen); tvGroup.position.set(x, 0, z); tvGroup.rotation.y = rotY; // Light from the screen (initially low intensity, will increase when video loads) screenLight = new THREE.PointLight(0xffffff, 0, 10); screenLight.position.set(0, 1.5, 1.0); // Screen light casts shadows screenLight.castShadow = true; screenLight.shadow.mapSize.width = 1024; screenLight.shadow.mapSize.height = 1024; screenLight.shadow.camera.near = 0.2; screenLight.shadow.camera.far = 5; tvGroup.add(screenLight); // -- VCR -- const vcr = createVcr(); vcr.position.set(-0.3, 0.6, 0.05); tvGroup.add(vcr); scene.add(tvGroup); } // --- Scene Modeling Function --- function createSceneObjects() { // --- Materials (MeshPhongMaterial) --- const darkWood = new THREE.MeshPhongMaterial({ color: 0x3d352e, shininess: 10 }); const darkMetal = new THREE.MeshPhongMaterial({ color: 0x6b6b6b, shininess: 80, specular: 0x888888 }); const tvPlastic = new THREE.MeshPhongMaterial({ color: 0x2d251e, shininess: 10 }); // --- 1. Floor --- const floorGeometry = new THREE.PlaneGeometry(20, 20); const floorTexture = loader.load('./textures/floor.jpg'); floorTexture.wrapS = THREE.RepeatWrapping; floorTexture.wrapT = THREE.RepeatWrapping; floorTexture.repeat.set(roomSize, roomSize); const floorMaterial = new THREE.MeshPhongMaterial({ map: floorTexture, color: 0x555555, shininess: 5 }); const floor = new THREE.Mesh(floorGeometry, floorMaterial); floor.rotation.x = -Math.PI / 2; floor.position.y = 0; floor.receiveShadow = true; scene.add(floor); landingSurfaces.push(floor); createTvSet(-roomSize/2 + 1.2, -roomSize/2 + 0.8, Math.PI * 0.1); // --- 5. Lamp (On the table, right side) --- const lampBase = new THREE.CylinderGeometry(0.05, 0.2, 0.1, 12); const lampPole = new THREE.CylinderGeometry(0.02, 0.02, 1.5, 8); const lampShade = new THREE.ConeGeometry(0.2, 0.4, 16); const baseMesh = new THREE.Mesh(lampBase, darkMetal); const poleMesh = new THREE.Mesh(lampPole, darkMetal); const shadeMesh = new THREE.Mesh(lampShade, darkMetal); // Ensure lamp parts cast shadows baseMesh.castShadow = true; baseMesh.receiveShadow = true; poleMesh.castShadow = true; poleMesh.receiveShadow = true; //shadeMesh.castShadow = true; shadeMesh.receiveShadow = true; baseMesh.position.y = -0.6; poleMesh.position.y = 0.0; shadeMesh.position.y = 0.8 + 0.1; shadeMesh.rotation.x = Math.PI; // Lamp Light (Warm Glow) - Configured to cast shadows lampLightPoint = new THREE.PointLight(0xffaa00, originalLampIntensity, 4); lampLightPoint.position.set(-0.01, roomHeight-0.9, 0.01); lampLightPoint.castShadow = true; // Optimization: Reduced map size and far plane to ease resource burden lampLightPoint.shadow.mapSize.width = 512; lampLightPoint.shadow.mapSize.height = 512; lampLightPoint.shadow.camera.near = 0.1; lampLightPoint.shadow.camera.far = 4; // Matches the light's attenuation distance (4) lampLightPoint.penumbra = 0.5; lampLightSpot = new THREE.SpotLight(0xffaa00, originalLampIntensity, 4); lampLightSpot.position.set(-0.01, 1.0, 0.01); lampLightSpot.target.position.set(0, 5, 0); lampLightSpot.castShadow = true; // Optimization: Reduced map size and far plane to ease resource burden lampLightSpot.shadow.mapSize.width = 512; lampLightSpot.shadow.mapSize.height = 512; lampLightSpot.shadow.camera.near = 0.1; lampLightSpot.shadow.camera.far = 4; // Matches the light's attenuation distance (4) lampLightSpot.penumbra = 0.5; const lampGroup = new THREE.Group(); lampGroup.add(baseMesh, poleMesh, shadeMesh, lampLightSpot, lampLightSpot.target, lampLightPoint); lampGroup.position.set(0.8, 0.7, -roomSize/2+0.5); scene.add(lampGroup); landingSurfaces.push(shadeMesh); // --- 7. Old Camera (On the table) --- const cameraBody = new THREE.BoxGeometry(0.4, 0.3, 0.15); const cameraLens = new THREE.CylinderGeometry(0.08, 0.08, 0.05, 12); const cameraMaterial = new THREE.MeshPhongMaterial({ color: 0x333333, shininess: 50, specular: 0x444444 }); const cameraMesh = new THREE.Mesh(cameraBody, cameraMaterial); const lensMesh = new THREE.Mesh(cameraLens, cameraMaterial); lensMesh.position.z = 0.15; lensMesh.rotation.x = Math.PI/2; cameraMesh.add(lensMesh); cameraMesh.position.set(-1.7, 0.15, 0.4); cameraMesh.rotation.y = -Math.PI / 10; cameraMesh.castShadow = true; cameraMesh.receiveShadow = true; scene.add(cameraMesh); // --- 8. Pizza Box --- const boxGeometry = new THREE.BoxGeometry(0.5, 0.05, 0.5); const boxMaterial = new THREE.MeshPhongMaterial({ color: 0xe0c896, shininess: 5 }); const pizzaBox = new THREE.Mesh(boxGeometry, boxMaterial); pizzaBox.position.set(-1.8, 0.025, -0.8); pizzaBox.rotation.y = Math.PI / 5; pizzaBox.castShadow = true; pizzaBox.receiveShadow = true; scene.add(pizzaBox); // --- 8. Cassette --- const cassetteGeometry = new THREE.BoxGeometry(0.2, 0.05, 0.45); const cassetteMaterial = new THREE.MeshPhongMaterial({ color: 0xe0c896, shininess: 5 }); const cassette = new THREE.Mesh(cassetteGeometry, cassetteMaterial); cassette.position.set(-0.5, 0.025, -1.4); cassette.rotation.y = Math.PI / 3; cassette.castShadow = true; cassette.receiveShadow = true; scene.add(cassette); createDoor(roomSize/2, -roomSize/2 * 0.5, -Math.PI/2); createBookshelf(-roomSize/2 + 0.2, roomSize/2*0.2, Math.PI/2, 0); createBookshelf(-roomSize/2 + 0.2, roomSize/2*0.7, Math.PI/2, 0); createBookshelf(roomSize/2 * 0.7, -roomSize/2+0.3, 0, 1); setupFlies(); }