Compare commits
No commits in common. "ae3383986aa20ab3ec85d57c79e61e01eb27eee3" and "40952b348abeac17b44d1b2e09c4979c3273745b" have entirely different histories.
ae3383986a
...
40952b348a
@ -1,17 +1,17 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
import { updateDoor } from '../scene/door.js';
|
||||||
import { updateVcrDisplay } from '../scene/vcr-display.js';
|
import { updateVcrDisplay } from '../scene/vcr-display.js';
|
||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
import { updateScreenEffect } from '../scene/magic-mirror.js'
|
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';
|
|
||||||
|
|
||||||
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.0002;
|
||||||
|
|
||||||
const camAmplitude = 0.2;
|
const camAmplitude = 0.2;
|
||||||
const lookAmplitude = 0.4;
|
const lookAmplitude = 0.1;
|
||||||
|
|
||||||
// Base Camera Position in front of the TV
|
// Base Camera Position in front of the TV
|
||||||
const baseX = -0.5;
|
const baseX = -0.5;
|
||||||
@ -19,8 +19,8 @@ function updateCamera() {
|
|||||||
const baseZ = 2.2;
|
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.7;
|
||||||
const baseTargetY = 1.6;
|
const baseTargetY = 1.7;
|
||||||
const baseTargetZ = -0.3;
|
const baseTargetZ = -0.3;
|
||||||
|
|
||||||
// Camera Position Offsets (Drift)
|
// Camera Position Offsets (Drift)
|
||||||
@ -33,7 +33,7 @@ function updateCamera() {
|
|||||||
state.camera.position.z = baseZ + camOffsetZ;
|
state.camera.position.z = baseZ + camOffsetZ;
|
||||||
|
|
||||||
// LookAt Target Offsets (Subtle Gaze Shift)
|
// LookAt Target Offsets (Subtle Gaze Shift)
|
||||||
const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude * 4;
|
const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude * 3;
|
||||||
const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude;
|
const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude;
|
||||||
|
|
||||||
// Apply lookAt to the subtly shifted target
|
// Apply lookAt to the subtly shifted target
|
||||||
@ -70,7 +70,9 @@ function updateScreenLight() {
|
|||||||
|
|
||||||
function updateShaderTime() {
|
function updateShaderTime() {
|
||||||
if (state.tvScreen && state.tvScreen.material.uniforms && state.tvScreen.material.uniforms.u_time) {
|
if (state.tvScreen && state.tvScreen.material.uniforms && state.tvScreen.material.uniforms.u_time) {
|
||||||
state.tvScreen.material.uniforms.u_time.value = state.clock.getElapsedTime();
|
if (state.tvScreenPowered) {
|
||||||
|
state.tvScreen.material.uniforms.u_time.value = state.clock.getElapsedTime();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +82,18 @@ function updateVideo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateVcr() {
|
||||||
|
const currentTime = state.baseTime + state.videoElement.currentTime;
|
||||||
|
if (Math.abs(currentTime - state.lastUpdateTime) > 0.1) {
|
||||||
|
updateVcrDisplay(currentTime);
|
||||||
|
state.lastUpdateTime = currentTime;
|
||||||
|
}
|
||||||
|
if (currentTime - state.lastBlinkToggleTime > 0.5) { // Blink every 0.5 seconds
|
||||||
|
state.blinkState = !state.blinkState;
|
||||||
|
state.lastBlinkToggleTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateBooks() {
|
function updateBooks() {
|
||||||
const LEVITATE_CHANCE = 0.0003; // Chance for a resting book to start levitating per frame
|
const LEVITATE_CHANCE = 0.0003; // Chance for a resting book to start levitating per frame
|
||||||
const LEVITATE_DURATION_MIN = 100; // frames
|
const LEVITATE_DURATION_MIN = 100; // frames
|
||||||
@ -155,12 +169,13 @@ export function animate() {
|
|||||||
updateScreenLight();
|
updateScreenLight();
|
||||||
updateVideo();
|
updateVideo();
|
||||||
updateShaderTime();
|
updateShaderTime();
|
||||||
|
// updateVcr();
|
||||||
updateBooks();
|
updateBooks();
|
||||||
updatePictureFrame();
|
// updateDoor();
|
||||||
|
// updatePictureFrame();
|
||||||
updateScreenEffect();
|
updateScreenEffect();
|
||||||
updateFire();
|
updateFire();
|
||||||
updateCauldron();
|
updateCauldron();
|
||||||
updateRats();
|
|
||||||
|
|
||||||
// RENDER!
|
// RENDER!
|
||||||
state.renderer.render(state.scene, state.camera);
|
state.renderer.render(state.scene, state.camera);
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import * as THREE from 'three';
|
|||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
import { fireVertexShader, fireFragmentShader } from '../shaders/fire-shaders.js';
|
import { fireVertexShader, fireFragmentShader } from '../shaders/fire-shaders.js';
|
||||||
import stoneTextureUrl from '/textures/stone_floor.png';
|
import stoneTextureUrl from '/textures/stone_floor.png';
|
||||||
import woodTextureUrl from '/textures/wood.png';
|
|
||||||
|
|
||||||
let fireMaterial;
|
let fireMaterial;
|
||||||
let fireLight;
|
let fireLight;
|
||||||
@ -17,12 +16,6 @@ export function createFireplace(x, z, rotY) {
|
|||||||
stoneTexture.repeat.set(2, 2);
|
stoneTexture.repeat.set(2, 2);
|
||||||
const stoneMaterial = new THREE.MeshPhongMaterial({ map: stoneTexture, color: 0x999999, shininess: 10 });
|
const stoneMaterial = new THREE.MeshPhongMaterial({ map: stoneTexture, color: 0x999999, shininess: 10 });
|
||||||
|
|
||||||
const woodTexture = state.loader.load(woodTextureUrl);
|
|
||||||
const logMaterial = new THREE.MeshPhongMaterial({
|
|
||||||
map: woodTexture,
|
|
||||||
color: 0x5c4033 // A dark wood tint
|
|
||||||
});
|
|
||||||
|
|
||||||
const hearthWidth = 2.5;
|
const hearthWidth = 2.5;
|
||||||
const hearthHeight = 0.2;
|
const hearthHeight = 0.2;
|
||||||
const hearthDepth = 1.5;
|
const hearthDepth = 1.5;
|
||||||
@ -79,23 +72,6 @@ export function createFireplace(x, z, rotY) {
|
|||||||
rightJamb.position.set(openingWidth / 2 + frameThickness / 2, hearthHeight + jambHeight / 2, bodyDepth / 2 + frameDepth / 2);
|
rightJamb.position.set(openingWidth / 2 + frameThickness / 2, hearthHeight + jambHeight / 2, bodyDepth / 2 + frameDepth / 2);
|
||||||
fireplaceGroup.add(rightJamb);
|
fireplaceGroup.add(rightJamb);
|
||||||
|
|
||||||
// 3.8. Logs inside fireplace
|
|
||||||
const logGeo = new THREE.CylinderGeometry(0.08, 0.1, openingWidth * 0.7, 8);
|
|
||||||
|
|
||||||
const createLog = (pos, rot) => {
|
|
||||||
const log = new THREE.Mesh(logGeo, logMaterial);
|
|
||||||
log.position.copy(pos);
|
|
||||||
log.rotation.copy(rot);
|
|
||||||
log.castShadow = false;
|
|
||||||
log.receiveShadow = true;
|
|
||||||
fireplaceGroup.add(log);
|
|
||||||
};
|
|
||||||
|
|
||||||
const logY = hearthHeight + 0.1;
|
|
||||||
createLog(new THREE.Vector3(0, logY, 0.5), new THREE.Euler(0, 0, Math.PI / 2));
|
|
||||||
createLog(new THREE.Vector3(-0.1, logY + 0.1, 0.4), new THREE.Euler(0.2, 0, Math.PI / 2.2));
|
|
||||||
createLog(new THREE.Vector3(0.2, logY + 0.1, 0.5), new THREE.Euler(-0.2, 0, Math.PI / 1.8));
|
|
||||||
|
|
||||||
// 4. Animated Fire
|
// 4. Animated Fire
|
||||||
fireMaterial = new THREE.ShaderMaterial({
|
fireMaterial = new THREE.ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
|
|||||||
@ -1,93 +0,0 @@
|
|||||||
import * as THREE from 'three';
|
|
||||||
import { state } from '../state.js';
|
|
||||||
|
|
||||||
const SLEEP_WAIT = 60;
|
|
||||||
|
|
||||||
export class Rat {
|
|
||||||
constructor(path, initialDelay) {
|
|
||||||
this.path = path;
|
|
||||||
this.speed = 0.002 + Math.random() * 0.002;
|
|
||||||
this.progress = -initialDelay; // Start with a negative progress as a delay
|
|
||||||
|
|
||||||
// The rat's body
|
|
||||||
const bodyGeo = new THREE.CapsuleGeometry(0.02, 0.07, 4, 8);
|
|
||||||
const bodyMat = new THREE.MeshPhongMaterial({ color: 0x2a1d1d, shininess: 10 });
|
|
||||||
this.mesh = new THREE.Mesh(bodyGeo, bodyMat);
|
|
||||||
this.mesh.castShadow = true;
|
|
||||||
this.mesh.rotation.z = Math.PI / 2;
|
|
||||||
this.mesh.rotation.y = Math.PI / 2;
|
|
||||||
this.mesh.position.y = 0;
|
|
||||||
|
|
||||||
this.actor = new THREE.Group();
|
|
||||||
this.actor.visible = false; // Start invisible
|
|
||||||
this.actor.add(this.mesh);
|
|
||||||
|
|
||||||
state.scene.add(this.actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
this.progress += this.speed;
|
|
||||||
|
|
||||||
// If the rat has finished its path, reset with a new delay
|
|
||||||
if (this.progress > 1.0) {
|
|
||||||
this.progress = -(Math.random() * SLEEP_WAIT); // wait some seconds before going again
|
|
||||||
this.actor.visible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't do anything if we are in the delay period
|
|
||||||
if (this.progress < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First time it becomes visible
|
|
||||||
if (!this.actor.visible) {
|
|
||||||
this.actor.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move along the path
|
|
||||||
const position = this.path.getPointAt(this.progress);
|
|
||||||
this.actor.position.copy(position);
|
|
||||||
|
|
||||||
// Orient along the path
|
|
||||||
const tangent = this.path.getTangentAt(this.progress).normalize();
|
|
||||||
const lookAtPosition = position.clone().add(tangent);
|
|
||||||
this.actor.lookAt(lookAtPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createRats(x, y, z, rotY) {
|
|
||||||
// --- 9.5 Rat Hole ---
|
|
||||||
const holeGeo = new THREE.CircleGeometry(0.15, 16);
|
|
||||||
const holeMat = new THREE.MeshBasicMaterial({ color: 0x000000 });
|
|
||||||
const ratHole = new THREE.Mesh(holeGeo, holeMat);
|
|
||||||
ratHole.position.set(x, y + 0.1, z);
|
|
||||||
ratHole.rotation.y = rotY;
|
|
||||||
state.scene.add(ratHole);
|
|
||||||
|
|
||||||
// Define a path for the rats to follow
|
|
||||||
const ratPath = new THREE.CatmullRomCurve3([
|
|
||||||
new THREE.Vector3(x, y, z), // Start at the hole
|
|
||||||
new THREE.Vector3(x-2.0, 0, z),
|
|
||||||
new THREE.Vector3(0.8, 0, -1.2),
|
|
||||||
new THREE.Vector3(-1.0, 0, -1.3),
|
|
||||||
new THREE.Vector3(-1.8, 0, -1.0),
|
|
||||||
new THREE.Vector3(-1.9, 0, 0.7),
|
|
||||||
new THREE.Vector3(0.5, 0, 0.5),
|
|
||||||
new THREE.Vector3(1.8, 0, 1.0),
|
|
||||||
new THREE.Vector3(x, y, z), // End at the hole
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Create a few rats with different starting delays
|
|
||||||
state.rats.push(new Rat(ratPath, 0.5));
|
|
||||||
state.rats.push(new Rat(ratPath, 3.0));
|
|
||||||
state.rats.push(new Rat(ratPath, 5.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateRats() {
|
|
||||||
if (!state.rats || state.rats.length === 0) return;
|
|
||||||
|
|
||||||
state.rats.forEach(rat => {
|
|
||||||
rat.update();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -6,7 +6,6 @@ import { createMagicMirror } from './magic-mirror.js';
|
|||||||
import { createFireplace } from './fireplace.js';
|
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 { 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';
|
||||||
@ -78,7 +77,7 @@ export function createSceneObjects() {
|
|||||||
|
|
||||||
state.scene.add(candleGroup);
|
state.scene.add(candleGroup);
|
||||||
|
|
||||||
createTable(-1.8, 0, -1.2, Math.PI / 2.3);
|
createTable(-1.8, 0, -0.8, Math.PI / 2.3);
|
||||||
|
|
||||||
// Add cauldron on top of the table (Y = table height + cauldron radius)
|
// Add cauldron on top of the table (Y = table height + cauldron radius)
|
||||||
createCauldron(-1.8, 0.5 + 0.2, -0.8);
|
createCauldron(-1.8, 0.5 + 0.2, -0.8);
|
||||||
@ -141,20 +140,17 @@ 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);
|
||||||
|
|
||||||
createRats(state.roomSize/2 - 0.01, 0, 0.37, -Math.PI / 2);
|
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.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);
|
||||||
|
|
||||||
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.7),
|
position: new THREE.Vector3(-state.roomSize/2, 1.7, -state.roomSize/2 + 0.6),
|
||||||
width: 0.7,
|
width: 0.5,
|
||||||
height: 1,
|
height: 1,
|
||||||
imageUrls: [painting1, painting2],
|
imageUrls: [painting1, painting2],
|
||||||
rotationY: Math.PI / 2
|
rotationY: Math.PI / 2
|
||||||
});
|
});
|
||||||
|
|
||||||
state.pictureFrames.push(pictureFrame);
|
state.pictureFrames.push(pictureFrame);
|
||||||
|
|
||||||
const pictureFrame2 = new PictureFrame(state.scene, {
|
const pictureFrame2 = new PictureFrame(state.scene, {
|
||||||
@ -165,13 +161,4 @@ export function createSceneObjects() {
|
|||||||
rotationY: -Math.PI / 2
|
rotationY: -Math.PI / 2
|
||||||
});
|
});
|
||||||
state.pictureFrames.push(pictureFrame2);
|
state.pictureFrames.push(pictureFrame2);
|
||||||
|
|
||||||
const pictureFrame3 = new PictureFrame(state.scene, {
|
|
||||||
position: new THREE.Vector3(state.roomSize/2, 1.7, 0.75),
|
|
||||||
width: 0.7,
|
|
||||||
height: 1.2,
|
|
||||||
imageUrls: [painting2, painting1],
|
|
||||||
rotationY: -Math.PI / 2
|
|
||||||
});
|
|
||||||
state.pictureFrames.push(pictureFrame3);
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
magic-mirror/src/scene/screen-shaders.js
Normal file
18
magic-mirror/src/scene/screen-shaders.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const screenVertexShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const screenFragmentShader = `
|
||||||
|
varying vec2 vUv;
|
||||||
|
uniform sampler2D videoTexture;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Sample the video texture
|
||||||
|
gl_FragColor = texture2D(videoTexture, vUv);
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -49,7 +49,7 @@ float fbm(in vec2 st) {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 uv = vUv;
|
vec2 uv = vUv;
|
||||||
float q = fbm(uv * 2.0 - vec2(0.0, u_time * 1.2));
|
float q = fbm(uv * 2.0 - vec2(0.0, u_time * 0.2));
|
||||||
float r = fbm(uv * 2.0 + q + vec2(1.7, 9.2) + vec2(0.0, u_time * -0.3));
|
float r = fbm(uv * 2.0 + q + vec2(1.7, 9.2) + vec2(0.0, u_time * -0.3));
|
||||||
|
|
||||||
float fireAmount = fbm(uv * 2.0 + r + vec2(0.0, u_time * 0.15));
|
float fireAmount = fbm(uv * 2.0 + r + vec2(0.0, u_time * 0.15));
|
||||||
@ -57,7 +57,7 @@ void main() {
|
|||||||
// Shape the fire to rise from the bottom
|
// Shape the fire to rise from the bottom
|
||||||
fireAmount *= (1.0 - uv.y);
|
fireAmount *= (1.0 - uv.y);
|
||||||
|
|
||||||
vec3 fireColor = mix(vec3(0.9, 0.3, 0.1), vec3(1.0, 0.9, 0.3), fireAmount);
|
vec3 fireColor = mix(vec3(0.9, 0.5, 0.1), vec3(1.0, 0.9, 0.3), fireAmount);
|
||||||
gl_FragColor = vec4(fireColor, fireAmount * 2.0);
|
gl_FragColor = vec4(fireColor, fireAmount * 2.0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -57,7 +57,7 @@ void main() {
|
|||||||
finalColor = baseColor;
|
finalColor = baseColor;
|
||||||
} else if (u_effect_type < 1.9) { // "Summon Vision" (Warm-up) effect
|
} else if (u_effect_type < 1.9) { // "Summon Vision" (Warm-up) effect
|
||||||
// This is now a multi-stage effect controlled by u_effect_strength (0.0 -> 1.0)
|
// This is now a multi-stage effect controlled by u_effect_strength (0.0 -> 1.0)
|
||||||
float noiseVal = noise(vUv * 50.0 + vec2(0.0, u_time * -125.0));
|
float noiseVal = noise(vUv * 10.0);
|
||||||
vec3 mistColor = vec3(0.8, 0.7, 1.0) * noiseVal;
|
vec3 mistColor = vec3(0.8, 0.7, 1.0) * noiseVal;
|
||||||
vec4 videoColor = texture2D(videoTexture, vUv);
|
vec4 videoColor = texture2D(videoTexture, vUv);
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ void main() {
|
|||||||
} else { // "Vision Fades" (Power-down) effect
|
} else { // "Vision Fades" (Power-down) effect
|
||||||
// Multi-stage effect: Last frame -> fade to mist -> fade to transparent
|
// Multi-stage effect: Last frame -> fade to mist -> fade to transparent
|
||||||
|
|
||||||
float noiseVal = noise(vUv * 50.0 + vec2(0.0, u_time * 123.0));
|
float noiseVal = noise(vUv * 10.0);
|
||||||
vec3 mistColor = vec3(0.8, 0.7, 1.0) * noiseVal;
|
vec3 mistColor = vec3(0.8, 0.7, 1.0) * noiseVal;
|
||||||
vec4 videoColor = texture2D(videoTexture, vUv);
|
vec4 videoColor = texture2D(videoTexture, vUv);
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,6 @@ export function initState() {
|
|||||||
loader: new THREE.TextureLoader(),
|
loader: new THREE.TextureLoader(),
|
||||||
landingSurfaces: [],
|
landingSurfaces: [],
|
||||||
crawlSurfaces: [], // Surfaces for spiders to crawl on
|
crawlSurfaces: [], // Surfaces for spiders to crawl on
|
||||||
rats: [], // Array to hold rats
|
|
||||||
bookLevitation: {
|
bookLevitation: {
|
||||||
state: 'resting', // 'resting', 'levitating', 'returning'
|
state: 'resting', // 'resting', 'levitating', 'returning'
|
||||||
timer: 0,
|
timer: 0,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user