Tweaked scene - tall lamp

This commit is contained in:
Dejvino 2025-11-08 19:33:49 +01:00
parent dc864ca24e
commit 6a505f0af1
2 changed files with 705 additions and 105 deletions

423
tv-player/bookshelves.html Normal file
View File

@ -0,0 +1,423 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fully Furnished Bookshelf Room (3D)</title>
<!-- Load Tailwind CSS for styling --><script src="https://cdn.tailwindcss.com"></script>
<!-- Load Three.js for 3D rendering --><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
/* Light room aesthetic */
body {
background-color: #f0f0f5; /* Light grey background */
margin: 0;
overflow: hidden;
font-family: 'Inter', sans-serif;
}
canvas {
display: block;
}
</style>
</head>
<body>
<!-- 3D Canvas will be injected here by Three.js --><script>
// --- Global Variables ---
let scene, camera, renderer;
let fanPropeller; // Reference for the rotating fan blades
// --- Seedable Random Number Generator (Mulberry32) ---
let seed = 12345; // Default seed, will be overridden per shelf
function seededRandom() {
let t = seed += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
}
const container = document.body;
// --- Initialization ---
function init() {
// 1. Scene Setup (White Room)
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// 2. Camera Setup
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1.7, 5);
// 3. Renderer Setup
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
// 4. Lighting
const ambientLight = new THREE.AmbientLight(0xaaaaaa, 0.8);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);
directionalLight.position.set(10, 15, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048; // Increased shadow map for sharper book shadows
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
// 5. Build scene
createSceneObjects();
// 6. Events
window.addEventListener('resize', onWindowResize, false);
animate();
}
// --- Utility: Random Color (seeded) ---
function getRandomColor() {
const hue = seededRandom();
const saturation = 0.6 + seededRandom() * 0.4;
const lightness = 0.3 + seededRandom() * 0.4;
return new THREE.Color().setHSL(hue, saturation, lightness).getHex();
}
// --- REVISED Procedural Bookshelf Generator ---
function createBookshelf(x, z, rotationY, uniqueSeed) {
seed = uniqueSeed; // Reset seed for this specific shelf instance
const shelfHeight = 2.2;
const shelfDepth = 0.35;
const shelfWidth = 1.2;
const numShelves = 6;
const woodThickness = 0.04;
const woodColor = 0x5c4033; // Darker, richer wood
const shelfGroup = new THREE.Group();
shelfGroup.position.set(x, 0, z);
shelfGroup.rotation.y = rotationY;
const woodMaterial = new THREE.MeshPhongMaterial({ color: woodColor, shininess: 30 });
// 1. Build Frame (Hollow box)
// Back Panel
const backGeo = new THREE.BoxGeometry(shelfWidth, shelfHeight, woodThickness);
const backPanel = new THREE.Mesh(backGeo, woodMaterial);
backPanel.position.set(0, shelfHeight / 2, -shelfDepth / 2 + woodThickness / 2);
backPanel.castShadow = true;
backPanel.receiveShadow = true;
shelfGroup.add(backPanel);
// Side Panels (Left & Right)
const sideGeo = new THREE.BoxGeometry(woodThickness, shelfHeight, shelfDepth);
const leftSide = new THREE.Mesh(sideGeo, woodMaterial);
leftSide.position.set(-shelfWidth / 2 + woodThickness / 2, shelfHeight / 2, 0);
leftSide.castShadow = true;
leftSide.receiveShadow = true;
shelfGroup.add(leftSide);
const rightSide = new THREE.Mesh(sideGeo, woodMaterial);
rightSide.position.set(shelfWidth / 2 - woodThickness / 2, shelfHeight / 2, 0);
rightSide.castShadow = true;
rightSide.receiveShadow = true;
shelfGroup.add(rightSide);
// Top & Bottom Panels
const topBottomGeo = new THREE.BoxGeometry(shelfWidth, woodThickness, shelfDepth);
const bottomPanel = new THREE.Mesh(topBottomGeo, woodMaterial);
bottomPanel.position.set(0, woodThickness / 2, 0);
bottomPanel.receiveShadow = true;
shelfGroup.add(bottomPanel);
const topPanel = new THREE.Mesh(topBottomGeo, woodMaterial);
topPanel.position.set(0, shelfHeight - woodThickness / 2, 0);
topPanel.castShadow = true;
shelfGroup.add(topPanel);
// 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) {
const bookWidth = 0.02 + seededRandom() * 0.05; // 2cm to 7cm wide
const bookHeight = (shelfSpacing * 0.6) + seededRandom() * (shelfSpacing * 0.3); // Vary height within shelf limits
const bookDepth = 0.15 + seededRandom() * 0.1; // Vary depth
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
}
}
scene.add(shelfGroup);
}
function createDoor(x, z) {
const doorGroup = new THREE.Group();
doorGroup.position.set(x, 1.1, z); // Centered vertically for a 2.2m door
// Door Frame
const frameMaterial = new THREE.MeshPhongMaterial({ color: 0x473e3a }); // Dark wood for frame
const frameTop = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.1, 0.15), frameMaterial);
frameTop.position.set(0, 1.15, 0);
frameTop.castShadow = true;
doorGroup.add(frameTop);
const frameLeft = new THREE.Mesh(new THREE.BoxGeometry(0.1, 2.3, 0.15), frameMaterial);
frameLeft.position.set(-0.55, 0.05, 0);
frameLeft.castShadow = true;
doorGroup.add(frameLeft);
const frameRight = new THREE.Mesh(new THREE.BoxGeometry(0.1, 2.3, 0.15), frameMaterial);
frameRight.position.set(0.55, 0.05, 0);
frameRight.castShadow = true;
doorGroup.add(frameRight);
// Main Door Panel
const doorMaterial = new THREE.MeshPhongMaterial({ color: 0x8b5a2b, shininess: 10 }); // Lighter wood for door
const door = new THREE.Mesh(new THREE.BoxGeometry(1.0, 2.2, 0.08), doorMaterial);
door.castShadow = true;
door.receiveShadow = true;
doorGroup.add(door);
// Door Knob
const knobMaterial = new THREE.MeshPhongMaterial({ color: 0xd4af37, shininess: 100 }); // Gold/Brass
const knob = new THREE.Mesh(new THREE.SphereGeometry(0.05, 16, 16), knobMaterial);
knob.position.set(0.4, 0, 0.06); // Position on the right side of the door
knob.castShadow = true;
doorGroup.add(knob);
scene.add(doorGroup);
}
function createDeskAndChair(x, z, rotationY) {
const group = new THREE.Group();
const woodMaterial = new THREE.MeshPhongMaterial({ color: 0x8b4513, shininess: 20 });
const metalMaterial = new THREE.MeshPhongMaterial({ color: 0x777777, shininess: 50 });
const fabricMaterial = new THREE.MeshPhongMaterial({ color: 0x2c3e50, shininess: 10 });
// Desk
const deskTop = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.05, 0.6), woodMaterial);
deskTop.position.set(0, 0.75, 0);
deskTop.castShadow = true;
group.add(deskTop);
const legGeo = new THREE.BoxGeometry(0.05, 0.75, 0.05);
const leg1 = new THREE.Mesh(legGeo, metalMaterial); leg1.position.set(0.7, 0.375, 0.25); leg1.castShadow = true; group.add(leg1);
const leg2 = new THREE.Mesh(legGeo, metalMaterial); leg2.position.set(-0.7, 0.375, 0.25); leg2.castShadow = true; group.add(leg2);
// Chair
const chairSeat = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.05, 0.5), fabricMaterial);
chairSeat.position.set(0, 0.45, 1.0); chairSeat.castShadow = true; group.add(chairSeat);
const chairBack = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.5, 0.05), fabricMaterial);
chairBack.position.set(0, 0.7, 0.75); chairBack.castShadow = true; group.add(chairBack);
const cLegGeo = new THREE.CylinderGeometry(0.02, 0.02, 0.45, 8);
const cLeg1 = new THREE.Mesh(cLegGeo, metalMaterial); cLeg1.position.set(0.2, 0.225, 1.2); cLeg1.castShadow = true; group.add(cLeg1);
const cLeg2 = new THREE.Mesh(cLegGeo, metalMaterial); cLeg2.position.set(-0.2, 0.225, 1.2); cLeg2.castShadow = true; group.add(cLeg2);
group.position.set(x, 0, z);
group.rotation.y = rotationY;
scene.add(group);
}
function createWallDecor() {
// 1. Purple Image on Left Wall
const imagePlane = new THREE.Mesh(new THREE.PlaneGeometry(1.0, 0.7), new THREE.MeshPhongMaterial({ color: 0x9c27b0 }));
imagePlane.rotation.y = Math.PI / 2;
imagePlane.position.set(-4.95, 2.0, -3.0);
scene.add(imagePlane);
const frame = new THREE.Mesh(new THREE.BoxGeometry(1.05, 0.75, 0.05), new THREE.MeshPhongMaterial({ color: 0x444444 }));
frame.rotation.y = Math.PI / 2;
frame.position.set(-4.98, 2.0, -3.0);
scene.add(frame);
// 2. Calendar (Generated via Canvas Texture)
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 768;
const ctx = canvas.getContext('2d');
// Paper background
ctx.fillStyle = '#fdfdfd';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Red Header
ctx.fillStyle = '#d32f2f';
ctx.fillRect(0, 0, canvas.width, 150);
// Month Text
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 80px Inter, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('JANUARY', canvas.width / 2, 75);
// YEAR
ctx.fillStyle = '#222222';
ctx.font = 'bold 160px Inter, sans-serif';
ctx.fillText('1987', canvas.width / 2, 280);
// Grid (simple representation of days)
ctx.strokeStyle = '#999999';
ctx.lineWidth = 4;
const startY = 400;
const endY = 720;
const startX = 40;
const endX = 472;
const rows = 5;
const cols = 7;
// Horizontal lines
for (let i = 0; i <= rows; i++) {
const y = startY + (i * (endY - startY) / rows);
ctx.beginPath();
ctx.moveTo(startX, y);
ctx.lineTo(endX, y);
ctx.stroke();
}
// Vertical lines
for (let i = 0; i <= cols; i++) {
const x = startX + (i * (endX - startX) / cols);
ctx.beginPath();
ctx.moveTo(x, startY);
ctx.lineTo(x, endY);
ctx.stroke();
}
const calendarTexture = new THREE.CanvasTexture(canvas);
// Use MeshBasicMaterial so it's bright and legible regardless of lighting angles,
// or MeshPhongMaterial with low shininess for a matte paper look.
const calendarMat = new THREE.MeshPhongMaterial({
map: calendarTexture,
shininess: 5,
color: 0xffffff // Ensure white tint doesn't darken texture
});
const calendarGeo = new THREE.PlaneGeometry(1.5, 2.25); // Large size
const calendar = new THREE.Mesh(calendarGeo, calendarMat);
calendar.rotation.y = Math.PI / 2;
// Positioned on the left wall, further forward so it's visible
calendar.position.set(-4.95, 2.5, 2.0);
scene.add(calendar);
}
function createCeilingFan() {
const fanGroup = new THREE.Group();
const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.5, 8), new THREE.MeshPhongMaterial({ color: 0xaaaaaa }));
pole.position.set(0, 4.75, 0); fanGroup.add(pole);
fanPropeller = new THREE.Group();
const bladeGeo = new THREE.BoxGeometry(0.8, 0.02, 0.15);
const bladeMat = new THREE.MeshPhongMaterial({ color: 0x555555 });
for (let i = 0; i < 3; i++) {
const blade = new THREE.Mesh(bladeGeo, bladeMat);
blade.position.set(0.4, 0, 0); blade.castShadow = true;
const pivot = new THREE.Group(); pivot.add(blade); pivot.rotation.y = (i * Math.PI * 2) / 3;
fanPropeller.add(pivot);
}
fanPropeller.position.set(0, 4.5, 0); fanGroup.add(fanPropeller);
scene.add(fanGroup);
}
// --- Main Scene ---
function createSceneObjects() {
// Room shell
const wallMat = new THREE.MeshPhongMaterial({ color: 0xfcfcfc, side: THREE.DoubleSide });
const floorMat = new THREE.MeshPhongMaterial({ color: 0xe0e0e0 });
const floor = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), floorMat);
floor.rotation.x = -Math.PI / 2; floor.receiveShadow = true; scene.add(floor);
const backWall = new THREE.Mesh(new THREE.PlaneGeometry(10, 5), wallMat);
backWall.position.set(0, 2.5, -5); backWall.receiveShadow = true; scene.add(backWall);
const rightWall = new THREE.Mesh(new THREE.PlaneGeometry(10, 5), wallMat);
rightWall.rotation.y = -Math.PI / 2; rightWall.position.set(5, 2.5, 0); rightWall.receiveShadow = true; scene.add(rightWall);
const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(10, 5), wallMat);
leftWall.rotation.y = Math.PI / 2; leftWall.position.set(-5, 2.5, 0); leftWall.receiveShadow = true; scene.add(leftWall);
const ceiling = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), wallMat);
ceiling.rotation.x = Math.PI / 2; ceiling.position.set(0, 5, 0); scene.add(ceiling);
// Placed Objects
// Back Wall: Door on the left, shelves shifted right
createDoor(-3.0, -4.95); // New door position
// Adjusted shelf positions to make room for the door
createBookshelf(-1.2, -4.8, 0, 101);
createBookshelf(0.6, -4.8, 0, 102);
createBookshelf(2.4, -4.8, 0, 103);
// Right wall shelves (Seeds: 201, 202, 203)
createBookshelf(4.8, -2.0, -Math.PI / 2, 201);
createBookshelf(4.8, 0, -Math.PI / 2, 202);
createBookshelf(4.8, 2.0, -Math.PI / 2, 203);
createDeskAndChair(-3.0, 2.5, -Math.PI * 0.15);
createWallDecor();
createCeilingFan();
}
function animate() {
requestAnimationFrame(animate);
if (fanPropeller) fanPropeller.rotation.y += 0.05;
// Camera sway
const time = Date.now() * 0.0005;
camera.position.x = Math.sin(time * 0.3) * 0.2;
camera.position.y = 1.7 + Math.cos(time * 0.2) * 0.1;
camera.lookAt(0, 1.7, 0);
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.onload = init;
</script>
</body>
</html>

View File

@ -62,14 +62,18 @@
<!-- 3D Canvas will be injected here by Three.js --><script> <!-- 3D Canvas will be injected here by Three.js --><script>
// --- Global Variables --- // --- Global Variables ---
let scene, camera, renderer, tvScreen, videoTexture, dust, screenLight, lampLight; let scene, camera, renderer, tvScreen, videoTexture, dust, screenLight, lampLightPoint, lampLightSpot;
let isVideoLoaded = false; let isVideoLoaded = false;
let videoUrls = []; // Array to hold all video URLs let videoUrls = []; // Array to hold all video URLs
let currentVideoIndex = -1; // Index of the currently playing video let currentVideoIndex = -1; // Index of the currently playing video
const originalLampIntensity = 1.5; // Base intensity for the flickering lamp const originalLampIntensity = 1.5; // Base intensity for the flickering lamp
const originalScreenIntensity = 1.5; // Base intensity for the screen glow const originalScreenIntensity = 0.2; // Base intensity for the screen glow
const screenIntensityPulse = 0.4;
const roomSize = 5;
const roomHeight = 3;
const container = document.body; const container = document.body;
const videoElement = document.getElementById('video'); const videoElement = document.getElementById('video');
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
@ -78,7 +82,24 @@
const nextTapeButton = document.getElementById('nextTapeButton'); const nextTapeButton = document.getElementById('nextTapeButton');
const loader = new THREE.TextureLoader(); const loader = new THREE.TextureLoader();
const debugLight = false; const debugLight = true;
// --- Utility: Random Color (seeded) ---
function getRandomColor() {
const hue = seededRandom();
const saturation = 0.6 + seededRandom() * 0.4;
const lightness = 0.3 + seededRandom() * 0.4;
return new THREE.Color().setHSL(hue, saturation, lightness).getHex();
}
// --- Seedable Random Number Generator (Mulberry32) ---
let seed = 12345; // Default seed, will be overridden per shelf
function seededRandom() {
let t = seed += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
}
// --- Initialization --- // --- Initialization ---
function init() { function init() {
@ -87,7 +108,8 @@
scene.background = new THREE.Color(0x000000); scene.background = new THREE.Color(0x000000);
// 2. Camera Setup // 2. Camera Setup
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const FOV = 65;
camera = new THREE.PerspectiveCamera(FOV, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1.5, 4); camera.position.set(0, 1.5, 4);
// 3. Renderer Setup // 3. Renderer Setup
@ -104,17 +126,6 @@
const ambientLight = new THREE.AmbientLight(0x111111); const ambientLight = new THREE.AmbientLight(0x111111);
scene.add(ambientLight); scene.add(ambientLight);
// Light from the screen (initially low intensity, will increase when video loads)
screenLight = new THREE.PointLight(0xffffff, 0.1, 10);
screenLight.position.set(0, 1.7, 1.2);
// Screen light casts shadows
screenLight.castShadow = true;
screenLight.shadow.mapSize.width = 1024;
screenLight.shadow.mapSize.height = 1024;
screenLight.shadow.camera.near = 0.2;
screenLight.shadow.camera.far = 10;
scene.add(screenLight);
// 5. Build the entire scene with TV and surrounding objects // 5. Build the entire scene with TV and surrounding objects
createSceneObjects(); createSceneObjects();
@ -131,8 +142,8 @@
scene.add(screenHelper); scene.add(screenHelper);
// Lamp Helper will now work since lampLight is added to the scene // Lamp Helper will now work since lampLight is added to the scene
const lampHelper = new THREE.PointLightHelper(lampLight, 0.1, 0x00ff00); // Green for lamp const lampHelperPoint = new THREE.PointLightHelper(lampLightPoint, 0.1, 0x00ff00); // Green for lamp
scene.add(lampHelper); scene.add(lampHelperPoint);
} }
// 9. Event Listeners // 9. Event Listeners
@ -166,9 +177,6 @@
specular: 0x111111 // Subtle reflection specular: 0x111111 // Subtle reflection
}); });
const roomSize = 15;
const roomHeight = 8;
// 1. Back Wall (behind the TV) // 1. Back Wall (behind the TV)
const backWall = new THREE.Mesh(new THREE.PlaneGeometry(roomSize, roomHeight), wallMaterial); const backWall = new THREE.Mesh(new THREE.PlaneGeometry(roomSize, roomHeight), wallMaterial);
backWall.position.set(0, roomHeight / 2, -roomSize / 2); backWall.position.set(0, roomHeight / 2, -roomSize / 2);
@ -249,8 +257,152 @@
scene.add(vBar); scene.add(vBar);
} }
// --- Scene Modeling Function --- function createBookshelf(x, z, rotationY, uniqueSeed) {
function createSceneObjects() { 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);
// 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) {
const bookWidth = 0.02 + seededRandom() * 0.05; // 2cm to 7cm wide
const bookHeight = (shelfSpacing * 0.6) + seededRandom() * (shelfSpacing * 0.3); // Vary height within shelf limits
const bookDepth = 0.15 + seededRandom() * 0.1; // Vary depth
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
}
}
scene.add(shelfGroup);
}
function createDoor(x, z, rotY) {
const doorGroup = new THREE.Group();
doorGroup.position.set(x, 1.1, z); // Centered vertically for a 2.2m door
doorGroup.rotation.set(0, rotY, 0);
// Door Frame
const frameMaterial = new THREE.MeshPhongMaterial({ color: 0x473e3a }); // Dark wood for frame
const frameTop = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.1, 0.15), frameMaterial);
frameTop.position.set(0, 1.15, 0);
frameTop.castShadow = true;
doorGroup.add(frameTop);
const frameLeft = new THREE.Mesh(new THREE.BoxGeometry(0.1, 2.3, 0.15), frameMaterial);
frameLeft.position.set(-0.55, 0.05, 0);
frameLeft.castShadow = true;
doorGroup.add(frameLeft);
const frameRight = new THREE.Mesh(new THREE.BoxGeometry(0.1, 2.3, 0.15), frameMaterial);
frameRight.position.set(0.55, 0.05, 0);
frameRight.castShadow = true;
doorGroup.add(frameRight);
// Main Door Panel
const doorMaterial = new THREE.MeshPhongMaterial({ color: 0x8b5a2b, shininess: 10 }); // Lighter wood for door
const door = new THREE.Mesh(new THREE.BoxGeometry(1.0, 2.2, 0.08), doorMaterial);
door.castShadow = true;
door.receiveShadow = true;
doorGroup.add(door);
// Door Knob
const knobMaterial = new THREE.MeshPhongMaterial({ color: 0xd4af37, shininess: 100 }); // Gold/Brass
const knob = new THREE.Mesh(new THREE.SphereGeometry(0.05, 16, 16), knobMaterial);
knob.position.set(0.4, 0, 0.06); // Position on the right side of the door
knob.castShadow = true;
doorGroup.add(knob);
scene.add(doorGroup);
}
function createTvSet(x, z, rotY) {
// --- Materials (MeshPhongMaterial) --- // --- Materials (MeshPhongMaterial) ---
const darkWood = new THREE.MeshPhongMaterial({ color: 0x3d352e, shininess: 10 }); const darkWood = new THREE.MeshPhongMaterial({ color: 0x3d352e, shininess: 10 });
const darkMetal = new THREE.MeshPhongMaterial({ const darkMetal = new THREE.MeshPhongMaterial({
@ -259,45 +411,38 @@
specular: 0x888888 specular: 0x888888
}); });
const tvPlastic = new THREE.MeshPhongMaterial({ color: 0x2d251e, shininess: 10 }); const tvPlastic = new THREE.MeshPhongMaterial({ color: 0x2d251e, shininess: 10 });
// --- 1. Floor ---
const floorGeometry = new THREE.PlaneGeometry(20, 20);
const floorMaterial = new THREE.MeshPhongMaterial({ color: 0x1a1a1a, shininess: 5 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.position.y = 0;
floor.receiveShadow = true;
scene.add(floor);
// --- 2. Table (TV stand) --- const tvGroup = new THREE.Group();
const tableGeometry = new THREE.BoxGeometry(4.0, 0.7, 2.5);
// --- 1. Table (TV stand) ---
const tableGeometry = new THREE.BoxGeometry(2.0, 0.7, 1.0);
const table = new THREE.Mesh(tableGeometry, darkWood); const table = new THREE.Mesh(tableGeometry, darkWood);
table.position.y = 0.35; table.position.y = 0.35;
table.castShadow = true; table.castShadow = true;
table.receiveShadow = true; table.receiveShadow = true;
scene.add(table); tvGroup.add(table);
// --- 3. The TV Set --- // --- 2. The TV box ---
const cabinetGeometry = new THREE.BoxGeometry(2.8, 2, 1.5); const cabinetGeometry = new THREE.BoxGeometry(1.75, 1.5, 1.0);
const cabinet = new THREE.Mesh(cabinetGeometry, tvPlastic); const cabinet = new THREE.Mesh(cabinetGeometry, tvPlastic);
cabinet.position.y = 1.7; cabinet.position.y = 1.51;
cabinet.castShadow = true; cabinet.castShadow = true;
cabinet.receiveShadow = true; cabinet.receiveShadow = true;
scene.add(cabinet); tvGroup.add(cabinet);
// Screen Frame // --- 3. Screen Frame ---
const frameGeometry = new THREE.BoxGeometry(2.3, 1.6, 0.2); const frameGeometry = new THREE.BoxGeometry(1.5, 1.3, 0.2);
const frameMaterial = new THREE.MeshPhongMaterial({ color: 0x111111, shininess: 20 }); const frameMaterial = new THREE.MeshPhongMaterial({ color: 0x111111, shininess: 20 });
const frame = new THREE.Mesh(frameGeometry, frameMaterial); const frame = new THREE.Mesh(frameGeometry, frameMaterial);
frame.position.set(0, 1.7, 0.68); frame.position.set(0, 1.5, 0.68);
frame.castShadow = true; frame.castShadow = true;
frame.receiveShadow = true; frame.receiveShadow = true;
scene.add(frame); tvGroup.add(frame);
// --- 4. Curved Screen (CRT Effect) --- // --- 4. Curved Screen (CRT Effect) ---
const screenRadius = 3.0; // Radius for the subtle curve const screenRadius = 3.0; // Radius for the subtle curve
const screenWidth = 2.3; const screenWidth = 1.4;
const screenHeight = 1.5; const screenHeight = 1.2;
const thetaLength = screenWidth / screenRadius; // Calculate angle needed for the arc const thetaLength = screenWidth / screenRadius; // Calculate angle needed for the arc
// Use CylinderGeometry as a segment // Use CylinderGeometry as a segment
@ -321,14 +466,52 @@
tvScreen = new THREE.Mesh(screenGeometry, screenMaterial); tvScreen = new THREE.Mesh(screenGeometry, screenMaterial);
// Position the curved screen // Position the curved screen
tvScreen.position.set(0, 1.7, -2); tvScreen.position.set(0, 1.5, -2);
scene.add(tvScreen); tvGroup.add(tvScreen);
tvGroup.position.set(x, 0, z);
tvGroup.rotation.y = rotY;
// Light from the screen (initially low intensity, will increase when video loads)
screenLight = new THREE.PointLight(0xffffff, 0, 10);
screenLight.position.set(0, 1.5, 1.0);
// Screen light casts shadows
screenLight.castShadow = true;
screenLight.shadow.mapSize.width = 1024;
screenLight.shadow.mapSize.height = 1024;
screenLight.shadow.camera.near = 0.2;
screenLight.shadow.camera.far = 5;
tvGroup.add(screenLight);
scene.add(tvGroup);
}
// --- Scene Modeling Function ---
function createSceneObjects() {
// --- Materials (MeshPhongMaterial) ---
const darkWood = new THREE.MeshPhongMaterial({ color: 0x3d352e, shininess: 10 });
const darkMetal = new THREE.MeshPhongMaterial({
color: 0x6b6b6b,
shininess: 80,
specular: 0x888888
});
const tvPlastic = new THREE.MeshPhongMaterial({ color: 0x2d251e, shininess: 10 });
// --- 1. Floor ---
const floorGeometry = new THREE.PlaneGeometry(20, 20);
const floorMaterial = new THREE.MeshPhongMaterial({ color: 0x1a1a1a, shininess: 5 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.position.y = 0;
floor.receiveShadow = true;
scene.add(floor);
createTvSet(-roomSize/2 + 1.2, -roomSize/2 + 0.8, Math.PI * 0.1);
// --- 5. Lamp (On the table, right side) --- // --- 5. Lamp (On the table, right side) ---
const lampBase = new THREE.CylinderGeometry(0.1, 0.2, 0.1, 12); const lampBase = new THREE.CylinderGeometry(0.05, 0.2, 0.1, 12);
const lampPole = new THREE.CylinderGeometry(0.05, 0.05, 0.6, 8); const lampPole = new THREE.CylinderGeometry(0.02, 0.02, 1.5, 8);
const lampShade = new THREE.ConeGeometry(0.3, 0.4, 16); const lampShade = new THREE.ConeGeometry(0.2, 0.4, 16);
const baseMesh = new THREE.Mesh(lampBase, darkMetal); const baseMesh = new THREE.Mesh(lampBase, darkMetal);
const poleMesh = new THREE.Mesh(lampPole, darkMetal); const poleMesh = new THREE.Mesh(lampPole, darkMetal);
@ -337,55 +520,40 @@
// Ensure lamp parts cast shadows // Ensure lamp parts cast shadows
baseMesh.castShadow = true; baseMesh.receiveShadow = true; baseMesh.castShadow = true; baseMesh.receiveShadow = true;
poleMesh.castShadow = true; poleMesh.receiveShadow = true; poleMesh.castShadow = true; poleMesh.receiveShadow = true;
shadeMesh.castShadow = true; shadeMesh.receiveShadow = true; //shadeMesh.castShadow = true; shadeMesh.receiveShadow = true;
poleMesh.position.y = 0.3; baseMesh.position.y = -0.6;
poleMesh.position.y = 0.0;
shadeMesh.position.y = 0.8 + 0.1; shadeMesh.position.y = 0.8 + 0.1;
shadeMesh.rotation.x = Math.PI; shadeMesh.rotation.x = Math.PI;
const lampGroup = new THREE.Group();
lampGroup.add(baseMesh, poleMesh, shadeMesh);
lampGroup.position.set(2.5, 0.7, -0.6);
// Lamp Light (Warm Glow) - Configured to cast shadows // Lamp Light (Warm Glow) - Configured to cast shadows
lampLight = new THREE.PointLight(0xffaa00, originalLampIntensity, 4); lampLightPoint = new THREE.PointLight(0xffaa00, originalLampIntensity, 4);
lampLight.position.set(2.5, 1.35, -0.6); lampLightPoint.position.set(-0.01, roomHeight-0.9, 0.01);
lampLight.castShadow = true; lampLightPoint.castShadow = true;
// Optimization: Reduced map size and far plane to ease resource burden // Optimization: Reduced map size and far plane to ease resource burden
lampLight.shadow.mapSize.width = 512; lampLightPoint.shadow.mapSize.width = 512;
lampLight.shadow.mapSize.height = 512; lampLightPoint.shadow.mapSize.height = 512;
lampLight.shadow.camera.near = 0.1; lampLightPoint.shadow.camera.near = 0.1;
lampLight.shadow.camera.far = 4; // Matches the light's attenuation distance (4) lampLightPoint.shadow.camera.far = 4; // Matches the light's attenuation distance (4)
lampLightPoint.penumbra = 0.5;
lampLightSpot = new THREE.SpotLight(0xffaa00, originalLampIntensity, 4);
lampLightSpot.position.set(-0.01, 1.0, 0.01);
lampLightSpot.target.position.set(0, 5, 0);
lampLightSpot.castShadow = true;
// Optimization: Reduced map size and far plane to ease resource burden
lampLightSpot.shadow.mapSize.width = 512;
lampLightSpot.shadow.mapSize.height = 512;
lampLightSpot.shadow.camera.near = 0.1;
lampLightSpot.shadow.camera.far = 4; // Matches the light's attenuation distance (4)
lampLightSpot.penumbra = 0.5;
// Crucially, add the lamp group and its light to the scene const lampGroup = new THREE.Group();
scene.add(lampGroup, lampLight); lampGroup.add(baseMesh, poleMesh, shadeMesh, lampLightSpot, lampLightSpot.target, lampLightPoint);
lampGroup.position.set(0.8, 0.7, -roomSize/2+0.5);
scene.add(lampGroup);
// --- 6. Vase with a Flower (On the table, left side) ---
const vaseGeometry = new THREE.CylinderGeometry(0.2, 0.15, 0.4, 12);
const vaseMaterial = new THREE.MeshPhongMaterial({ color: 0x356644, shininess: 15 });
const vase = new THREE.Mesh(vaseGeometry, vaseMaterial);
vase.position.set(0, -0.2, 0);
vase.castShadow = true; vase.receiveShadow = true;
// Flower
const flowerStem = new THREE.CylinderGeometry(0.01, 0.01, 0.3, 8);
const flowerHead = new THREE.SphereGeometry(0.08, 10, 10);
const stemMaterial = new THREE.MeshPhongMaterial({ color: 0x228B22, shininess: 10 });
const headMaterial = new THREE.MeshPhongMaterial({ color: 0xdd2222, shininess: 30 });
const stem = new THREE.Mesh(flowerStem, stemMaterial);
stem.position.y = 0.1;
const head = new THREE.Mesh(flowerHead, headMaterial);
head.position.y = 0.3;
stem.castShadow = true; head.castShadow = true;
stem.receiveShadow = true; head.receiveShadow = true;
const flowerGroup = new THREE.Group();
flowerGroup.add(stem, head, vase);
flowerGroup.position.set(-1.65, 1.1, 1);
scene.add(flowerGroup);
// --- 7. Old Camera (On the table) --- // --- 7. Old Camera (On the table) ---
const cameraBody = new THREE.BoxGeometry(0.4, 0.3, 0.2); const cameraBody = new THREE.BoxGeometry(0.4, 0.3, 0.2);
@ -399,9 +567,10 @@
const cameraMesh = new THREE.Mesh(cameraBody, cameraMaterial); const cameraMesh = new THREE.Mesh(cameraBody, cameraMaterial);
const lensMesh = new THREE.Mesh(cameraLens, cameraMaterial); const lensMesh = new THREE.Mesh(cameraLens, cameraMaterial);
lensMesh.position.z = 0.15; lensMesh.position.z = 0.15;
lensMesh.rotation.x = Math.PI/2;
cameraMesh.add(lensMesh); cameraMesh.add(lensMesh);
cameraMesh.position.set(-2.0, 0.7 + 0.15, -0.4); cameraMesh.position.set(-1.7, 0.15, 0.4);
cameraMesh.rotation.y = -Math.PI / 10; cameraMesh.rotation.y = -Math.PI / 10;
cameraMesh.castShadow = true; cameraMesh.receiveShadow = true; cameraMesh.castShadow = true; cameraMesh.receiveShadow = true;
scene.add(cameraMesh); scene.add(cameraMesh);
@ -414,6 +583,10 @@
pizzaBox.rotation.y = Math.PI / 5; pizzaBox.rotation.y = Math.PI / 5;
pizzaBox.castShadow = true; pizzaBox.receiveShadow = true; pizzaBox.castShadow = true; pizzaBox.receiveShadow = true;
scene.add(pizzaBox); scene.add(pizzaBox);
createDoor(roomSize/2, -roomSize/2 * 0.5, -Math.PI/2);
createBookshelf(-roomSize/2 + 0.2, roomSize/2*0.7, Math.PI/2, 0);
createBookshelf(roomSize/2 * 0.7, -roomSize/2+0.3, 0, 1);
} }
// --- Dust Particle System Function --- // --- Dust Particle System Function ---
@ -478,7 +651,7 @@
function playVideoByIndex(index) { function playVideoByIndex(index) {
if (index < 0 || index >= videoUrls.length) { if (index < 0 || index >= videoUrls.length) {
statusText.textContent = 'End of playlist reached. Reload tapes to start again.'; statusText.textContent = 'End of playlist reached. Reload tapes to start again.';
screenLight.intensity = 0.1; // Keep minimum intensity for shadow map screenLight.intensity = 0.0;
return; return;
} }
@ -521,7 +694,7 @@
statusText.textContent = `Playing tape ${currentVideoIndex + 1} of ${videoUrls.length}.`; statusText.textContent = `Playing tape ${currentVideoIndex + 1} of ${videoUrls.length}.`;
updateControls(); updateControls();
}).catch(error => { }).catch(error => {
screenLight.intensity = 0.5; // Dim the light if playback fails screenLight.intensity = originalScreenIntensity * 0.5; // Dim the light if playback fails
statusText.textContent = `Playback blocked for tape ${currentVideoIndex + 1}. Click Next Tape to try again.`; statusText.textContent = `Playback blocked for tape ${currentVideoIndex + 1}. Click Next Tape to try again.`;
console.error('Playback Error: Could not start video playback.', error); console.error('Playback Error: Could not start video playback.', error);
}); });
@ -601,12 +774,12 @@
// Base Camera Position in front of the TV // Base Camera Position in front of the TV
const baseX = 0; const baseX = 0;
const baseY = 1.5; const baseY = 1.5;
const baseZ = 4; const baseZ = 3;
// Base LookAt target (Center of the screen) // Base LookAt target (Center of the screen)
const baseTargetX = 0; const baseTargetX = -1;
const baseTargetY = 1.7; const baseTargetY = 1.7;
const baseTargetZ = 0.96; const baseTargetZ = -0.96;
// Camera Position Offsets (Drift) // Camera Position Offsets (Drift)
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude; const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
@ -633,31 +806,35 @@
if (Math.random() > flickerChance) { if (Math.random() > flickerChance) {
// Flickers quickly to a dimmer random value (between 0.3 and 1.05) // Flickers quickly to a dimmer random value (between 0.3 and 1.05)
lampLight.intensity = originalLampIntensity * (0.3 + Math.random() * 0.7); let lampLightIntensity = originalLampIntensity * (0.3 + Math.random() * 0.7);
} else if (lampLight.intensity < originalLampIntensity) { lampLightSpot.intensity = lampLightIntensity;
lampLightPoint.intensity = lampLightIntensity;
} else if (lampLightPoint.intensity < originalLampIntensity) {
// Smoothly restore original intensity // Smoothly restore original intensity
lampLight.intensity = THREE.MathUtils.lerp(lampLight.intensity, originalLampIntensity, restoreRate); let lampLightIntensity = THREE.MathUtils.lerp(lampLightPoint.intensity, originalLampIntensity, restoreRate);
lampLightSpot.intensity = lampLightIntensity;
lampLightPoint.intensity = lampLightIntensity;
} }
// 4. Screen Light Pulse and Movement Effect (Updated) // 4. Screen Light Pulse and Movement Effect (Updated)
if (isVideoLoaded && screenLight.intensity > 0) { if (isVideoLoaded && screenLight.intensity > 0) {
// A. Pulse Effect (Intensity Fluctuation) // A. Pulse Effect (Intensity Fluctuation)
// Generate a small random fluctuation for the pulse (Range: 1.35 to 1.65 around base 1.5) // Generate a small random fluctuation for the pulse (Range: 1.35 to 1.65 around base 1.5)
const pulseTarget = originalScreenIntensity + (Math.random() - 0.5) * 0.7; const pulseTarget = originalScreenIntensity + (Math.random() - 0.5) * screenIntensityPulse;
// Smoothly interpolate towards the new target fluctuation // Smoothly interpolate towards the new target fluctuation
screenLight.intensity = THREE.MathUtils.lerp(screenLight.intensity, pulseTarget, 0.1); screenLight.intensity = THREE.MathUtils.lerp(screenLight.intensity, pulseTarget, 0.1);
// B. Movement Effect (Subtle circle around the screen center - circling the room area) // B. Movement Effect (Subtle circle around the screen center - circling the room area)
const lightTime = Date.now() * 0.002; const lightTime = Date.now() * 0.0001;
const radius = 0.05; // Small circle radius (5 cm) const radius = 0.01;
const centerX = 0; const centerX = 0;
const centerY = 1.7; const centerY = 1.5;
const centerZ = 1.2; // Use the updated Z position of the light source //const centerZ = 1.2; // Use the updated Z position of the light source
// Move the light in a subtle, erratic circle // Move the light in a subtle, erratic circle
screenLight.position.x = centerX + Math.cos(lightTime) * radius; screenLight.position.x = centerX + Math.cos(lightTime) * radius;
screenLight.position.y = centerY + Math.sin(lightTime * 1.5) * radius * 0.5; // Slightly different freq for Y screenLight.position.y = centerY + Math.sin(lightTime * 1.5) * radius * 0.5; // Slightly different freq for Y
screenLight.position.z = centerZ; // Keep Z constant at the screen light plane //screenLight.position.z = centerZ; // Keep Z constant at the screen light plane
} }
// 5. Update video texture (essential to grab the next frame) // 5. Update video texture (essential to grab the next frame)