diff --git a/tv-player/src/core/init.js b/tv-player/src/core/init.js index de82611..f724cb6 100644 --- a/tv-player/src/core/init.js +++ b/tv-player/src/core/init.js @@ -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'; diff --git a/tv-player/src/scene/bookshelf.js b/tv-player/src/scene/bookshelf.js new file mode 100644 index 0000000..03d29be --- /dev/null +++ b/tv-player/src/scene/bookshelf.js @@ -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); +} \ No newline at end of file diff --git a/tv-player/src/scene/door.js b/tv-player/src/scene/door.js new file mode 100644 index 0000000..dfa3a68 --- /dev/null +++ b/tv-player/src/scene/door.js @@ -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); +} \ No newline at end of file diff --git a/tv-player/src/scene/room-walls.js b/tv-player/src/scene/room-walls.js new file mode 100644 index 0000000..7ac1e75 --- /dev/null +++ b/tv-player/src/scene/room-walls.js @@ -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); +} \ No newline at end of file diff --git a/tv-player/src/scene/root.js b/tv-player/src/scene/root.js new file mode 100644 index 0000000..a30bc39 --- /dev/null +++ b/tv-player/src/scene/root.js @@ -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); + +} diff --git a/tv-player/src/scene/scene.js b/tv-player/src/scene/scene.js deleted file mode 100644 index 2e7ce98..0000000 --- a/tv-player/src/scene/scene.js +++ /dev/null @@ -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); - -} diff --git a/tv-player/src/scene/tv-set.js b/tv-player/src/scene/tv-set.js new file mode 100644 index 0000000..a0ba50d --- /dev/null +++ b/tv-player/src/scene/tv-set.js @@ -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); +} \ No newline at end of file diff --git a/tv-player/src/scene/vcr.js b/tv-player/src/scene/vcr.js new file mode 100644 index 0000000..a18fca7 --- /dev/null +++ b/tv-player/src/scene/vcr.js @@ -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; +} \ No newline at end of file