music-video-gen/magic-mirror/src/scene/cauldron.js
2025-11-19 23:43:52 +01:00

97 lines
3.5 KiB
JavaScript

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;
}