148 lines
5.7 KiB
JavaScript
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;
|
|
} |