Compare commits
No commits in common. "bb923968e54cec16a2a4cf033a5669bd7ab86c7d" and "ae3383986aa20ab3ec85d57c79e61e01eb27eee3" have entirely different histories.
bb923968e5
...
ae3383986a
@ -5,19 +5,18 @@ import { updateScreenEffect } from '../scene/magic-mirror.js'
|
|||||||
import { updateCauldron } from '../scene/cauldron.js';
|
import { updateCauldron } from '../scene/cauldron.js';
|
||||||
import { updateFire } from '../scene/fireplace.js';
|
import { updateFire } from '../scene/fireplace.js';
|
||||||
import { updateRats } from '../scene/rat.js';
|
import { updateRats } from '../scene/rat.js';
|
||||||
import { updateLectern } from '../scene/lectern.js';
|
|
||||||
|
|
||||||
function updateCamera() {
|
function updateCamera() {
|
||||||
const globalTime = Date.now() * 0.00003;
|
const globalTime = Date.now() * 0.00003;
|
||||||
const lookAtTime = Date.now() * 0.00005;
|
const lookAtTime = Date.now() * 0.00005;
|
||||||
|
|
||||||
const camAmplitude = 0.9;
|
const camAmplitude = 0.2;
|
||||||
const lookAmplitude = 0.4;
|
const lookAmplitude = 0.4;
|
||||||
|
|
||||||
// Base Camera Position in front of the TV
|
// Base Camera Position in front of the TV
|
||||||
const baseX = -0.5;
|
const baseX = -0.5;
|
||||||
const baseY = 1.5;
|
const baseY = 1.5;
|
||||||
const baseZ = 1.9;
|
const baseZ = 2.2;
|
||||||
|
|
||||||
// Base LookAt target (Center of the screen)
|
// Base LookAt target (Center of the screen)
|
||||||
const baseTargetX = -0.2;
|
const baseTargetX = -0.2;
|
||||||
@ -27,7 +26,7 @@ function updateCamera() {
|
|||||||
// Camera Position Offsets (Drift)
|
// Camera Position Offsets (Drift)
|
||||||
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
|
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
|
||||||
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.4;
|
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.4;
|
||||||
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude * 1.2;
|
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude * 1.4;
|
||||||
|
|
||||||
state.camera.position.x = baseX + camOffsetX;
|
state.camera.position.x = baseX + camOffsetX;
|
||||||
state.camera.position.y = baseY + camOffsetY;
|
state.camera.position.y = baseY + camOffsetY;
|
||||||
@ -161,7 +160,6 @@ export function animate() {
|
|||||||
updateScreenEffect();
|
updateScreenEffect();
|
||||||
updateFire();
|
updateFire();
|
||||||
updateCauldron();
|
updateCauldron();
|
||||||
updateLectern();
|
|
||||||
updateRats();
|
updateRats();
|
||||||
|
|
||||||
// RENDER!
|
// RENDER!
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export function createFireplace(x, z, rotY) {
|
|||||||
|
|
||||||
// 2. Fireplace Body
|
// 2. Fireplace Body
|
||||||
const bodyWidth = 2.2;
|
const bodyWidth = 2.2;
|
||||||
const bodyHeight = 2.2;
|
const bodyHeight = 2.0;
|
||||||
const bodyDepth = 0.8;
|
const bodyDepth = 0.8;
|
||||||
const bodyGeo = new THREE.BoxGeometry(bodyWidth, bodyHeight, bodyDepth);
|
const bodyGeo = new THREE.BoxGeometry(bodyWidth, bodyHeight, bodyDepth);
|
||||||
const body = new THREE.Mesh(bodyGeo, stoneMaterial);
|
const body = new THREE.Mesh(bodyGeo, stoneMaterial);
|
||||||
@ -45,15 +45,6 @@ export function createFireplace(x, z, rotY) {
|
|||||||
body.receiveShadow = true;
|
body.receiveShadow = true;
|
||||||
fireplaceGroup.add(body);
|
fireplaceGroup.add(body);
|
||||||
|
|
||||||
// Chimney
|
|
||||||
const chimneyGeo = new THREE.BoxGeometry(bodyWidth*0.6, state.roomHeight, bodyDepth*0.6);
|
|
||||||
const chimney = new THREE.Mesh(chimneyGeo, stoneMaterial);
|
|
||||||
chimney.position.z = -bodyDepth*0.2;
|
|
||||||
chimney.position.y = bodyHeight;
|
|
||||||
chimney.castShadow = true;
|
|
||||||
chimney.receiveShadow = true;
|
|
||||||
fireplaceGroup.add(chimney);
|
|
||||||
|
|
||||||
// 3. Fireplace Opening (carved out look)
|
// 3. Fireplace Opening (carved out look)
|
||||||
const openingWidth = 1.2;
|
const openingWidth = 1.2;
|
||||||
const openingHeight = 1.0;
|
const openingHeight = 1.0;
|
||||||
|
|||||||
@ -1,213 +0,0 @@
|
|||||||
import * as THREE from 'three';
|
|
||||||
import { state } from '../state.js';
|
|
||||||
import woodTextureUrl from '/textures/wood.png';
|
|
||||||
import spellbookSpritesheetUrl from '/textures/pages_sheet.png';
|
|
||||||
|
|
||||||
let lecternState = {
|
|
||||||
isFlipping: false,
|
|
||||||
flipProgress: 0,
|
|
||||||
flipSpeed: 0.015,
|
|
||||||
leftPageIndex: 0,
|
|
||||||
rightPageIndex: 1,
|
|
||||||
totalPages: 4, // 2x2 spritesheet
|
|
||||||
pageFlipChance: 0.01,
|
|
||||||
flippingPage: null,
|
|
||||||
flippingPageMaterials: null,
|
|
||||||
leftPage: null,
|
|
||||||
rightPage: null,
|
|
||||||
spritesheetTexture: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const bookWidth = 0.7;
|
|
||||||
const bookHeight = 0.45;
|
|
||||||
|
|
||||||
function setPageTexture(material, pageIndex) {
|
|
||||||
const texture = material.map;
|
|
||||||
if (!texture) return;
|
|
||||||
|
|
||||||
texture.wrapS = THREE.ClampToEdgeWrapping;
|
|
||||||
texture.wrapT = THREE.ClampToEdgeWrapping;
|
|
||||||
texture.repeat.set(0.5, 0.5);
|
|
||||||
|
|
||||||
const col = pageIndex % 2;
|
|
||||||
const row = 1 - Math.floor(pageIndex / 2); // Invert row for THREE.js UVs (bottom-left origin)
|
|
||||||
|
|
||||||
texture.offset.set(col * 0.5, row * 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createLectern(x, y, z, rotY) {
|
|
||||||
const lecternGroup = new THREE.Group();
|
|
||||||
|
|
||||||
// --- Materials ---
|
|
||||||
const woodMaterial = new THREE.MeshPhongMaterial({
|
|
||||||
map: state.loader.load(woodTextureUrl),
|
|
||||||
color: 0x6b4f3a,
|
|
||||||
shininess: 20
|
|
||||||
});
|
|
||||||
|
|
||||||
// 1. Lectern Stand
|
|
||||||
const baseGeo = new THREE.BoxGeometry(0.6, 0.05, 0.6);
|
|
||||||
const base = new THREE.Mesh(baseGeo, woodMaterial);
|
|
||||||
base.castShadow = true;
|
|
||||||
lecternGroup.add(base);
|
|
||||||
|
|
||||||
const poleGeo = new THREE.CylinderGeometry(0.08, 0.1, 1.0, 12);
|
|
||||||
const pole = new THREE.Mesh(poleGeo, woodMaterial);
|
|
||||||
pole.position.y = 0.5;
|
|
||||||
pole.castShadow = true;
|
|
||||||
lecternGroup.add(pole);
|
|
||||||
|
|
||||||
const topGeo = new THREE.BoxGeometry(0.8, 0.05, 0.5);
|
|
||||||
const top = new THREE.Mesh(topGeo, woodMaterial);
|
|
||||||
top.position.y = 1.1;
|
|
||||||
top.rotation.x = Math.PI/2 + -Math.PI * 0.3; // Slanted top
|
|
||||||
top.castShadow = true;
|
|
||||||
lecternGroup.add(top);
|
|
||||||
|
|
||||||
// 2. Spellbook
|
|
||||||
const bookGroup = new THREE.Group();
|
|
||||||
bookGroup.position.y = 1.2;
|
|
||||||
bookGroup.position.z = 0.01;
|
|
||||||
bookGroup.rotation.x = -Math.PI * 0.3;
|
|
||||||
|
|
||||||
const pageGeo = new THREE.PlaneGeometry(bookWidth / 2, bookHeight);
|
|
||||||
|
|
||||||
// 2.6. Stack of Pages
|
|
||||||
const pageStackHeight = 0.08;
|
|
||||||
const pageStackGeo = new THREE.BoxGeometry(bookWidth, bookHeight, pageStackHeight);
|
|
||||||
const pageStackMaterial = new THREE.MeshPhongMaterial({ color: 0xffeedd, shininess: 5 });
|
|
||||||
const pageStack = new THREE.Mesh(pageStackGeo, pageStackMaterial);
|
|
||||||
pageStack.position.x = 0.01;
|
|
||||||
pageStack.position.z = -pageStackHeight/2; // Position it just behind the pages
|
|
||||||
pageStack.position.y = 0.01; // Position it a bit lower
|
|
||||||
pageStack.castShadow = true;
|
|
||||||
bookGroup.add(pageStack);
|
|
||||||
|
|
||||||
|
|
||||||
// 2.5. Book Cover
|
|
||||||
const coverMaterial = new THREE.MeshPhongMaterial({ color: 0x8B0000, shininess: 15 }); // DarkRed
|
|
||||||
const coverWidth = bookWidth + 0.04;
|
|
||||||
const coverHeight = bookHeight + 0.04;
|
|
||||||
const coverDepth = 0.01;
|
|
||||||
const coverGeo = new THREE.BoxGeometry(coverWidth, coverHeight, coverDepth);
|
|
||||||
const bookCover = new THREE.Mesh(coverGeo, coverMaterial);
|
|
||||||
// Position it just behind the pages
|
|
||||||
bookCover.position.x = 0.01;
|
|
||||||
bookCover.position.z = -pageStackHeight/2 -coverDepth / 2 - 0.01;
|
|
||||||
bookCover.castShadow = true;
|
|
||||||
bookGroup.add(bookCover);
|
|
||||||
|
|
||||||
// Load single spritesheet texture
|
|
||||||
lecternState.spritesheetTexture = state.loader.load(spellbookSpritesheetUrl);
|
|
||||||
|
|
||||||
// Create page materials
|
|
||||||
// We clone the material so each page can have an independent texture offset
|
|
||||||
const leftPageMat = new THREE.MeshPhongMaterial({ map: lecternState.spritesheetTexture.clone() });
|
|
||||||
const rightPageMat = new THREE.MeshPhongMaterial({ map: lecternState.spritesheetTexture.clone() });
|
|
||||||
|
|
||||||
// Left and Right static pages
|
|
||||||
setPageTexture(leftPageMat, lecternState.leftPageIndex);
|
|
||||||
setPageTexture(rightPageMat, lecternState.rightPageIndex);
|
|
||||||
|
|
||||||
lecternState.leftPage = new THREE.Mesh(pageGeo, leftPageMat);
|
|
||||||
lecternState.leftPage.position.x = -bookWidth / 4;
|
|
||||||
lecternState.leftPage.position.z = 0.01;
|
|
||||||
lecternState.leftPage.visible = true;
|
|
||||||
lecternState.leftPage.receiveShadow = true;
|
|
||||||
bookGroup.add(lecternState.leftPage);
|
|
||||||
|
|
||||||
lecternState.rightPage = new THREE.Mesh(pageGeo, rightPageMat);
|
|
||||||
lecternState.rightPage.position.x = bookWidth / 4;
|
|
||||||
lecternState.rightPage.position.z = 0.01;
|
|
||||||
lecternState.rightPage.visible = true;
|
|
||||||
lecternState.rightPage.receiveShadow = true;
|
|
||||||
bookGroup.add(lecternState.rightPage);
|
|
||||||
|
|
||||||
|
|
||||||
// The page that will animate
|
|
||||||
const flippingPageMat = new THREE.MeshPhongMaterial({
|
|
||||||
map: lecternState.spritesheetTexture.clone(),
|
|
||||||
});
|
|
||||||
const flippingPageBackMat = new THREE.MeshPhongMaterial({
|
|
||||||
map: lecternState.spritesheetTexture.clone(),
|
|
||||||
});
|
|
||||||
// Assign a name to identify the back material in setPageTexture
|
|
||||||
flippingPageBackMat.name = 'back';
|
|
||||||
lecternState.flippingPageMaterials = [flippingPageMat, flippingPageBackMat];
|
|
||||||
|
|
||||||
// Create a group for the flipping page
|
|
||||||
const flippingPageGroup = new THREE.Group();
|
|
||||||
|
|
||||||
const frontPage = new THREE.Mesh(pageGeo, flippingPageMat);
|
|
||||||
const backPage = new THREE.Mesh(pageGeo, flippingPageBackMat);
|
|
||||||
backPage.rotation.y = Math.PI; // Rotate the back page to face the other way
|
|
||||||
|
|
||||||
flippingPageGroup.add(frontPage);
|
|
||||||
flippingPageGroup.add(backPage);
|
|
||||||
|
|
||||||
lecternState.flippingPage = flippingPageGroup; // Assign the group to the state
|
|
||||||
lecternState.flippingPage.position.x = bookWidth / 4;
|
|
||||||
lecternState.flippingPage.visible = false;
|
|
||||||
lecternState.flippingPage.castShadow = true;
|
|
||||||
bookGroup.add(lecternState.flippingPage);
|
|
||||||
|
|
||||||
lecternGroup.add(bookGroup);
|
|
||||||
|
|
||||||
// Position and add to scene
|
|
||||||
lecternGroup.position.set(x, y, z);
|
|
||||||
lecternGroup.rotation.y = rotY;
|
|
||||||
state.scene.add(lecternGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateLectern() {
|
|
||||||
const { isFlipping, pageFlipChance, flippingPage, flippingPageMaterials, leftPage, rightPage } = lecternState;
|
|
||||||
|
|
||||||
// Randomly decide to start flipping a page
|
|
||||||
if (!isFlipping && Math.random() < pageFlipChance) {
|
|
||||||
lecternState.isFlipping = true;
|
|
||||||
lecternState.flipProgress = 0;
|
|
||||||
|
|
||||||
const nextPageIndex = (lecternState.rightPageIndex + 1) % lecternState.totalPages;
|
|
||||||
const nextNextPageIndex = (lecternState.rightPageIndex + 2) % lecternState.totalPages;
|
|
||||||
|
|
||||||
// Set the front of the flipping page to the current right page's content
|
|
||||||
setPageTexture(flippingPageMaterials[0], lecternState.rightPageIndex);
|
|
||||||
// Set the back of the flipping page to what will be the new right page's content
|
|
||||||
setPageTexture(flippingPageMaterials[1], nextPageIndex);
|
|
||||||
flippingPageMaterials[0].map.needsUpdate = true;
|
|
||||||
flippingPageMaterials[1].map.needsUpdate = true;
|
|
||||||
flippingPage.visible = true;
|
|
||||||
flippingPage.position.x = rightPage.position.x;
|
|
||||||
flippingPage.rotation.y = 0;
|
|
||||||
|
|
||||||
// Immediately update the static right page to show the *next* page, and keep it visible.
|
|
||||||
setPageTexture(rightPage.material, nextNextPageIndex);
|
|
||||||
rightPage.material.needsUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFlipping) {
|
|
||||||
lecternState.flipProgress += lecternState.flipSpeed;
|
|
||||||
const progress = lecternState.flipProgress;
|
|
||||||
|
|
||||||
// Animate the page turning from right to left (0 to PI)
|
|
||||||
flippingPage.rotation.y = -progress * Math.PI;
|
|
||||||
// Move it in an arc
|
|
||||||
flippingPage.position.x = (rightPage.position.x) * Math.cos(progress * Math.PI);
|
|
||||||
flippingPage.position.z = Math.sin(progress * Math.PI) * bookWidth/4 + 0.015;
|
|
||||||
|
|
||||||
// When flip is complete
|
|
||||||
if (progress >= 1.0) {
|
|
||||||
// The left page now shows the content of the page that just landed.
|
|
||||||
lecternState.leftPageIndex = (lecternState.rightPageIndex + 1) % lecternState.totalPages;
|
|
||||||
// The right page index officially becomes the next page's index.
|
|
||||||
lecternState.rightPageIndex = (lecternState.rightPageIndex + 2) % lecternState.totalPages;
|
|
||||||
|
|
||||||
// Update the left page's texture to finalize the flip.
|
|
||||||
setPageTexture(leftPage.material, lecternState.leftPageIndex);
|
|
||||||
|
|
||||||
// Reset state
|
|
||||||
flippingPage.visible = false;
|
|
||||||
lecternState.isFlipping = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,11 +7,9 @@ import { createFireplace } from './fireplace.js';
|
|||||||
import { createTable } from './table.js';
|
import { createTable } from './table.js';
|
||||||
import { createCauldron } from './cauldron.js';
|
import { createCauldron } from './cauldron.js';
|
||||||
import { createRats } from './rat.js';
|
import { createRats } from './rat.js';
|
||||||
import { createLectern } from './lectern.js';
|
|
||||||
import { PictureFrame } from './PictureFrame.js';
|
import { PictureFrame } from './PictureFrame.js';
|
||||||
import painting1 from '/textures/painting1.jpg';
|
import painting1 from '/textures/painting1.jpg';
|
||||||
import painting2 from '/textures/painting2.jpg';
|
import painting2 from '/textures/painting2.jpg';
|
||||||
import painting3 from '/textures/painting3.jpg';
|
|
||||||
import floorTextureUrl from '/textures/stone_floor.png';
|
import floorTextureUrl from '/textures/stone_floor.png';
|
||||||
import tableTextureUrl from '/textures/wood.png';
|
import tableTextureUrl from '/textures/wood.png';
|
||||||
|
|
||||||
@ -143,19 +141,15 @@ export function createSceneObjects() {
|
|||||||
// --- 9. Fireplace ---
|
// --- 9. Fireplace ---
|
||||||
createFireplace(state.roomSize / 2 - 0.5, -1, -Math.PI / 2);
|
createFireplace(state.roomSize / 2 - 0.5, -1, -Math.PI / 2);
|
||||||
|
|
||||||
// --- Lectern ---
|
|
||||||
createLectern(-state.roomSize/2 + 0.4, 0, -0.1, Math.PI / 2.3);
|
|
||||||
|
|
||||||
createRats(state.roomSize/2 - 0.01, 0, 0.37, -Math.PI / 2);
|
createRats(state.roomSize/2 - 0.01, 0, 0.37, -Math.PI / 2);
|
||||||
|
|
||||||
//createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.1, Math.PI/2, 0);
|
createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.1, Math.PI/2, 0);
|
||||||
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);
|
||||||
|
|
||||||
// next to bookshelf
|
|
||||||
const pictureFrame = new PictureFrame(state.scene, {
|
const pictureFrame = new PictureFrame(state.scene, {
|
||||||
position: new THREE.Vector3(-state.roomSize/2, 1.7, -state.roomSize/2 + 0.8),
|
position: new THREE.Vector3(-state.roomSize/2, 1.7, -state.roomSize/2 + 0.7),
|
||||||
width: 0.75,
|
width: 0.7,
|
||||||
height: 1,
|
height: 1,
|
||||||
imageUrls: [painting1, painting2],
|
imageUrls: [painting1, painting2],
|
||||||
rotationY: Math.PI / 2
|
rotationY: Math.PI / 2
|
||||||
@ -163,22 +157,20 @@ export function createSceneObjects() {
|
|||||||
|
|
||||||
state.pictureFrames.push(pictureFrame);
|
state.pictureFrames.push(pictureFrame);
|
||||||
|
|
||||||
// on fireplace
|
|
||||||
const pictureFrame2 = new PictureFrame(state.scene, {
|
const pictureFrame2 = new PictureFrame(state.scene, {
|
||||||
position: new THREE.Vector3(state.roomSize/2 - 0.9, 1.8, -1.1),
|
position: new THREE.Vector3(state.roomSize/2 - 0.9, 1.7, -1.1),
|
||||||
width: 0.5,
|
width: 0.8,
|
||||||
height: 0.7,
|
height: 0.5,
|
||||||
imageUrls: [painting2, painting1, painting3],
|
imageUrls: [painting2, painting1],
|
||||||
rotationY: -Math.PI / 2
|
rotationY: -Math.PI / 2
|
||||||
});
|
});
|
||||||
state.pictureFrames.push(pictureFrame2);
|
state.pictureFrames.push(pictureFrame2);
|
||||||
|
|
||||||
// next to fireplace
|
|
||||||
const pictureFrame3 = new PictureFrame(state.scene, {
|
const pictureFrame3 = new PictureFrame(state.scene, {
|
||||||
position: new THREE.Vector3(state.roomSize/2, 1.7, 0.75),
|
position: new THREE.Vector3(state.roomSize/2, 1.7, 0.75),
|
||||||
width: 0.7,
|
width: 0.7,
|
||||||
height: 1.2,
|
height: 1.2,
|
||||||
imageUrls: [painting3, painting2, painting1, painting3],
|
imageUrls: [painting2, painting1],
|
||||||
rotationY: -Math.PI / 2
|
rotationY: -Math.PI / 2
|
||||||
});
|
});
|
||||||
state.pictureFrames.push(pictureFrame3);
|
state.pictureFrames.push(pictureFrame3);
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 488 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 76 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 56 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 362 KiB |
Loading…
Reference in New Issue
Block a user