From 43e746a5a00638fcca329cc342575476ef7cd913 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Thu, 20 Nov 2025 18:17:11 +0100 Subject: [PATCH] Feature: add rats running on a path from a hole around the room --- magic-mirror/src/scene/rat.js | 92 ++++++++++++++++++++++++++++++++++ magic-mirror/src/scene/root.js | 4 ++ magic-mirror/src/state.js | 1 + 3 files changed, 97 insertions(+) create mode 100644 magic-mirror/src/scene/rat.js diff --git a/magic-mirror/src/scene/rat.js b/magic-mirror/src/scene/rat.js new file mode 100644 index 0000000..76fd3a1 --- /dev/null +++ b/magic-mirror/src/scene/rat.js @@ -0,0 +1,92 @@ +import * as THREE from 'three'; +import { state } from '../state.js'; + +const SLEEP_WAIT = 1; + +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, 0, 1.0), + new THREE.Vector3(-1.0, 0, 1.8), + new THREE.Vector3(-1.5, 0, 0.5), + 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(); + }); +} \ No newline at end of file diff --git a/magic-mirror/src/scene/root.js b/magic-mirror/src/scene/root.js index 3af39ed..aa6ad77 100644 --- a/magic-mirror/src/scene/root.js +++ b/magic-mirror/src/scene/root.js @@ -6,6 +6,7 @@ import { createMagicMirror } from './magic-mirror.js'; import { createFireplace } from './fireplace.js'; import { createTable } from './table.js'; import { createCauldron } from './cauldron.js'; +import { createRats } from './rat.js'; import { PictureFrame } from './PictureFrame.js'; import painting1 from '/textures/painting1.jpg'; import painting2 from '/textures/painting2.jpg'; @@ -140,6 +141,8 @@ export function createSceneObjects() { // --- 9. Fireplace --- 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.7, Math.PI/2, 0); createBookshelf(-state.roomSize/2 * 0.7, -state.roomSize/2+0.3, 0, 1); @@ -151,6 +154,7 @@ export function createSceneObjects() { imageUrls: [painting1, painting2], rotationY: Math.PI / 2 }); + state.pictureFrames.push(pictureFrame); const pictureFrame2 = new PictureFrame(state.scene, { diff --git a/magic-mirror/src/state.js b/magic-mirror/src/state.js index 096b9c2..96d9dd3 100644 --- a/magic-mirror/src/state.js +++ b/magic-mirror/src/state.js @@ -54,6 +54,7 @@ export function initState() { loader: new THREE.TextureLoader(), landingSurfaces: [], crawlSurfaces: [], // Surfaces for spiders to crawl on + rats: [], // Array to hold rats bookLevitation: { state: 'resting', // 'resting', 'levitating', 'returning' timer: 0,