From cdd90a4c5732b5f86c51e609312a5a4e2768fd72 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Fri, 21 Nov 2025 23:44:02 +0100 Subject: [PATCH] Feature: torches on stage --- party-cathedral/src/scene/root.js | 1 + party-cathedral/src/scene/stage-torches.js | 117 +++++++++++++++++++++ party-cathedral/textures/spark.png | Bin 0 -> 950 bytes 3 files changed, 118 insertions(+) create mode 100644 party-cathedral/src/scene/stage-torches.js create mode 100644 party-cathedral/textures/spark.png diff --git a/party-cathedral/src/scene/root.js b/party-cathedral/src/scene/root.js index d273540..b659329 100644 --- a/party-cathedral/src/scene/root.js +++ b/party-cathedral/src/scene/root.js @@ -9,6 +9,7 @@ import { Pews } from './pews.js'; import { Stage } from './stage.js'; import { MedievalMusicians } from './medieval-musicians.js'; import { PartyGuests } from './party-guests.js'; +import { StageTorches } from './stage-torches.js'; // Scene Features ^^^ // --- Scene Modeling Function --- diff --git a/party-cathedral/src/scene/stage-torches.js b/party-cathedral/src/scene/stage-torches.js new file mode 100644 index 0000000..0f28a8f --- /dev/null +++ b/party-cathedral/src/scene/stage-torches.js @@ -0,0 +1,117 @@ +import * as THREE from 'three'; +import { state } from '../state.js'; +import { SceneFeature } from './SceneFeature.js'; +import sceneFeatureManager from './SceneFeatureManager.js'; +import sparkTextureUrl from '/textures/spark.png'; + +export class StageTorches extends SceneFeature { + constructor() { + super(); + this.torches = []; + sceneFeatureManager.register(this); + } + + init() { + // --- Stage Dimensions for positioning --- + const length = 40; + const naveWidth = 12; + const stageWidth = naveWidth - 1; + const stageHeight = 1.5; + const stageDepth = 5; + + const torchPositions = [ + new THREE.Vector3(-stageWidth / 2, stageHeight, -length / 2 + 0.5), + new THREE.Vector3(stageWidth / 2, stageHeight, -length / 2 + 0.5), + new THREE.Vector3(-stageWidth / 2, stageHeight, -length / 2 + stageDepth - 0.5), + new THREE.Vector3(stageWidth / 2, stageHeight, -length / 2 + stageDepth - 0.5), + ]; + + torchPositions.forEach(pos => { + const torch = this.createTorch(pos); + this.torches.push(torch); + state.scene.add(torch.group); + }); + } + + createTorch(position) { + const torchGroup = new THREE.Group(); + torchGroup.position.copy(position); + + // --- Torch Holder --- + const holderMaterial = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.6, metalness: 0.5 }); + const holderGeo = new THREE.CylinderGeometry(0.1, 0.15, 1.0, 12); + const holderMesh = new THREE.Mesh(holderGeo, holderMaterial); + holderMesh.position.y = 0.5; + holderMesh.castShadow = true; + holderMesh.receiveShadow = true; + torchGroup.add(holderMesh); + + // --- Point Light --- + const pointLight = new THREE.PointLight(0xffaa44, 2.5, 8); + pointLight.position.y = 1.2; + pointLight.castShadow = true; + pointLight.shadow.mapSize.width = 128; + pointLight.shadow.mapSize.height = 128; + torchGroup.add(pointLight); + + // --- Particle System for Fire --- + const particleCount = 50; + const particles = new THREE.BufferGeometry(); + const positions = []; + const particleData = []; + + const sparkTexture = state.loader.load(sparkTextureUrl); + const particleMaterial = new THREE.PointsMaterial({ + map: sparkTexture, + color: 0xffaa00, + size: 0.5, + blending: THREE.AdditiveBlending, + transparent: true, + depthWrite: false, + }); + + for (let i = 0; i < particleCount; i++) { + positions.push(0, 1, 0); + particleData.push({ + velocity: new THREE.Vector3((Math.random() - 0.5) * 0.2, Math.random() * 1.5, (Math.random() - 0.5) * 0.2), + life: Math.random() * 1.0, + }); + } + particles.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); + const particleSystem = new THREE.Points(particles, particleMaterial); + torchGroup.add(particleSystem); + + return { group: torchGroup, light: pointLight, particles: particleSystem, particleData: particleData }; + } + + update(deltaTime) { + this.torches.forEach(torch => { + // --- Animate Particles --- + const positions = torch.particles.geometry.attributes.position.array; + for (let i = 0; i < torch.particleData.length; i++) { + const data = torch.particleData[i]; + data.life -= deltaTime; + + if (data.life <= 0) { + // Reset particle + positions[i * 3] = 0; + positions[i * 3 + 1] = 1; + positions[i * 3 + 2] = 0; + data.life = Math.random() * 1.0; + } else { + // Update position + positions[i * 3] += data.velocity.x * deltaTime; + positions[i * 3 + 1] += data.velocity.y * deltaTime; + positions[i * 3 + 2] += data.velocity.z * deltaTime; + } + } + torch.particles.geometry.attributes.position.needsUpdate = true; + + // --- Flicker Light --- + const flicker = Math.random() * 0.5; + torch.light.intensity = 2.0 + flicker; + }); + } +} + +new StageTorches(); \ No newline at end of file diff --git a/party-cathedral/textures/spark.png b/party-cathedral/textures/spark.png new file mode 100644 index 0000000000000000000000000000000000000000..828a67fe2c34bd89a9427fa5d869d59a19f1d2f9 GIT binary patch literal 950 zcmV;n14;aeP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2k8qH7AFG1=z84%00R0+L_t(o!|j(%NEJ~O$A9G0&Tz+ z;4rW~sv$DsxmutJP+CU4XItAn+e-bepjbo#7l9%m6Yi>K!@qL-H{1+wzW}Pyb`F_dkr82q^e7xno7aY zPRxeNxH(V`>;Q&LPK12+r*8M0Us0CI78-PY&YmTgHNKS-1t3y>)oJ9Pjl>`d;_){Qn4jOu;+jQJb zznp}o3x} zy})$b2S5*icruE7l12=UPbXwHL<~;mwhx$&+ggZjcv~lEO5Xl}s!O2Ol!f84)Trzm zO^RnDaUczJ7HlH&Z0jI!9w^f?-T{5UdSD#5U^?!ZLe2wSz8N3tCHwc>Ba{EqTj6EGdu0bXblV?g`gnhEpq02r8i;rzGw Y3f~_53j_q>>Hq)$07*qoM6N<$f>Q;U@Bjb+ literal 0 HcmV?d00001