music-video-gen/magic-mirror/src/scene/fireplace.js
2025-11-20 22:32:01 +01:00

148 lines
5.7 KiB
JavaScript

import * as THREE from 'three';
import { state } from '../state.js';
import { fireVertexShader, fireFragmentShader } from '../shaders/fire-shaders.js';
import stoneTextureUrl from '/textures/stone_floor.png';
import woodTextureUrl from '/textures/wood.png';
let fireMaterial;
let fireLight;
export function createFireplace(x, z, rotY) {
const fireplaceGroup = new THREE.Group();
// --- Materials ---
const stoneTexture = state.loader.load(stoneTextureUrl);
stoneTexture.wrapS = THREE.RepeatWrapping;
stoneTexture.wrapT = THREE.RepeatWrapping;
stoneTexture.repeat.set(2, 2);
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 hearthHeight = 0.2;
const hearthDepth = 1.5;
// 1. Hearth (base)
const hearthGeo = new THREE.BoxGeometry(hearthWidth, hearthHeight, hearthDepth);
const hearth = new THREE.Mesh(hearthGeo, stoneMaterial);
hearth.position.y = hearthHeight / 2;
hearth.receiveShadow = true;
fireplaceGroup.add(hearth);
// 2. Fireplace Body
const bodyWidth = 2.2;
const bodyHeight = 2.2;
const bodyDepth = 0.8;
const bodyGeo = new THREE.BoxGeometry(bodyWidth, bodyHeight, bodyDepth);
const body = new THREE.Mesh(bodyGeo, stoneMaterial);
body.position.y = hearthHeight + bodyHeight / 2;
body.castShadow = true;
body.receiveShadow = true;
fireplaceGroup.add(body);
// Chimney
const chimneyGeo = new THREE.BoxGeometry(bodyWidth*0.6, state.roomHeight, bodyDepth*0.6);
const chimney = new THREE.Mesh(chimneyGeo, stoneMaterial);
chimney.position.z = -bodyDepth*0.2;
chimney.position.y = bodyHeight;
chimney.castShadow = true;
chimney.receiveShadow = true;
fireplaceGroup.add(chimney);
// 3. Fireplace Opening (carved out look)
const openingWidth = 1.2;
const openingHeight = 1.0;
const openingGeo = new THREE.BoxGeometry(openingWidth, openingHeight, bodyDepth);
const openingMaterial = new THREE.MeshPhongMaterial({ color: 0x1a1a1a }); // Dark interior
const opening = new THREE.Mesh(openingGeo, openingMaterial);
opening.position.set(0, hearthHeight + openingHeight / 2, 0.01);
fireplaceGroup.add(opening);
// 3.5. Thick Stone Frame around Opening
const frameThickness = 0.2;
const frameDepth = bodyDepth * 0.25; // Make it stick out a bit
// Lintel (Top Frame)
const lintelWidth = openingWidth + 2 * frameThickness;
const lintelGeo = new THREE.BoxGeometry(lintelWidth, frameThickness, frameDepth+0.1);
const lintel = new THREE.Mesh(lintelGeo, stoneMaterial);
lintel.position.set(0, hearthHeight + openingHeight + frameThickness / 2, bodyDepth / 2 + frameDepth / 2);
lintel.castShadow = true;
lintel.receiveShadow = true;
fireplaceGroup.add(lintel);
// Jambs (Side Frames)
const jambHeight = openingHeight + frameThickness; // Go up to the lintel
const jambGeo = new THREE.BoxGeometry(frameThickness, jambHeight, frameDepth);
const leftJamb = new THREE.Mesh(jambGeo, stoneMaterial);
leftJamb.position.set(-openingWidth / 2 - frameThickness / 2, hearthHeight + jambHeight / 2, bodyDepth / 2 + frameDepth / 2);
fireplaceGroup.add(leftJamb);
const rightJamb = new THREE.Mesh(jambGeo, stoneMaterial);
rightJamb.position.set(openingWidth / 2 + frameThickness / 2, hearthHeight + jambHeight / 2, bodyDepth / 2 + frameDepth / 2);
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
fireMaterial = new THREE.ShaderMaterial({
uniforms: {
u_time: { value: 0.0 },
},
vertexShader: fireVertexShader,
fragmentShader: fireFragmentShader,
transparent: true,
depthWrite: false,
});
const fireGeo = new THREE.PlaneGeometry(openingWidth * 1.2, openingHeight * 1.2);
const firePlane = new THREE.Mesh(fireGeo, fireMaterial);
firePlane.position.set(0, hearthHeight + openingHeight / 2, 0.42);
fireplaceGroup.add(firePlane);
// 5. Fire Light
fireLight = new THREE.PointLight(0xffaa33, 1.5, 8);
fireLight.position.set(0, hearthHeight + openingHeight / 2, 0.3);
fireLight.castShadow = true;
fireLight.shadow.mapSize.width = 512;
fireLight.shadow.mapSize.height = 512;
fireplaceGroup.add(fireLight);
// Position and add to scene
fireplaceGroup.position.set(x, 0, z);
fireplaceGroup.rotation.y = rotY;
state.scene.add(fireplaceGroup);
}
export function updateFire() {
if (!fireMaterial || !fireLight) return;
// Animate shader time
fireMaterial.uniforms.u_time.value = state.clock.getElapsedTime();
// Flicker light
const flicker = Math.random() * 0.4;
fireLight.intensity = 1.2 + flicker;
const t = state.clock.getElapsedTime();
fireLight.position.y = 0.2 + 1.0 / 2 + Math.sin(t * 3)*0.01 + Math.cos(t * 7)*0.01 + Math.cos(t * 11 ) * 0.01;
}