Feature: rose window lightshafts
This commit is contained in:
parent
1c8eb8534e
commit
c612b38574
@ -13,6 +13,8 @@ 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 Modeling Function ---
|
||||
|
||||
149
party-cathedral/src/scene/rose-window-lightshafts.js
Normal file
149
party-cathedral/src/scene/rose-window-lightshafts.js
Normal file
@ -0,0 +1,149 @@
|
||||
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 = 7;
|
||||
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.1;
|
||||
|
||||
// --- 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.25 : 0;
|
||||
mesh.material.opacity = fadeOpacity + beatPulse;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new RoseWindowLightshafts();
|
||||
Loading…
Reference in New Issue
Block a user