From 499fc006f5a4151e4fbbe4f520eb6a9005f2a9f4 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Wed, 19 Nov 2025 23:43:52 +0100 Subject: [PATCH] Feature: bubling cauldron --- magic-mirror/src/core/animate.js | 2 + magic-mirror/src/scene/cauldron.js | 97 ++++++++++++++++++++++++++++++ magic-mirror/src/scene/root.js | 4 ++ 3 files changed, 103 insertions(+) create mode 100644 magic-mirror/src/scene/cauldron.js diff --git a/magic-mirror/src/core/animate.js b/magic-mirror/src/core/animate.js index ac566d3..7feac1c 100644 --- a/magic-mirror/src/core/animate.js +++ b/magic-mirror/src/core/animate.js @@ -3,6 +3,7 @@ import { updateDoor } from '../scene/door.js'; import { updateVcrDisplay } from '../scene/vcr-display.js'; import { state } from '../state.js'; import { updateScreenEffect } from '../scene/magic-mirror.js' +import { updateCauldron } from '../scene/cauldron.js'; import { updateFire } from '../scene/fireplace.js'; function updateCamera() { @@ -174,6 +175,7 @@ export function animate() { // updatePictureFrame(); updateScreenEffect(); updateFire(); + updateCauldron(); // RENDER! state.renderer.render(state.scene, state.camera); diff --git a/magic-mirror/src/scene/cauldron.js b/magic-mirror/src/scene/cauldron.js new file mode 100644 index 0000000..702a922 --- /dev/null +++ b/magic-mirror/src/scene/cauldron.js @@ -0,0 +1,97 @@ +import * as THREE from 'three'; +import { state } from '../state.js'; + +let cauldronParticles; +let cauldronLight; +const particleCount = 8; +const particleVelocities = []; + +export function createCauldron(x, y, z) { + const cauldronGroup = new THREE.Group(); + + const cauldronRadius = 0.2; + const cauldronHeight = 0.25; + + // 1. Cauldron Body + const cauldronMaterial = new THREE.MeshPhongMaterial({ color: 0x111111, shininess: 80 }); + // Use a sphere geometry cut in half for the bowl shape + const cauldronGeo = new THREE.SphereGeometry(cauldronRadius, 32, 16, 0, Math.PI * 2, 0, Math.PI / 2); + const cauldronMesh = new THREE.Mesh(cauldronGeo, cauldronMaterial); + cauldronMesh.castShadow = true; + cauldronMesh.rotation.z = Math.PI; + cauldronGroup.add(cauldronMesh); + + // 2. Glowing Liquid Surface + const liquidColor = 0x00ff00; // Bright green + const liquidMaterial = new THREE.MeshPhongMaterial({ + color: liquidColor, + emissive: liquidColor, + emissiveIntensity: 0.6, + shininess: 100 + }); + const liquidGeo = new THREE.CircleGeometry(cauldronRadius * 0.95, 32); + const liquidSurface = new THREE.Mesh(liquidGeo, liquidMaterial); + liquidSurface.rotation.x = -Math.PI / 2; + liquidSurface.position.y = -0.01; // Slightly below the rim + cauldronGroup.add(liquidSurface); + + // 3. Bubbling Particles + const particleGeo = new THREE.BufferGeometry(); + const positions = new Float32Array(particleCount * 3); + + for (let i = 0; i < particleCount; i++) { + positions[i * 3] = (Math.random() - 0.5) * cauldronRadius * 1.5; + positions[i * 3 + 1] = (Math.random() - 0.5) * 0.05; // Start near the surface + positions[i * 3 + 2] = (Math.random() - 0.5) * cauldronRadius * 1.5; + particleVelocities.push((0.05 + Math.random() * 0.1) / 60); // Random upward velocity + } + particleGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + + const particleMaterial = new THREE.PointsMaterial({ + color: liquidColor, + size: 0.015, + transparent: true, + blending: THREE.AdditiveBlending, + depthWrite: false, + opacity: 0.7 + }); + + cauldronParticles = new THREE.Points(particleGeo, particleMaterial); + cauldronGroup.add(cauldronParticles); + + // 4. Light from the cauldron + cauldronLight = new THREE.PointLight(liquidColor, 0.8, 3); + cauldronLight.position.y = 0.2; + cauldronLight.castShadow = true; + cauldronGroup.add(cauldronLight); + + // Position and add to scene + cauldronGroup.position.set(x, y, z); + state.scene.add(cauldronGroup); +} + +export function updateCauldron() { + if (!cauldronParticles || !cauldronLight) return; + + // Animate Bubbles + const positions = cauldronParticles.geometry.attributes.position.array; + const bubbleMaxHeight = 0.1; + const overfly = Math.random() * bubbleMaxHeight; + + for (let i = 0; i < particleCount; i++) { + positions[i * 3 + 1] += particleVelocities[i]; // Move bubble up + + // Reset bubble if it goes too high + if (positions[i * 3 + 1] > bubbleMaxHeight + overfly) { + positions[i * 3 + 1] = (Math.random() - 0.5) * 0.05; + // Give it a new random X/Z position + positions[i * 3] = (Math.random() - 0.5) * 0.2 * 1.5; + positions[i * 3 + 2] = (Math.random() - 0.5) * 0.2 * 1.5; + } + } + cauldronParticles.geometry.attributes.position.needsUpdate = true; + + // Flicker Light + const flicker = Math.random() * 0.02; + cauldronLight.intensity = 0.1 + flicker; +} \ No newline at end of file diff --git a/magic-mirror/src/scene/root.js b/magic-mirror/src/scene/root.js index da768ba..3af39ed 100644 --- a/magic-mirror/src/scene/root.js +++ b/magic-mirror/src/scene/root.js @@ -5,6 +5,7 @@ import { createBookshelf } from './bookshelf.js'; import { createMagicMirror } from './magic-mirror.js'; import { createFireplace } from './fireplace.js'; import { createTable } from './table.js'; +import { createCauldron } from './cauldron.js'; import { PictureFrame } from './PictureFrame.js'; import painting1 from '/textures/painting1.jpg'; import painting2 from '/textures/painting2.jpg'; @@ -78,6 +79,9 @@ export function createSceneObjects() { createTable(-1.8, 0, -0.8, Math.PI / 2.3); + // Add cauldron on top of the table (Y = table height + cauldron radius) + createCauldron(-1.8, 0.5 + 0.2, -0.8); + // --- 8. Timber Frames --- const beamThickness = 0.15; const beamDepth = 0.2;