Compare commits

..

No commits in common. "37f0d30fee6862c5a2f829230e514d993d9e0386" and "383ba8baf190154bae771d43d47e9e966d19e8b9" have entirely different histories.

13 changed files with 25 additions and 529 deletions

View File

@ -33,6 +33,17 @@ export function init() {
// 6. Initialize all visual effects via the manager // 6. Initialize all visual effects via the manager
state.effectsManager = new EffectsManager(state.scene); state.effectsManager = new EffectsManager(state.scene);
// --- 8. Debug Visualization Helpers ---
// Visual aids for the light source positions
if (state.debugLight && THREE.PointLightHelper) {
const screenHelper = new THREE.PointLightHelper(state.screenLight, 0.1, 0xff0000); // Red for screen
state.scene.add(screenHelper);
// Lamp Helper will now work since lampLight is added to the scene
const lampHelperPoint = new THREE.PointLightHelper(state.lampLightPoint, 0.1, 0x00ff00); // Green for lamp
state.scene.add(lampHelperPoint);
}
// 9. Event Listeners // 9. Event Listeners
window.addEventListener('resize', onWindowResize, false); window.addEventListener('resize', onWindowResize, false);

View File

@ -1,181 +0,0 @@
import * as THREE from 'three';
import { state } from '../state.js';
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';
const dancerTextureUrls = [
'/textures/dancer1.png',
];
// --- Scene dimensions for positioning ---
const stageHeight = 1.5;
const stageDepth = 5;
const length = 40;
// --- Billboard Properties ---
const dancerHeight = 2.5;
const dancerWidth = 2.5;
export class Dancers extends SceneFeature {
constructor() {
super();
this.dancers = [];
sceneFeatureManager.register(this);
}
async init() {
const processTexture = (texture) => {
const image = texture.image;
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
const keyPixelData = context.getImageData(0, 0, 1, 1).data;
const keyColor = { r: keyPixelData[0], g: keyPixelData[1], b: keyPixelData[2] };
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const threshold = 20;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i + 1], b = data[i + 2];
const distance = Math.sqrt(Math.pow(r - keyColor.r, 2) + Math.pow(g - keyColor.g, 2) + Math.pow(b - keyColor.b, 2));
if (distance < threshold) data[i + 3] = 0;
}
context.putImageData(imageData, 0, 0);
return new THREE.CanvasTexture(canvas);
};
const materials = await Promise.all(dancerTextureUrls.map(async (url) => {
const texture = await state.loader.loadAsync(url);
const processedTexture = processTexture(texture);
// Configure texture for a 2x2 sprite sheet
processedTexture.repeat.set(0.5, 0.5);
return new THREE.MeshStandardMaterial({
map: processedTexture,
side: THREE.DoubleSide,
alphaTest: 0.5,
roughness: 0.7,
metalness: 0.1,
});
}));
const createDancers = () => {
const geometry = new THREE.PlaneGeometry(dancerWidth, dancerHeight);
const dancerPositions = [
new THREE.Vector3(-4, stageHeight + dancerHeight / 2, -length / 2 + stageDepth / 2 - 2),
new THREE.Vector3(4.5, stageHeight + dancerHeight / 2, -length / 2 + stageDepth / 2 - 1.8),
];
dancerPositions.forEach((pos, index) => {
const material = materials[index % materials.length];
const dancer = new THREE.Mesh(geometry, material);
dancer.position.copy(pos);
state.scene.add(dancer);
this.dancers.push({
mesh: dancer,
baseY: pos.y,
// --- Movement State ---
state: 'WAITING',
targetPosition: pos.clone(),
waitStartTime: 0,
waitTime: 1 + Math.random() * 2, // Wait 1-3 seconds
// --- Animation State ---
currentFrame: Math.floor(Math.random() * 4), // Start on a random frame
isMirrored: false,
canChangePose: true, // Flag to ensure pose changes only once per beat
// --- Jumping State ---
isJumping: false,
jumpStartTime: 0,
});
});
};
createDancers();
}
update(deltaTime) {
if (this.dancers.length === 0) return;
const cameraPosition = new THREE.Vector3();
state.camera.getWorldPosition(cameraPosition);
const time = state.clock.getElapsedTime();
const jumpDuration = 0.5;
const jumpHeight = 2.0;
const moveSpeed = 2.0;
const movementArea = { x: 10, z: 4, centerZ: -length / 2 + stageDepth / 2 };
this.dancers.forEach(dancerObj => {
const { mesh } = dancerObj;
mesh.lookAt(cameraPosition.x, mesh.position.y, cameraPosition.z);
// --- Point-to-Point Movement Logic ---
if (dancerObj.state === 'WAITING') {
if (time > dancerObj.waitStartTime + dancerObj.waitTime) {
// Time to find a new spot
const newTarget = new THREE.Vector3(
(Math.random() - 0.5) * movementArea.x,
dancerObj.baseY,
movementArea.centerZ + (Math.random() - 0.5) * movementArea.z
);
dancerObj.targetPosition = newTarget;
dancerObj.state = 'MOVING';
}
} else if (dancerObj.state === 'MOVING') {
const distance = mesh.position.distanceTo(dancerObj.targetPosition);
if (distance > 0.1) {
const direction = dancerObj.targetPosition.clone().sub(mesh.position).normalize();
mesh.position.add(direction.multiplyScalar(moveSpeed * deltaTime));
} else {
// Arrived at destination
dancerObj.state = 'WAITING';
dancerObj.waitStartTime = time;
dancerObj.waitTime = 1 + Math.random() * 2; // Set new wait time
}
}
// --- Spritesheet Animation ---
if (state.music) {
if (state.music.beatIntensity > 0.8 && dancerObj.canChangePose) {
// On the beat, select a new random frame and mirroring state
dancerObj.currentFrame = Math.floor(Math.random() * 4); // Select a random frame on the beat
dancerObj.isMirrored = Math.random() < 0.5;
const frameX = dancerObj.currentFrame % 2;
const frameY = Math.floor(dancerObj.currentFrame / 2);
// Adjust repeat and offset for mirroring
mesh.material.map.repeat.x = dancerObj.isMirrored ? -0.5 : 0.5;
mesh.material.map.offset.x = dancerObj.isMirrored ? (frameX * 0.5) + 0.5 : frameX * 0.5;
// The Y offset is inverted because UV coordinates start from the bottom-left
mesh.material.map.offset.y = (1 - frameY) * 0.5;
dancerObj.canChangePose = false; // Prevent changing again on this same beat
} else if (state.music.beatIntensity < 0.2) {
dancerObj.canChangePose = true; // Reset the flag when the beat is over
}
}
// --- Jumping Logic ---
if (dancerObj.isJumping) {
const jumpProgress = (time - dancerObj.jumpStartTime) / jumpDuration;
if (jumpProgress < 1.0) {
mesh.position.y = dancerObj.baseY + Math.sin(jumpProgress * Math.PI) * jumpHeight;
} else {
dancerObj.isJumping = false;
mesh.position.y = dancerObj.baseY;
}
} else {
if (state.music && state.music.beatIntensity > 0.8 && Math.random() < 0.2) {
dancerObj.isJumping = true;
dancerObj.jumpStartTime = time;
}
}
});
}
}
new Dancers();

