Feature: books start levitating

This commit is contained in:
Dejvino 2025-11-16 16:32:13 +01:00
parent 6e2a59ce77
commit 2e1e6bddfa
3 changed files with 73 additions and 0 deletions

View File

@ -88,6 +88,65 @@ function updateVcr() {
}
}
function updateBooks() {
const LEVITATE_CHANCE = 0.001; // Chance for a resting book to start levitating per frame
const LEVITATE_DURATION_MIN = 100; // frames
const LEVITATE_DURATION_MAX = 300; // frames
const LEVITATE_AMPLITUDE = 0.02; // Max vertical displacement
const LEVITATE_SPEED_FACTOR = 0.03; // Speed of oscillation
const START_RATE = 0.05; // How quickly a book starts to levitate
const RETURN_RATE = 0.1; // How quickly a book returns to original position
const START_DURATION = 120; // frames for the starting transition
const levitation = state.bookLevitation;
// Manage the global levitation state
if (levitation.state === 'resting') {
if (Math.random() < LEVITATE_CHANCE) {
levitation.state = 'starting';
levitation.timer = START_DURATION;
}
} else if (levitation.state === 'starting') {
levitation.timer--;
if (levitation.timer <= 0) {
levitation.state = 'levitating';
levitation.timer = LEVITATE_DURATION_MIN + Math.random() * (LEVITATE_DURATION_MAX - LEVITATE_DURATION_MIN);
}
} else if (levitation.state === 'levitating') {
levitation.timer--;
if (levitation.timer <= 0) {
levitation.state = 'returning';
}
}
// Animate books based on the global state
let allBooksReturned = true;
state.books.forEach(book => {
const data = book.userData;
if (levitation.state === 'starting') {
allBooksReturned = false;
book.position.y = THREE.MathUtils.lerp(book.position.y, data.originalY + LEVITATE_AMPLITUDE/2, START_RATE);
data.oscillationTime = 0;
} else if (levitation.state === 'levitating') {
allBooksReturned = false;
data.oscillationTime += LEVITATE_SPEED_FACTOR;
data.levitateOffset = Math.sin(data.oscillationTime) * LEVITATE_AMPLITUDE;
book.position.y = data.originalY + data.levitateOffset + LEVITATE_AMPLITUDE/2;
} else if (levitation.state === 'returning') {
book.position.y = THREE.MathUtils.lerp(book.position.y, data.originalY, RETURN_RATE);
data.levitateOffset = book.position.y - data.originalY;
if (Math.abs(data.levitateOffset) > 0.001) {
allBooksReturned = false;
}
}
});
if (levitation.state === 'returning' && allBooksReturned) {
levitation.state = 'resting';
}
}
// --- Animation Loop ---
export function animate() {
requestAnimationFrame(animate);
@ -98,6 +157,7 @@ export function animate() {
updateScreenLight();
updateVideo();
updateVcr();
updateBooks();
updateDoor();
// RENDER!

View File

@ -102,8 +102,16 @@ export function createBookshelf(x, z, rotationY, uniqueSeed) {
book.castShadow = true;
book.receiveShadow = true;
// Store original Y position and animation data
book.userData.originalY = book.position.y;
book.userData.levitateOffset = 0;
book.userData.oscillationTime = Math.random() * Math.PI * 2; // Start at random phase
shelfGroup.add(book);
if (Math.random() > 0.8) {
state.books.push(book);
}
currentBookX += bookWidth + 0.002; // Tiny gap between books
if (seededRandom() > 0.92) {

View File

@ -44,6 +44,11 @@ export function initState() {
// Utilities
loader: new THREE.TextureLoader(),
landingSurfaces: [],
bookLevitation: {
state: 'resting', // 'resting', 'levitating', 'returning'
timer: 0,
},
books: [], // Array to hold all individual book meshes for animation
raycaster: new THREE.Raycaster(),
seed: 12345,
};