Feature: Picture frame with changing images
This commit is contained in:
parent
a1992f899c
commit
192fc39461
@ -147,6 +147,12 @@ function updateBooks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updatePictureFrame() {
|
||||||
|
state.pictureFrames.forEach((pictureFrame) => {
|
||||||
|
pictureFrame.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// --- Animation Loop ---
|
// --- Animation Loop ---
|
||||||
export function animate() {
|
export function animate() {
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
@ -159,6 +165,7 @@ export function animate() {
|
|||||||
updateVcr();
|
updateVcr();
|
||||||
updateBooks();
|
updateBooks();
|
||||||
updateDoor();
|
updateDoor();
|
||||||
|
updatePictureFrame();
|
||||||
|
|
||||||
// RENDER!
|
// RENDER!
|
||||||
state.renderer.render(state.scene, state.camera);
|
state.renderer.render(state.scene, state.camera);
|
||||||
|
|||||||
115
tv-player/src/scene/PictureFrame.js
Normal file
115
tv-player/src/scene/PictureFrame.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
const FRAME_DEPTH = 0.05;
|
||||||
|
const TRANSITION_DURATION = 5000;
|
||||||
|
const IMAGE_CHANGE_CHANCE = 0.0001;
|
||||||
|
|
||||||
|
export class PictureFrame {
|
||||||
|
constructor(scene, { position, width, height, imageUrls, rotationY = 0 }) {
|
||||||
|
if (!imageUrls || imageUrls.length === 0) {
|
||||||
|
throw new Error('PictureFrame requires at least one image URL in the imageUrls array.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scene = scene;
|
||||||
|
this.mesh = this._createPictureFrame(width, height, imageUrls, 0.05);
|
||||||
|
|
||||||
|
this.mesh.position.copy(position);
|
||||||
|
this.mesh.rotation.y = rotationY;
|
||||||
|
|
||||||
|
this.isTransitioning = false;
|
||||||
|
this.transitionStartTime = 0;
|
||||||
|
|
||||||
|
this.scene.add(this.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
_createPictureFrame(width, height, imageUrls, frameThickness) {
|
||||||
|
const paintingGroup = new THREE.Group();
|
||||||
|
|
||||||
|
// 1. Create the wooden frame
|
||||||
|
const frameMaterial = new THREE.MeshPhongMaterial({ color: 0x8B4513 }); // SaddleBrown
|
||||||
|
|
||||||
|
const topFrame = new THREE.Mesh(new THREE.BoxGeometry(width + 2 * frameThickness, frameThickness, FRAME_DEPTH), frameMaterial);
|
||||||
|
topFrame.position.y = height / 2 + frameThickness / 2;
|
||||||
|
topFrame.castShadow = true;
|
||||||
|
topFrame.receiveShadow = true;
|
||||||
|
paintingGroup.add(topFrame);
|
||||||
|
|
||||||
|
const bottomFrame = new THREE.Mesh(new THREE.BoxGeometry(width + 2 * frameThickness, frameThickness, FRAME_DEPTH), frameMaterial);
|
||||||
|
bottomFrame.position.y = -height / 2 - frameThickness / 2;
|
||||||
|
bottomFrame.castShadow = true;
|
||||||
|
bottomFrame.receiveShadow = true;
|
||||||
|
paintingGroup.add(bottomFrame);
|
||||||
|
|
||||||
|
const leftFrame = new THREE.Mesh(new THREE.BoxGeometry(frameThickness, height, FRAME_DEPTH), frameMaterial);
|
||||||
|
leftFrame.position.x = -width / 2 - frameThickness / 2;
|
||||||
|
leftFrame.castShadow = true;
|
||||||
|
leftFrame.receiveShadow = true;
|
||||||
|
paintingGroup.add(leftFrame);
|
||||||
|
|
||||||
|
const rightFrame = new THREE.Mesh(new THREE.BoxGeometry(frameThickness, height, FRAME_DEPTH), frameMaterial);
|
||||||
|
rightFrame.position.x = width / 2 + frameThickness / 2;
|
||||||
|
rightFrame.castShadow = true;
|
||||||
|
rightFrame.receiveShadow = true;
|
||||||
|
paintingGroup.add(rightFrame);
|
||||||
|
|
||||||
|
// 2. Create the picture canvases with textures
|
||||||
|
const textureLoader = new THREE.TextureLoader();
|
||||||
|
this.textures = imageUrls.map(url => textureLoader.load(url));
|
||||||
|
this.currentTextureIndex = 0;
|
||||||
|
|
||||||
|
const pictureGeometry = new THREE.PlaneGeometry(width, height);
|
||||||
|
|
||||||
|
// Create two picture planes for cross-fading
|
||||||
|
this.pictureBack = new THREE.Mesh(pictureGeometry, new THREE.MeshPhongMaterial({ map: this.textures[this.currentTextureIndex] }));
|
||||||
|
this.pictureBack.receiveShadow = true;
|
||||||
|
paintingGroup.add(this.pictureBack);
|
||||||
|
|
||||||
|
this.pictureFront = new THREE.Mesh(pictureGeometry, new THREE.MeshPhongMaterial({ map: this.textures[this.currentTextureIndex], transparent: true, opacity: 0 }));
|
||||||
|
this.pictureFront.position.z = 0.001; // Place slightly in front to avoid z-fighting
|
||||||
|
this.pictureFront.receiveShadow = true;
|
||||||
|
paintingGroup.add(this.pictureFront);
|
||||||
|
|
||||||
|
return paintingGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPicture(index) {
|
||||||
|
if (this.isTransitioning || index === this.currentTextureIndex || index < 0 || index >= this.textures.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isTransitioning = true;
|
||||||
|
this.transitionStartTime = Date.now();
|
||||||
|
|
||||||
|
// Front plane fades in with the new texture
|
||||||
|
this.pictureFront.material.map = this.textures[index];
|
||||||
|
this.pictureFront.material.opacity = 0;
|
||||||
|
|
||||||
|
this.nextTextureIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPicture() {
|
||||||
|
this.setPicture((this.currentTextureIndex + 1) % this.textures.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
if (!this.isTransitioning) {
|
||||||
|
if (Math.random() > 1.0 - IMAGE_CHANGE_CHANCE) {
|
||||||
|
this.nextPicture();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elapsedTime = Date.now() - this.transitionStartTime;
|
||||||
|
const progress = Math.min(elapsedTime / TRANSITION_DURATION, 1.0);
|
||||||
|
this.pictureFront.material.opacity = progress;
|
||||||
|
|
||||||
|
if (progress >= 1.0) {
|
||||||
|
this.isTransitioning = false;
|
||||||
|
this.currentTextureIndex = this.nextTextureIndex;
|
||||||
|
|
||||||
|
// Reset for next transition
|
||||||
|
this.pictureBack.material.map = this.textures[this.currentTextureIndex];
|
||||||
|
this.pictureFront.material.opacity = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import { createRoomWalls } from './room-walls.js';
|
|||||||
import { createBookshelf } from './bookshelf.js';
|
import { createBookshelf } from './bookshelf.js';
|
||||||
import { createDoor } from './door.js';
|
import { createDoor } from './door.js';
|
||||||
import { createTvSet } from './tv-set.js';
|
import { createTvSet } from './tv-set.js';
|
||||||
|
import { PictureFrame } from './PictureFrame.js';
|
||||||
|
|
||||||
// --- Scene Modeling Function ---
|
// --- Scene Modeling Function ---
|
||||||
export function createSceneObjects() {
|
export function createSceneObjects() {
|
||||||
@ -134,4 +135,21 @@ export function createSceneObjects() {
|
|||||||
createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.7, 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);
|
createBookshelf(state.roomSize/2 * 0.7, -state.roomSize/2+0.3, 0, 1);
|
||||||
|
|
||||||
|
const pictureFrame = new PictureFrame(state.scene, {
|
||||||
|
position: new THREE.Vector3(-state.roomSize/2 + 0.1, 2.0, -state.roomSize/2 + 1.5),
|
||||||
|
width: 1.5,
|
||||||
|
height: 1,
|
||||||
|
imageUrls: ['/textures/painting1.jpg', '/textures/painting2.jpg'],
|
||||||
|
rotationY: Math.PI / 2
|
||||||
|
});
|
||||||
|
state.pictureFrames.push(pictureFrame);
|
||||||
|
|
||||||
|
const pictureFrame2 = new PictureFrame(state.scene, {
|
||||||
|
position: new THREE.Vector3(state.roomSize/2 - 0.1, 2.0, 0.5),
|
||||||
|
width: 1.5,
|
||||||
|
height: 1,
|
||||||
|
imageUrls: ['/textures/painting2.jpg', '/textures/painting1.jpg'],
|
||||||
|
rotationY: -Math.PI / 2
|
||||||
|
});
|
||||||
|
state.pictureFrames.push(pictureFrame2);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,9 @@ export function initState() {
|
|||||||
timer: 0,
|
timer: 0,
|
||||||
},
|
},
|
||||||
books: [], // Array to hold all individual book meshes for animation
|
books: [], // Array to hold all individual book meshes for animation
|
||||||
|
pictureFrames: [],
|
||||||
raycaster: new THREE.Raycaster(),
|
raycaster: new THREE.Raycaster(),
|
||||||
seed: 12345,
|
seed: 12345,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
tv-player/textures/painting1.jpg
Normal file
BIN
tv-player/textures/painting1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
tv-player/textures/painting2.jpg
Normal file
BIN
tv-player/textures/painting2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
Loading…
Reference in New Issue
Block a user