Refactoring: split out objects in scene
This commit is contained in:
parent
1f3f5b372c
commit
15cb69a120
@ -1,7 +1,7 @@
|
||||
import * as THREE from 'three';
|
||||
import { state, initState } from '../state.js';
|
||||
import { EffectsManager } from '../effects/EffectsManager.js';
|
||||
import { createSceneObjects } from '../scene/scene.js';
|
||||
import { createSceneObjects } from '../scene/root.js';
|
||||
import { animate, onWindowResize } from './animate.js';
|
||||
import { loadVideoFile, playNextVideo } from './video-player.js';
|
||||
|
||||
|
||||
116
tv-player/src/scene/bookshelf.js
Normal file
116
tv-player/src/scene/bookshelf.js
Normal file
@ -0,0 +1,116 @@
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
import { getRandomColor, seededRandom } from '../utils.js';
|
||||
|
||||
export function createBookshelf(x, z, rotationY, uniqueSeed) {
|
||||
state.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);
|
||||
|
||||
state.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.scene.add(shelfGroup);
|
||||
}
|
||||
41
tv-player/src/scene/door.js
Normal file
41
tv-player/src/scene/door.js
Normal file
@ -0,0 +1,41 @@
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
|
||||
export 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);
|
||||
|
||||
state.scene.add(doorGroup);
|
||||
}
|
||||
78
tv-player/src/scene/room-walls.js
Normal file
78
tv-player/src/scene/room-walls.js
Normal file
@ -0,0 +1,78 @@
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
|
||||
export function createRoomWalls() {
|
||||
const wallTexture = state.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(state.roomSize, state.roomHeight), wallMaterial);
|
||||
backWall.position.set(0, state.roomHeight / 2, -state.roomSize / 2);
|
||||
backWall.receiveShadow = true;
|
||||
state.scene.add(backWall);
|
||||
|
||||
// 2. Front Wall (behind the camera)
|
||||
const frontWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||
frontWall.position.set(0, state.roomHeight / 2, state.roomSize / 2);
|
||||
frontWall.rotation.y = Math.PI;
|
||||
frontWall.receiveShadow = true;
|
||||
state.scene.add(frontWall);
|
||||
|
||||
// 3. Left Wall
|
||||
const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||
leftWall.rotation.y = Math.PI / 2;
|
||||
leftWall.position.set(-state.roomSize / 2, state.roomHeight / 2, 0);
|
||||
leftWall.receiveShadow = true;
|
||||
state.scene.add(leftWall);
|
||||
|
||||
// 4. Right Wall
|
||||
const rightWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||
rightWall.rotation.y = -Math.PI / 2;
|
||||
rightWall.position.set(state.roomSize / 2, state.roomHeight / 2, 0);
|
||||
rightWall.receiveShadow = true;
|
||||
state.scene.add(rightWall);
|
||||
|
||||
// 5. Ceiling
|
||||
const ceilingGeometry = new THREE.PlaneGeometry(state.roomSize, state.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, state.roomHeight, 0);
|
||||
ceiling.receiveShadow = true;
|
||||
state.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.MeshPhongMaterial({
|
||||
color: 0x0a1a3a,
|
||||
emissive: 0x0a1a3a,
|
||||
emissiveIntensity: 0.5,
|
||||
side: THREE.FrontSide
|
||||
});
|
||||
const windowPane = new THREE.Mesh(windowGeometry, nightSkyMaterial);
|
||||
|
||||
const windowZ = -state.roomSize / 2 + 0.001;
|
||||
windowPane.position.set(-3.5, state.roomHeight * 0.5 + 1.5, windowZ);
|
||||
state.scene.add(windowPane);
|
||||
}
|
||||
137
tv-player/src/scene/root.js
Normal file
137
tv-player/src/scene/root.js
Normal file
@ -0,0 +1,137 @@
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
import { createRoomWalls } from './room-walls.js';
|
||||
import { createBookshelf } from './bookshelf.js';
|
||||
import { createDoor } from './door.js';
|
||||
import { createTvSet } from './tv-set.js';
|
||||
|
||||
// --- Scene Modeling Function ---
|
||||
export function createSceneObjects() {
|
||||
// --- Materials (MeshPhongMaterial) ---
|
||||
const darkMetal = new THREE.MeshPhongMaterial({
|
||||
color: 0x6b6b6b,
|
||||
shininess: 80,
|
||||
specular: 0x888888
|
||||
});
|
||||
|
||||
// --- 1. Floor ---
|
||||
const floorGeometry = new THREE.PlaneGeometry(20, 20);
|
||||
const floorTexture = state.loader.load('/textures/floor.jpg');
|
||||
floorTexture.wrapS = THREE.RepeatWrapping;
|
||||
floorTexture.wrapT = THREE.RepeatWrapping;
|
||||
floorTexture.repeat.set(state.roomSize, state.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;
|
||||
state.scene.add(floor);
|
||||
|
||||
state.landingSurfaces.push(floor);
|
||||
|
||||
createRoomWalls();
|
||||
|
||||
// 3. Lighting (Minimal and focused)
|
||||
const ambientLight = new THREE.AmbientLight(0x111111, 1);
|
||||
state.scene.add(ambientLight);
|
||||
|
||||
const roomLight = new THREE.PointLight(0xffaa55, 0.5, state.roomSize);
|
||||
roomLight.position.set(0, 1.8, 0);
|
||||
state.scene.add(roomLight);
|
||||
|
||||
|
||||
createTvSet(-state.roomSize/2 + 1.2, -state.roomSize/2 + 0.8, Math.PI * 0.1);
|
||||
|
||||
// --- 5. Standing Lamp ---
|
||||
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
|
||||
state.lampLightPoint = new THREE.PointLight(0xffaa00, state.originalLampIntensity, 4);
|
||||
state.lampLightPoint.position.set(-0.01, state.roomHeight-0.9, 0.01);
|
||||
state.lampLightPoint.castShadow = true;
|
||||
// Optimization: Reduced map size and far plane to ease resource burden
|
||||
state.lampLightPoint.shadow.mapSize.width = 512;
|
||||
state.lampLightPoint.shadow.mapSize.height = 512;
|
||||
state.lampLightPoint.shadow.camera.near = 0.1;
|
||||
state.lampLightPoint.shadow.camera.far = 4; // Matches the light's attenuation distance (4)
|
||||
state.lampLightPoint.penumbra = 0.5;
|
||||
|
||||
state.lampLightSpot = new THREE.SpotLight(0xffaa00, state.originalLampIntensity, 4);
|
||||
state.lampLightSpot.position.set(-0.01, 1.0, 0.01);
|
||||
state.lampLightSpot.target.position.set(0, 5, 0);
|
||||
state.lampLightSpot.castShadow = true;
|
||||
// Optimization: Reduced map size and far plane to ease resource burden
|
||||
state.lampLightSpot.shadow.mapSize.width = 512;
|
||||
state.lampLightSpot.shadow.mapSize.height = 512;
|
||||
state.lampLightSpot.shadow.camera.near = 0.1;
|
||||
state.lampLightSpot.shadow.camera.far = 4; // Matches the light's attenuation distance (4)
|
||||
state.lampLightSpot.penumbra = 1;
|
||||
|
||||
const lampGroup = new THREE.Group();
|
||||
lampGroup.add(baseMesh, poleMesh, shadeMesh, state.lampLightSpot, state.lampLightSpot.target, state.lampLightPoint);
|
||||
lampGroup.position.set(0.8, 0.7, -state.roomSize/2+0.5);
|
||||
|
||||
state.scene.add(lampGroup);
|
||||
|
||||
state.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;
|
||||
state.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;
|
||||
state.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;
|
||||
state.scene.add(cassette);
|
||||
|
||||
createDoor(state.roomSize/2, -state.roomSize/2 * 0.5, -Math.PI/2);
|
||||
createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.2, Math.PI/2, 0);
|
||||
createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.7, Math.PI/2, 0);
|
||||
createBookshelf(state.roomSize/2 * 0.7, -state.roomSize/2+0.3, 0, 1);
|
||||
|
||||
}
|
||||
@ -1,586 +0,0 @@
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
import { getRandomColor, seededRandom } from '../utils.js';
|
||||
import { createVcrDisplay } from './vcr-display.js';
|
||||
|
||||
// --- Room Walls Function ---
|
||||
function createRoomWalls() {
|
||||
const wallTexture = state.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(state.roomSize, state.roomHeight), wallMaterial);
|
||||
backWall.position.set(0, state.roomHeight / 2, -state.roomSize / 2);
|
||||
backWall.receiveShadow = true;
|
||||
state.scene.add(backWall);
|
||||
|
||||
// 2. Front Wall (behind the camera)
|
||||
const frontWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||
frontWall.position.set(0, state.roomHeight / 2, state.roomSize / 2);
|
||||
frontWall.rotation.y = Math.PI;
|
||||
frontWall.receiveShadow = true;
|
||||
state.scene.add(frontWall);
|
||||
|
||||
// 3. Left Wall
|
||||
const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||
leftWall.rotation.y = Math.PI / 2;
|
||||
leftWall.position.set(-state.roomSize / 2, state.roomHeight / 2, 0);
|
||||
leftWall.receiveShadow = true;
|
||||
state.scene.add(leftWall);
|
||||
|
||||
// 4. Right Wall
|
||||
const rightWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||
rightWall.rotation.y = -Math.PI / 2;
|
||||
rightWall.position.set(state.roomSize / 2, state.roomHeight / 2, 0);
|
||||
rightWall.receiveShadow = true;
|
||||
state.scene.add(rightWall);
|
||||
|
||||
// 5. Ceiling
|
||||
const ceilingGeometry = new THREE.PlaneGeometry(state.roomSize, state.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, state.roomHeight, 0);
|
||||
ceiling.receiveShadow = true;
|
||||
state.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.MeshPhongMaterial({
|
||||
color: 0x0a1a3a,
|
||||
emissive: 0x0a1a3a,
|
||||
emissiveIntensity: 0.5,
|
||||
side: THREE.FrontSide
|
||||
});
|
||||
const windowPane = new THREE.Mesh(windowGeometry, nightSkyMaterial);
|
||||
|
||||
const windowZ = -state.roomSize / 2 + 0.001;
|
||||
windowPane.position.set(-3.5, state.roomHeight * 0.5 + 1.5, windowZ);
|
||||
state.scene.add(windowPane);
|
||||
}
|
||||
|
||||
function createBookshelf(x, z, rotationY, uniqueSeed) {
|
||||
state.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);
|
||||
|
||||
state.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.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);
|
||||
|
||||
state.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
|
||||
const vcrDisplayLight = new THREE.PointLight(0x00ff44, 0.03, 1.8);
|
||||
vcrDisplayLight.position.set(0.23, 0.03, 0.35 + 0.03);
|
||||
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 });
|
||||
state.tvScreen = new THREE.Mesh(screenGeometry, screenMaterial);
|
||||
|
||||
// Position the curved screen
|
||||
state.tvScreen.position.set(0.0, 1.5, -2.1);
|
||||
state.tvScreen.material = new THREE.MeshPhongMaterial({
|
||||
color: 0x0a0a0a, // Deep black
|
||||
shininess: 5,
|
||||
specular: 0x111111
|
||||
});
|
||||
state.tvScreen.material.needsUpdate = true;
|
||||
tvGroup.add(state.tvScreen);
|
||||
|
||||
tvGroup.position.set(x, 0, z);
|
||||
tvGroup.rotation.y = rotY;
|
||||
|
||||
// Light from the screen (initially low intensity, will increase when video loads)
|
||||
state.screenLight = new THREE.PointLight(0xffffff, 0, 10);
|
||||
state.screenLight.position.set(0, 1.5, 1.0);
|
||||
// Screen light casts shadows
|
||||
state.screenLight.castShadow = true;
|
||||
state.screenLight.shadow.mapSize.width = 1024;
|
||||
state.screenLight.shadow.mapSize.height = 1024;
|
||||
state.screenLight.shadow.camera.near = 0.2;
|
||||
state.screenLight.shadow.camera.far = 5;
|
||||
tvGroup.add(state.screenLight);
|
||||
|
||||
// -- VCR --
|
||||
const vcr = createVcr();
|
||||
vcr.position.set(-0.3, 0.6, 0.05);
|
||||
tvGroup.add(vcr);
|
||||
|
||||
state.scene.add(tvGroup);
|
||||
}
|
||||
|
||||
// --- Scene Modeling Function ---
|
||||
export 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 = state.loader.load('/textures/floor.jpg');
|
||||
floorTexture.wrapS = THREE.RepeatWrapping;
|
||||
floorTexture.wrapT = THREE.RepeatWrapping;
|
||||
floorTexture.repeat.set(state.roomSize, state.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;
|
||||
state.scene.add(floor);
|
||||
|
||||
state.landingSurfaces.push(floor);
|
||||
|
||||
createRoomWalls();
|
||||
|
||||
// 3. Lighting (Minimal and focused)
|
||||
const ambientLight = new THREE.AmbientLight(0x111111, 1);
|
||||
state.scene.add(ambientLight);
|
||||
|
||||
const roomLight = new THREE.PointLight(0xffaa55, 0.5, state.roomSize);
|
||||
roomLight.position.set(0, 1.8, 0);
|
||||
state.scene.add(roomLight);
|
||||
|
||||
|
||||
createTvSet(-state.roomSize/2 + 1.2, -state.roomSize/2 + 0.8, Math.PI * 0.1);
|
||||
|
||||
// --- 5. Standing Lamp ---
|
||||
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
|
||||
state.lampLightPoint = new THREE.PointLight(0xffaa00, state.originalLampIntensity, 4);
|
||||
state.lampLightPoint.position.set(-0.01, state.roomHeight-0.9, 0.01);
|
||||
state.lampLightPoint.castShadow = true;
|
||||
// Optimization: Reduced map size and far plane to ease resource burden
|
||||
state.lampLightPoint.shadow.mapSize.width = 512;
|
||||
state.lampLightPoint.shadow.mapSize.height = 512;
|
||||
state.lampLightPoint.shadow.camera.near = 0.1;
|
||||
state.lampLightPoint.shadow.camera.far = 4; // Matches the light's attenuation distance (4)
|
||||
state.lampLightPoint.penumbra = 0.5;
|
||||
|
||||
state.lampLightSpot = new THREE.SpotLight(0xffaa00, state.originalLampIntensity, 4);
|
||||
state.lampLightSpot.position.set(-0.01, 1.0, 0.01);
|
||||
state.lampLightSpot.target.position.set(0, 5, 0);
|
||||
state.lampLightSpot.castShadow = true;
|
||||
// Optimization: Reduced map size and far plane to ease resource burden
|
||||
state.lampLightSpot.shadow.mapSize.width = 512;
|
||||
state.lampLightSpot.shadow.mapSize.height = 512;
|
||||
state.lampLightSpot.shadow.camera.near = 0.1;
|
||||
state.lampLightSpot.shadow.camera.far = 4; // Matches the light's attenuation distance (4)
|
||||
state.lampLightSpot.penumbra = 1;
|
||||
|
||||
const lampGroup = new THREE.Group();
|
||||
lampGroup.add(baseMesh, poleMesh, shadeMesh, state.lampLightSpot, state.lampLightSpot.target, state.lampLightPoint);
|
||||
lampGroup.position.set(0.8, 0.7, -state.roomSize/2+0.5);
|
||||
|
||||
state.scene.add(lampGroup);
|
||||
|
||||
state.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;
|
||||
state.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;
|
||||
state.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;
|
||||
state.scene.add(cassette);
|
||||
|
||||
createDoor(state.roomSize/2, -state.roomSize/2 * 0.5, -Math.PI/2);
|
||||
createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.2, Math.PI/2, 0);
|
||||
createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.7, Math.PI/2, 0);
|
||||
createBookshelf(state.roomSize/2 * 0.7, -state.roomSize/2+0.3, 0, 1);
|
||||
|
||||
}
|
||||
167
tv-player/src/scene/tv-set.js
Normal file
167
tv-player/src/scene/tv-set.js
Normal file
@ -0,0 +1,167 @@
|
||||
import * as THREE from 'three';
|
||||
import { state } from '../state.js';
|
||||
import { createVcr } from './vcr.js';
|
||||
|
||||
export function createTvSet(x, z, rotY) {
|
||||
// --- Materials (MeshPhongMaterial) ---
|
||||
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 });
|
||||
state.tvScreen = new THREE.Mesh(screenGeometry, screenMaterial);
|
||||
|
||||
// Position the curved screen
|
||||
state.tvScreen.position.set(0.0, 1.5, -2.1);
|
||||
state.tvScreen.material = new THREE.MeshPhongMaterial({
|
||||
color: 0x0a0a0a, // Deep black
|
||||
shininess: 5,
|
||||
specular: 0x111111
|
||||
});
|
||||
state.tvScreen.material.needsUpdate = true;
|
||||
tvGroup.add(state.tvScreen);
|
||||
|
||||
tvGroup.position.set(x, 0, z);
|
||||
tvGroup.rotation.y = rotY;
|
||||
|
||||
// Light from the screen (initially low intensity, will increase when video loads)
|
||||
state.screenLight = new THREE.PointLight(0xffffff, 0, 10);
|
||||
state.screenLight.position.set(0, 1.5, 1.0);
|
||||
// Screen light casts shadows
|
||||
state.screenLight.castShadow = true;
|
||||
state.screenLight.shadow.mapSize.width = 1024;
|
||||
state.screenLight.shadow.mapSize.height = 1024;
|
||||
state.screenLight.shadow.camera.near = 0.2;
|
||||
state.screenLight.shadow.camera.far = 5;
|
||||
tvGroup.add(state.screenLight);
|
||||
|
||||
// -- VCR --
|
||||
const vcr = createVcr();
|
||||
vcr.position.set(-0.3, 0.6, 0.05);
|
||||
tvGroup.add(vcr);
|
||||
|
||||
state.scene.add(tvGroup);
|
||||
}
|
||||
51
tv-player/src/scene/vcr.js
Normal file
51
tv-player/src/scene/vcr.js
Normal file
@ -0,0 +1,51 @@
|
||||
import * as THREE from 'three';
|
||||
import { createVcrDisplay } from './vcr-display.js';
|
||||
|
||||
export 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
|
||||
const vcrDisplayLight = new THREE.PointLight(0x00ff44, 0.03, 1.8);
|
||||
vcrDisplayLight.position.set(0.23, 0.03, 0.35 + 0.03);
|
||||
vcrDisplayLight.castShadow = true;
|
||||
vcrDisplayLight.shadow.mapSize.width = 256;
|
||||
vcrDisplayLight.shadow.mapSize.height = 256;
|
||||
vcrGroup.add(vcrDisplayLight);
|
||||
|
||||
return vcrGroup;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user