117 lines
4.4 KiB
JavaScript
117 lines
4.4 KiB
JavaScript
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.position.z = 0.001;
|
|
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.003; // 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;
|
|
}
|
|
}
|
|
}
|