Compare commits
No commits in common. "192fc394616913318410b50470e6b4b87abfabaa" and "2e1e6bddfad06ba78cf3258d6006af6e6d296586" have entirely different histories.
192fc39461
...
2e1e6bddfa
@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
nix-shell -p nodejs --run "npx vite"
|
nix-shell -p python3 --run "python3 -m http.server -b 127.0.0.1 8000"
|
||||||
@ -147,12 +147,6 @@ function updateBooks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePictureFrame() {
|
|
||||||
state.pictureFrames.forEach((pictureFrame) => {
|
|
||||||
pictureFrame.update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Animation Loop ---
|
// --- Animation Loop ---
|
||||||
export function animate() {
|
export function animate() {
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
@ -165,7 +159,6 @@ 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);
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { DustEffect } from './dust.js';
|
import { DustEffect } from './dust.js';
|
||||||
import { FliesEffect } from './flies.js';
|
import { FliesEffect } from './flies.js';
|
||||||
import { SpiderEffect } from './spider.js';
|
|
||||||
|
|
||||||
export class EffectsManager {
|
export class EffectsManager {
|
||||||
constructor(scene) {
|
constructor(scene) {
|
||||||
@ -13,7 +12,6 @@ export class EffectsManager {
|
|||||||
// This is now the single place to manage which effects are active.
|
// This is now the single place to manage which effects are active.
|
||||||
this.addEffect(new DustEffect(scene));
|
this.addEffect(new DustEffect(scene));
|
||||||
this.addEffect(new FliesEffect(scene));
|
this.addEffect(new FliesEffect(scene));
|
||||||
this.addEffect(new SpiderEffect(scene));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addEffect(effect) {
|
addEffect(effect) {
|
||||||
|
|||||||
@ -1,131 +0,0 @@
|
|||||||
import * as THREE from 'three';
|
|
||||||
import { state } from '../state.js';
|
|
||||||
|
|
||||||
const SPIDER_COUNT = 5;
|
|
||||||
const SPIDER_SPEED = 0.0001;
|
|
||||||
const SPIDER_TURN_SPEED = 0.02;
|
|
||||||
const SPIDER_WAIT_MIN = 200; // frames
|
|
||||||
const SPIDER_WAIT_MAX = 500; // frames
|
|
||||||
|
|
||||||
export class SpiderEffect {
|
|
||||||
constructor(scene) {
|
|
||||||
this.spiders = [];
|
|
||||||
this._setupSpiders(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
_getRandomPointOnWall(wall) {
|
|
||||||
const position = new THREE.Vector3();
|
|
||||||
const width = wall.geometry.parameters.width;
|
|
||||||
const height = wall.geometry.parameters.height;
|
|
||||||
|
|
||||||
position.x = (Math.random() - 0.5) * width;
|
|
||||||
position.y = (Math.random() - 0.5) * height;
|
|
||||||
position.z = 0; // Local z is 0 for a plane
|
|
||||||
|
|
||||||
// Convert local position to world position
|
|
||||||
return wall.localToWorld(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
_createSpiderMesh() {
|
|
||||||
const spiderGroup = new THREE.Group();
|
|
||||||
const spiderMaterial = new THREE.MeshPhongMaterial({ color: 0x919191, shininess: 50 });
|
|
||||||
|
|
||||||
// Body
|
|
||||||
const bodyGeometry = new THREE.SphereGeometry(0.01, 6, 5);
|
|
||||||
const body = new THREE.Mesh(bodyGeometry, spiderMaterial);
|
|
||||||
body.scale.z = 0.6; // Flatten the sphere
|
|
||||||
body.castShadow = true;
|
|
||||||
spiderGroup.add(body);
|
|
||||||
|
|
||||||
// Head
|
|
||||||
const headGeometry = new THREE.SphereGeometry(0.005, 5, 4);
|
|
||||||
const head = new THREE.Mesh(headGeometry, spiderMaterial);
|
|
||||||
head.position.y = 0.015;
|
|
||||||
head.castShadow = true;
|
|
||||||
spiderGroup.add(head);
|
|
||||||
|
|
||||||
spiderGroup.userData = {
|
|
||||||
state: 'crawling', // 'crawling', 'waiting'
|
|
||||||
waitTimer: 0,
|
|
||||||
t: 0,
|
|
||||||
curve: null,
|
|
||||||
currentWall: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
return spiderGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
_findNewTarget(spider) {
|
|
||||||
if (!spider.userData.currentWall) {
|
|
||||||
// First time, pick a random wall
|
|
||||||
const walls = state.crawlSurfaces;
|
|
||||||
if (walls.length === 0) return;
|
|
||||||
spider.userData.currentWall = walls[Math.floor(Math.random() * walls.length)];
|
|
||||||
spider.position.copy(this._getRandomPointOnWall(spider.userData.currentWall));
|
|
||||||
}
|
|
||||||
|
|
||||||
const startPoint = spider.position.clone();
|
|
||||||
const endPoint = this._getRandomPointOnWall(spider.userData.currentWall);
|
|
||||||
|
|
||||||
// Create a curved path on the wall
|
|
||||||
const midPoint = new THREE.Vector3().lerpVectors(startPoint, endPoint, 0.5);
|
|
||||||
const direction = new THREE.Vector3().subVectors(endPoint, startPoint).normalize();
|
|
||||||
const wallNormal = spider.userData.currentWall.getWorldDirection(new THREE.Vector3()).negate();
|
|
||||||
|
|
||||||
// Get a perpendicular vector on the plane of the wall
|
|
||||||
const perpendicular = new THREE.Vector3().crossVectors(direction, wallNormal).normalize();
|
|
||||||
const offsetMagnitude = startPoint.distanceTo(endPoint) * (Math.random() * 0.4 - 0.2); // Random offset left or right
|
|
||||||
|
|
||||||
const controlPoint = midPoint.clone().add(perpendicular.multiplyScalar(offsetMagnitude));
|
|
||||||
|
|
||||||
spider.userData.curve = new THREE.QuadraticBezierCurve3(startPoint, controlPoint, endPoint);
|
|
||||||
spider.userData.t = 0;
|
|
||||||
spider.userData.state = 'crawling';
|
|
||||||
}
|
|
||||||
|
|
||||||
_setupSpiders(scene) {
|
|
||||||
for (let i = 0; i < SPIDER_COUNT; i++) {
|
|
||||||
const spider = this._createSpiderMesh();
|
|
||||||
scene.add(spider);
|
|
||||||
this.spiders.push(spider);
|
|
||||||
this._findNewTarget(spider); // Initial placement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
this.spiders.forEach(spider => {
|
|
||||||
const data = spider.userData;
|
|
||||||
|
|
||||||
if (data.state === 'crawling') {
|
|
||||||
if (!data.curve) {
|
|
||||||
this._findNewTarget(spider);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.t += SPIDER_SPEED;
|
|
||||||
if (data.t >= 1) {
|
|
||||||
spider.position.copy(data.curve.v2);
|
|
||||||
data.state = 'waiting';
|
|
||||||
data.waitTimer = SPIDER_WAIT_MIN + Math.random() * (SPIDER_WAIT_MAX - SPIDER_WAIT_MIN);
|
|
||||||
} else {
|
|
||||||
spider.position.copy(data.curve.getPoint(data.t));
|
|
||||||
|
|
||||||
// Smoothly turn the spider towards the tangent of the curve
|
|
||||||
const tangent = data.curve.getTangent(data.t);
|
|
||||||
const up = data.currentWall.getWorldDirection(new THREE.Vector3()).negate();
|
|
||||||
|
|
||||||
const targetQuaternion = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), tangent).multiply(
|
|
||||||
new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), up)
|
|
||||||
);
|
|
||||||
|
|
||||||
spider.quaternion.slerp(targetQuaternion, SPIDER_TURN_SPEED);
|
|
||||||
}
|
|
||||||
} else if (data.state === 'waiting') {
|
|
||||||
data.waitTimer--;
|
|
||||||
if (data.waitTimer <= 0) {
|
|
||||||
this._findNewTarget(spider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -18,28 +18,24 @@ export function createRoomWalls() {
|
|||||||
const backWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
const backWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||||
backWall.position.set(0, state.roomHeight / 2, -state.roomSize / 2);
|
backWall.position.set(0, state.roomHeight / 2, -state.roomSize / 2);
|
||||||
backWall.receiveShadow = true;
|
backWall.receiveShadow = true;
|
||||||
backWall.name = 'backWall';
|
|
||||||
state.scene.add(backWall);
|
state.scene.add(backWall);
|
||||||
|
|
||||||
// 2. Front Wall (behind the camera)
|
// 2. Front Wall (behind the camera)
|
||||||
const frontWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
const frontWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||||
frontWall.position.set(0, state.roomHeight / 2, state.roomSize / 2);
|
frontWall.position.set(0, state.roomHeight / 2, state.roomSize / 2);
|
||||||
frontWall.rotation.y = Math.PI;
|
frontWall.rotation.y = Math.PI;
|
||||||
frontWall.name = 'frontWall';
|
|
||||||
frontWall.receiveShadow = true;
|
frontWall.receiveShadow = true;
|
||||||
state.scene.add(frontWall);
|
state.scene.add(frontWall);
|
||||||
|
|
||||||
// 3. Left Wall
|
// 3. Left Wall
|
||||||
const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||||
leftWall.rotation.y = Math.PI / 2;
|
leftWall.rotation.y = Math.PI / 2;
|
||||||
leftWall.name = 'leftWall';
|
|
||||||
leftWall.position.set(-state.roomSize / 2, state.roomHeight / 2, 0);
|
leftWall.position.set(-state.roomSize / 2, state.roomHeight / 2, 0);
|
||||||
leftWall.receiveShadow = true;
|
leftWall.receiveShadow = true;
|
||||||
state.scene.add(leftWall);
|
state.scene.add(leftWall);
|
||||||
|
|
||||||
// 4. Right Wall
|
// 4. Right Wall
|
||||||
const rightWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
const rightWall = new THREE.Mesh(new THREE.PlaneGeometry(state.roomSize, state.roomHeight), wallMaterial);
|
||||||
rightWall.name = 'rightWall';
|
|
||||||
rightWall.rotation.y = -Math.PI / 2;
|
rightWall.rotation.y = -Math.PI / 2;
|
||||||
rightWall.position.set(state.roomSize / 2, state.roomHeight / 2, 0);
|
rightWall.position.set(state.roomSize / 2, state.roomHeight / 2, 0);
|
||||||
rightWall.receiveShadow = true;
|
rightWall.receiveShadow = true;
|
||||||
@ -62,8 +58,6 @@ export function createRoomWalls() {
|
|||||||
ceiling.position.set(0, state.roomHeight, 0);
|
ceiling.position.set(0, state.roomHeight, 0);
|
||||||
ceiling.receiveShadow = true;
|
ceiling.receiveShadow = true;
|
||||||
state.scene.add(ceiling);
|
state.scene.add(ceiling);
|
||||||
|
|
||||||
state.crawlSurfaces.push(backWall, frontWall, leftWall, rightWall);
|
|
||||||
|
|
||||||
// --- 6. Add a Window to the Back Wall ---
|
// --- 6. Add a Window to the Back Wall ---
|
||||||
const windowWidth = 1.5;
|
const windowWidth = 1.5;
|
||||||
|
|||||||
@ -4,7 +4,6 @@ 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,22 +133,5 @@ export function createSceneObjects() {
|
|||||||
createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.2, Math.PI/2, 0);
|
createBookshelf(-state.roomSize/2 + 0.2, state.roomSize/2*0.2, 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);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,15 +44,12 @@ export function initState() {
|
|||||||
// Utilities
|
// Utilities
|
||||||
loader: new THREE.TextureLoader(),
|
loader: new THREE.TextureLoader(),
|
||||||
landingSurfaces: [],
|
landingSurfaces: [],
|
||||||
crawlSurfaces: [], // Surfaces for spiders to crawl on
|
|
||||||
bookLevitation: {
|
bookLevitation: {
|
||||||
state: 'resting', // 'resting', 'levitating', 'returning'
|
state: 'resting', // 'resting', 'levitating', 'returning'
|
||||||
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB |
Loading…
Reference in New Issue
Block a user