Refactoring: split out objects in scene

This commit is contained in:
Dejvino 2025-11-16 13:33:17 +01:00
parent 1f3f5b372c
commit 15cb69a120
8 changed files with 591 additions and 587 deletions

View File

@ -1,7 +1,7 @@
import * as THREE from 'three'; import * as THREE from 'three';
import { state, initState } from '../state.js'; import { state, initState } from '../state.js';
import { EffectsManager } from '../effects/EffectsManager.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 { animate, onWindowResize } from './animate.js';
import { loadVideoFile, playNextVideo } from './video-player.js'; import { loadVideoFile, playNextVideo } from './video-player.js';

View 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);
}

View 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);
}

View 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
View 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);
}

View File

@ -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);
}

View 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);
}

View 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;
}