View File

@ -61,15 +61,6 @@ export class LightBall extends SceneFeature {
mesh.position.y = 10 + Math.cos(time * driftSpeed * 1.3 + offset) * naveHeight/2 * 0.6; mesh.position.y = 10 + Math.cos(time * driftSpeed * 1.3 + offset) * naveHeight/2 * 0.6;
mesh.position.z = Math.cos(time * driftSpeed * 0.7 + offset) * length/2 * 0.8; mesh.position.z = Math.cos(time * driftSpeed * 0.7 + offset) * length/2 * 0.8;
light.position.copy(mesh.position); light.position.copy(mesh.position);
// --- Music Visualization ---
if (state.music) {
const baseIntensity = 4.0;
light.intensity = baseIntensity + state.music.beatIntensity * 3.0;
const baseScale = 1.0;
mesh.scale.setScalar(baseScale + state.music.beatIntensity * 0.5);
}
}); });
} }
} }

View File

@ -103,8 +103,6 @@ export class MedievalMusicians extends SceneFeature {
jumpStartPos: null, jumpStartPos: null,
jumpEndPos: null, jumpEndPos: null,
jumpProgress: 0, jumpProgress: 0,
isMirrored: false,
canChangePose: true,
// --- State for jumping in place --- // --- State for jumping in place ---
isJumping: false, isJumping: false,
@ -140,18 +138,6 @@ export class MedievalMusicians extends SceneFeature {
// We only want to rotate on the Y axis to keep them upright // We only want to rotate on the Y axis to keep them upright
mesh.lookAt(cameraPosition.x, mesh.position.y, cameraPosition.z); mesh.lookAt(cameraPosition.x, mesh.position.y, cameraPosition.z);
// --- Mirroring on Beat ---
if (state.music) {
if (state.music.beatIntensity > 0.8 && musicianObj.canChangePose) {
musicianObj.isMirrored = Math.random() < 0.5;
mesh.material.map.repeat.x = musicianObj.isMirrored ? -1 : 1;
mesh.material.map.offset.x = musicianObj.isMirrored ? 1 : 0;
musicianObj.canChangePose = false;
} else if (state.music.beatIntensity < 0.2) {
musicianObj.canChangePose = true;
}
}
// --- Main State Machine --- // --- Main State Machine ---
const area = musicianObj.currentPlane === 'stage' ? stageArea : floorArea; const area = musicianObj.currentPlane === 'stage' ? stageArea : floorArea;
const otherArea = musicianObj.currentPlane === 'stage' ? floorArea : stageArea; const otherArea = musicianObj.currentPlane === 'stage' ? floorArea : stageArea;
@ -238,12 +224,7 @@ export class MedievalMusicians extends SceneFeature {
mesh.position.y = area.y + musicianHeight / 2; mesh.position.y = area.y + musicianHeight / 2;
} }
} else { } else {
let currentJumpChance = jumpChance * deltaTime; // Base chance over time if (Math.random() < jumpChance && musicianObj.state !== 'JUMPING_PLANE' && musicianObj.state !== 'PREPARING_JUMP') {
if (state.music && state.music.beatIntensity > 0.8) {
currentJumpChance = 0.1; // High, fixed chance on the beat
}
if (Math.random() < currentJumpChance && musicianObj.state !== 'JUMPING_PLANE' && musicianObj.state !== 'PREPARING_JUMP') {
musicianObj.isJumping = true; musicianObj.isJumping = true;
musicianObj.jumpHeight = jumpHeight + Math.random() * jumpVariance; musicianObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
musicianObj.jumpStartTime = time; musicianObj.jumpStartTime = time;

View File

@ -1,39 +0,0 @@
import { state } from '../state.js';
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';
export class MusicVisualizer extends SceneFeature {
constructor() {
super();
sceneFeatureManager.register(this);
}
init() {
// Initialize music state
state.music = {
bpm: 120,
beatDuration: 60 / 120,
measureDuration: (60 / 120) * 4,
beatIntensity: 0,
measurePulse: 0,
};
}
update(deltaTime) {
if (!state.music) return;
const time = state.clock.getElapsedTime();
// --- Calculate Beat Intensity (pulses every beat) ---
// This creates a sharp attack and slower decay (0 -> 1 -> 0)
const beatProgress = (time % state.music.beatDuration) / state.music.beatDuration;
state.music.beatIntensity = Math.pow(1.0 - beatProgress, 2);
// --- Calculate Measure Pulse (spikes every 4 beats) ---
// This creates a very sharp spike for the torch flame effect
const measureProgress = (time % state.music.measureDuration) / state.music.measureDuration;
state.music.measurePulse = measureProgress < 0.2 ? Math.sin(measureProgress * Math.PI * 5) : 0;
}
}
new MusicVisualizer();

View File

@ -80,8 +80,6 @@ export class PartyGuests extends SceneFeature {
targetPosition: pos.clone(), targetPosition: pos.clone(),
waitStartTime: 0, waitStartTime: 0,
waitTime: 3 + Math.random() * 4, // Wait longer: 3-7 seconds waitTime: 3 + Math.random() * 4, // Wait longer: 3-7 seconds
isMirrored: false,
canChangePose: true,
isJumping: false, isJumping: false,
jumpStartTime: 0, jumpStartTime: 0,
}); });
@ -99,7 +97,7 @@ export class PartyGuests extends SceneFeature {
const time = state.clock.getElapsedTime(); const time = state.clock.getElapsedTime();
const moveSpeed = 1.0; // Move slower const moveSpeed = 1.0; // Move slower
const movementArea = { x: 10, z: 30, y: 0, centerZ: 5 }; const movementArea = { x: 10, z: 30, y: 0, centerZ: 0 };
const jumpChance = 0.05; // Jump way more const jumpChance = 0.05; // Jump way more
const jumpDuration = 0.5; const jumpDuration = 0.5;
const jumpHeight = 0.1; const jumpHeight = 0.1;
@ -109,18 +107,6 @@ export class PartyGuests extends SceneFeature {
const { mesh } = guestObj; const { mesh } = guestObj;
mesh.lookAt(cameraPosition.x, mesh.position.y, cameraPosition.z); mesh.lookAt(cameraPosition.x, mesh.position.y, cameraPosition.z);
// --- Mirroring on Beat ---
if (state.music) {
if (state.music.beatIntensity > 0.8 && guestObj.canChangePose) {
guestObj.isMirrored = Math.random() < 0.5;
mesh.material.map.repeat.x = guestObj.isMirrored ? -1 : 1;
mesh.material.map.offset.x = guestObj.isMirrored ? 1 : 0;
guestObj.canChangePose = false;
} else if (state.music.beatIntensity < 0.2) {
guestObj.canChangePose = true;
}
}
if (guestObj.state === 'WAITING') { if (guestObj.state === 'WAITING') {
if (time > guestObj.waitStartTime + guestObj.waitTime) { if (time > guestObj.waitStartTime + guestObj.waitTime) {
const newTarget = new THREE.Vector3( const newTarget = new THREE.Vector3(
@ -153,12 +139,7 @@ export class PartyGuests extends SceneFeature {
mesh.position.y = movementArea.y + guestHeight / 2; mesh.position.y = movementArea.y + guestHeight / 2;
} }
} else { } else {
let currentJumpChance = jumpChance * deltaTime; // Base chance over time if (Math.random() < jumpChance) {
if (state.music && state.music.beatIntensity > 0.8) {
currentJumpChance = 0.1; // High, fixed chance on the beat
}
if (Math.random() < currentJumpChance) {
guestObj.isJumping = true; guestObj.isJumping = true;
guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance; guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
guestObj.jumpStartTime = time; guestObj.jumpStartTime = time;

View File

@ -10,11 +10,6 @@ import { Stage } from './stage.js';
import { MedievalMusicians } from './medieval-musicians.js'; import { MedievalMusicians } from './medieval-musicians.js';
import { PartyGuests } from './party-guests.js'; import { PartyGuests } from './party-guests.js';
import { StageTorches } from './stage-torches.js'; import { StageTorches } from './stage-torches.js';
import { Dancers } from './dancers.js';
import { MusicVisualizer } from './music-visualizer.js';
import { RoseWindowLight } from './rose-window-light.js';
import { RoseWindowLightshafts } from './rose-window-lightshafts.js';
// Scene Features ^^^ // Scene Features ^^^
// --- Scene Modeling Function --- // --- Scene Modeling Function ---
@ -39,18 +34,11 @@ export function createSceneObjects() {
state.scene.add(floor); state.scene.add(floor);
// 3. Lighting (Minimal and focused) // 3. Lighting (Minimal and focused)
const ambientLight = new THREE.AmbientLight(0x606060, 0.2); // Increased ambient light for a larger space const ambientLight = new THREE.AmbientLight(0x606060, 0.1); // Increased ambient light for a larger space
state.scene.add(ambientLight); state.scene.add(ambientLight);
// Add a HemisphereLight for more natural, general illumination in a large space. // Add a HemisphereLight for more natural, general illumination in a large space.
const hemisphereLight = new THREE.HemisphereLight(0xffddcc, 0x444455, 0.5); const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.2);
// Visual aids for the light source positions
if (state.debugLight && THREE.HemisphereLightHelper) {
// Lamp Helper will now work since lampLight is added to the scene
const hemisphereLightHelper = new THREE.HemisphereLightHelper(hemisphereLight, 0.1, 0x00ff00); // Green for lamp
state.scene.add(hemisphereLightHelper);
}
state.scene.add(hemisphereLight); state.scene.add(hemisphereLight);
} }

View File

@ -1,66 +0,0 @@
import * as THREE from 'three';
import { state } from '../state.js';
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';
export class RoseWindowLight extends SceneFeature {
constructor() {
super();
this.spotlight = null;
this.helper = null;
sceneFeatureManager.register(this);
}
init() {
// --- Dimensions for positioning ---
const length = 40;
const naveHeight = 15;
const stageDepth = 5;
// --- Create the spotlight ---
this.spotlight = new THREE.SpotLight(0xffffff, 100.0); // White light, high intensity
this.spotlight.position.set(0, naveHeight, -length / 2 + 10); // Position it at the rose window
this.spotlight.angle = Math.PI / 9; // A reasonably focused beam
this.spotlight.penumbra = 0.3; // Soft edges
this.spotlight.decay = 0.7;
this.spotlight.distance = 30;
this.spotlight.castShadow = false;
this.spotlight.shadow.mapSize.width = 1024;
this.spotlight.shadow.mapSize.height = 1024;
this.spotlight.shadow.camera.near = 1;
this.spotlight.shadow.camera.far = 30;
this.spotlight.shadow.focus = 1;
// --- Create a target for the spotlight to aim at ---
const targetObject = new THREE.Object3D();
targetObject.position.set(0, 0, -length / 2 + stageDepth); // Aim at the center of the stage
state.scene.add(targetObject);
this.spotlight.target = targetObject;
state.scene.add(this.spotlight);
// --- Add a debug helper ---
if (state.debugLight) {
this.helper = new THREE.SpotLightHelper(this.spotlight);
state.scene.add(this.helper);
}
}
update(deltaTime) {
if (!this.spotlight) return;
// Make the light pulse with the music
if (state.music) {
const baseIntensity = 4.0;
this.spotlight.intensity = baseIntensity + state.music.beatIntensity * 1.0;
}
// Update the helper if it exists
if (this.helper) {
this.helper.update();
}
}
}
new RoseWindowLight();

View File

@ -1,149 +0,0 @@
import * as THREE from 'three';
import { state } from '../state.js';
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';
export class RoseWindowLightshafts extends SceneFeature {
constructor() {
super();
this.shafts = [];
sceneFeatureManager.register(this);
}
init() {
// --- Dimensions for positioning ---
const length = 40;
const naveWidth = 12;
const naveHeight = 15;
const stageDepth = 5;
const stageWidth = naveWidth - 1;
const roseWindowRadius = naveWidth / 2 - 2;
const roseWindowCenter = new THREE.Vector3(0, naveHeight - 2, -length / 2 + 0.1);
// --- Procedural Noise Texture for Light Shafts ---
const createNoiseTexture = () => {
const width = 64;
const height = 512;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
const imageData = context.createImageData(width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// Create vertical streaks of noise
const y = Math.floor((i / 4) / width);
const noise = Math.pow(Math.random(), 2.5) * (1 - y / height) * 255;
data[i] = noise; // R
data[i + 1] = noise; // G
data[i + 2] = noise; // B
data[i + 3] = 255; // A
}
context.putImageData(imageData, 0, 0);
return new THREE.CanvasTexture(canvas);
};
const texture = createNoiseTexture();
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
const baseMaterial = new THREE.MeshBasicMaterial({
map: texture,
blending: THREE.AdditiveBlending,
transparent: true,
depthWrite: false,
opacity: 0.3,
color: 0x88aaff, // Give the light a cool blueish tint
});
// --- Create multiple thin light shafts ---
const numShafts = 12;
for (let i = 0; i < numShafts; i++) {
const material = baseMaterial.clone(); // Each shaft needs its own material for individual opacity
const startAngle = Math.random() * Math.PI * 2;
const startRadius = Math.random() * roseWindowRadius;
const startPoint = new THREE.Vector3(
roseWindowCenter.x + Math.cos(startAngle) * startRadius,
roseWindowCenter.y + Math.sin(startAngle) * startRadius,
roseWindowCenter.z
);
// Define a linear path on the floor for the beam to travel
const floorStartPoint = new THREE.Vector3(
(Math.random() - 0.5) * stageWidth * 1.0,
0,
-length / 2 + Math.random() * 10 + 0
);
const floorEndPoint = new THREE.Vector3(
(Math.random() - 0.5) * stageWidth * 1.0,
0,
-length / 2 + Math.random() * 10 + 3
);
const distance = startPoint.distanceTo(floorStartPoint);
const geometry = new THREE.CylinderGeometry(0.1, 0.5 + Math.random() * 0.5, distance, 16, 1, true);
const lightShaft = new THREE.Mesh(geometry, material);
state.scene.add(lightShaft);
this.shafts.push({
mesh: lightShaft,
startPoint: startPoint, // The stationary point in the window
endPoint: floorStartPoint.clone(), // The current position of the beam on the floor
floorStartPoint: floorStartPoint, // The start of the sweep path
floorEndPoint: floorEndPoint, // The end of the sweep path
moveSpeed: 0.5 + Math.random() * 1.5, // Each shaft has a different speed
// No 'state' needed anymore
});
}
}
update(deltaTime) {
const baseOpacity = 0.1;
this.shafts.forEach(shaft => {
const { mesh, startPoint, endPoint, floorStartPoint, floorEndPoint, moveSpeed } = shaft;
// Animate texture for dust motes
mesh.material.map.offset.y -= deltaTime * 0.001;
// --- Movement Logic ---
const pathDirection = floorEndPoint.clone().sub(floorStartPoint).normalize();
const pathLength = floorStartPoint.distanceTo(floorEndPoint);
// Move the endpoint along its path
endPoint.add(pathDirection.clone().multiplyScalar(moveSpeed * deltaTime));
const currentDistance = floorStartPoint.distanceTo(endPoint);
if (currentDistance >= pathLength) {
// Reached the end, reset to the start
endPoint.copy(floorStartPoint);
}
// --- Opacity based on Progress ---
const progress = Math.min(currentDistance / pathLength, 1.0);
// Use a sine curve to fade in at the start and out at the end
const fadeOpacity = Math.sin(progress * Math.PI) * baseOpacity;
// --- Update Mesh Position and Orientation ---
const distance = startPoint.distanceTo(endPoint);
mesh.scale.y = distance;
mesh.position.lerpVectors(startPoint, endPoint, 0.5);
const quaternion = new THREE.Quaternion();
const cylinderUp = new THREE.Vector3(0, 1, 0);
const direction = new THREE.Vector3().subVectors(endPoint, startPoint).normalize();
quaternion.setFromUnitVectors(cylinderUp, direction);
mesh.quaternion.copy(quaternion);
// --- Music Visualization ---
const beatPulse = state.music ? state.music.beatIntensity * 0.05 : 0;
mesh.material.opacity = fadeOpacity + beatPulse;
});
}
}
new RoseWindowLightshafts();

View File

@ -4,8 +4,6 @@ import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js'; import sceneFeatureManager from './SceneFeatureManager.js';
import sparkTextureUrl from '/textures/spark.png'; import sparkTextureUrl from '/textures/spark.png';
const lightPositionBaseY = 1.2;
export class StageTorches extends SceneFeature { export class StageTorches extends SceneFeature {
constructor() { constructor() {
super(); super();
@ -50,7 +48,7 @@ export class StageTorches extends SceneFeature {
// --- Point Light --- // --- Point Light ---
const pointLight = new THREE.PointLight(0xffaa44, 2.5, 8); const pointLight = new THREE.PointLight(0xffaa44, 2.5, 8);
pointLight.position.y = lightPositionBaseY; pointLight.position.y = 1.2;
pointLight.castShadow = true; pointLight.castShadow = true;
pointLight.shadow.mapSize.width = 128; pointLight.shadow.mapSize.width = 128;
pointLight.shadow.mapSize.height = 128; pointLight.shadow.mapSize.height = 128;
@ -88,46 +86,30 @@ export class StageTorches extends SceneFeature {
update(deltaTime) { update(deltaTime) {
this.torches.forEach(torch => { this.torches.forEach(torch => {
let measurePulse = 0;
if (state.music) {
measurePulse = state.music.measurePulse * 4.0; // Make flames jump higher
}
// --- Animate Particles --- // --- Animate Particles ---
const positions = torch.particles.geometry.attributes.position.array; const positions = torch.particles.geometry.attributes.position.array;
let averageY = 0;
for (let i = 0; i < torch.particleData.length; i++) { for (let i = 0; i < torch.particleData.length; i++) {
const data = torch.particleData[i]; const data = torch.particleData[i];
data.life -= deltaTime; data.life -= deltaTime;
const yVelocity = data.velocity.y;
if (data.life <= 0) { if (data.life <= 0) {
// Reset particle // Reset particle
positions[i * 3] = 0; positions[i * 3] = 0;
positions[i * 3 + 1] = 1; positions[i * 3 + 1] = 1;
positions[i * 3 + 2] = 0; positions[i * 3 + 2] = 0;
data.life = Math.random() * 1.0; data.life = Math.random() * 1.0;
data.velocity.y = Math.random() * 1.5 + measurePulse;
} else { } else {
// Update position // Update position
positions[i * 3] += data.velocity.x * deltaTime; positions[i * 3] += data.velocity.x * deltaTime;
positions[i * 3 + 1] += yVelocity * deltaTime; positions[i * 3 + 1] += data.velocity.y * deltaTime;
positions[i * 3 + 2] += data.velocity.z * deltaTime; positions[i * 3 + 2] += data.velocity.z * deltaTime;
} }
averageY += positions[i * 3 + 1];
} }
averageY = averageY / positions.length;
torch.particles.geometry.attributes.position.needsUpdate = true; torch.particles.geometry.attributes.position.needsUpdate = true;
// --- Flicker Light --- // --- Flicker Light ---
const baseIntensity = 2.0; const flicker = Math.random() * 0.5;
const flicker = Math.random() * 0.6; torch.light.intensity = 2.0 + flicker;
let beatPulse = 0;
if (state.music) {
beatPulse = state.music.beatIntensity * 1.5;
}
torch.light.intensity = baseIntensity + flicker + beatPulse;
torch.light.position.y = lightPositionBaseY + averageY;
}); });
} }
} }

View File

@ -235,13 +235,10 @@ export class StainedGlass extends SceneFeature {
update(deltaTime) { update(deltaTime) {
// Add a subtle pulsing glow to the windows // Add a subtle pulsing glow to the windows
let intensity = 0.15; // Base intensity const pulseSpeed = 0.5;
const minIntensity = 0.1; // Increased intensity for a stronger glow
// --- Music Visualization --- const maxIntensity = 0.2;
if (state.music) { const intensity = minIntensity + (maxIntensity - minIntensity) * (0.5 * (1 + Math.sin(state.clock.getElapsedTime() * pulseSpeed)));
const beatPulse = state.music.beatIntensity * 0.3;
intensity += beatPulse;
}
// To make the glow match the vertex colors, we set the emissive color to white // To make the glow match the vertex colors, we set the emissive color to white
// and modulate its intensity. The final glow color will be vertexColor * emissive * emissiveIntensity. // and modulate its intensity. The final glow color will be vertexColor * emissive * emissiveIntensity.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB