Feature: projection side-screens
This commit is contained in:
parent
1586df7e51
commit
0e9deaa161
@ -12,15 +12,13 @@ void main() {
|
||||
}
|
||||
`;
|
||||
|
||||
const ledCountX = 256;
|
||||
const ledCountY = ledCountX * (9 / 16);
|
||||
|
||||
const screenFragmentShader = `
|
||||
uniform sampler2D videoTexture;
|
||||
uniform float u_effect_type;
|
||||
uniform float u_effect_strength;
|
||||
uniform float u_time;
|
||||
uniform float u_opacity;
|
||||
uniform vec2 u_resolution;
|
||||
varying vec2 vUv;
|
||||
|
||||
float random(vec2 st) {
|
||||
@ -29,8 +27,8 @@ float random(vec2 st) {
|
||||
|
||||
void main() {
|
||||
// LED Grid Setup
|
||||
float ledCountX = ${ledCountX}.0;
|
||||
float ledCountY = ${ledCountY}.0;
|
||||
float ledCountX = u_resolution.x;
|
||||
float ledCountY = u_resolution.y;
|
||||
|
||||
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
||||
|
||||
@ -68,6 +66,7 @@ const visualizerFragmentShader = `
|
||||
uniform float u_time;
|
||||
uniform float u_beat;
|
||||
uniform float u_opacity;
|
||||
uniform vec2 u_resolution;
|
||||
varying vec2 vUv;
|
||||
|
||||
vec3 hsv2rgb(vec3 c) {
|
||||
@ -77,8 +76,8 @@ vec3 hsv2rgb(vec3 c) {
|
||||
}
|
||||
|
||||
void main() {
|
||||
float ledCountX = 128.0;
|
||||
float ledCountY = 72.0;
|
||||
float ledCountX = u_resolution.x;
|
||||
float ledCountY = u_resolution.y;
|
||||
|
||||
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
||||
|
||||
@ -115,7 +114,7 @@ export class ProjectionScreen extends SceneFeature {
|
||||
super();
|
||||
projectionScreenInstance = this;
|
||||
this.isVisualizerActive = false;
|
||||
this.originalPositions = null;
|
||||
this.screens = [];
|
||||
sceneFeatureManager.register(this);
|
||||
}
|
||||
|
||||
@ -149,8 +148,6 @@ export class ProjectionScreen extends SceneFeature {
|
||||
const geometry = new THREE.PlaneGeometry(width, height, 32, 32);
|
||||
|
||||
// Initial black material
|
||||
this.originalPositions = geometry.attributes.position.clone();
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({ color: 0x000000 });
|
||||
|
||||
this.mesh = new THREE.Mesh(geometry, material);
|
||||
@ -158,8 +155,31 @@ export class ProjectionScreen extends SceneFeature {
|
||||
this.mesh.position.set(0, 5.5, -20.5);
|
||||
state.scene.add(this.mesh);
|
||||
|
||||
this.screens.push({
|
||||
mesh: this.mesh,
|
||||
originalPositions: geometry.attributes.position.clone(),
|
||||
resolution: new THREE.Vector2(200, 200 * (9 / 16))
|
||||
});
|
||||
|
||||
// --- Create Side Screens ---
|
||||
const sideWidth = 4.5;
|
||||
const sideHeight = sideWidth * (9 / 16);
|
||||
const sideGeometry = new THREE.PlaneGeometry(sideWidth, sideHeight, 16, 16);
|
||||
|
||||
const createSideScreen = (x, y, z, rotY, resX) => {
|
||||
const resY = resX * (9 / 16);
|
||||
const mesh = new THREE.Mesh(sideGeometry, material); // Share initial material
|
||||
mesh.position.set(x, y, z);
|
||||
mesh.rotation.y = rotY;
|
||||
state.scene.add(mesh);
|
||||
this.screens.push({ mesh, originalPositions: sideGeometry.attributes.position.clone(), resolution: new THREE.Vector2(resX, resY) });
|
||||
};
|
||||
|
||||
createSideScreen(-9.5, 3.5, -16.6, 0.4, 100); // Left (Lower resolution)
|
||||
createSideScreen(9.5, 3.5, -16.5, -0.4, 100); // Right (Lower resolution)
|
||||
|
||||
state.tvScreen = this.mesh;
|
||||
state.tvScreen.visible = false;
|
||||
this.setAllVisible(false);
|
||||
|
||||
// --- Screen Light ---
|
||||
// A light that projects the screen's color/ambiance into the room
|
||||
@ -171,39 +191,57 @@ export class ProjectionScreen extends SceneFeature {
|
||||
state.scene.add(state.screenLight);
|
||||
}
|
||||
|
||||
setAllVisible(visible) {
|
||||
this.screens.forEach(s => s.mesh.visible = visible);
|
||||
}
|
||||
|
||||
applyMaterialToAll(baseMaterial) {
|
||||
this.screens.forEach(s => {
|
||||
if (baseMaterial instanceof THREE.ShaderMaterial && baseMaterial.uniforms.u_resolution) {
|
||||
const newMat = baseMaterial.clone();
|
||||
newMat.uniforms = THREE.UniformsUtils.clone(baseMaterial.uniforms);
|
||||
newMat.uniforms.u_resolution.value.copy(s.resolution);
|
||||
s.mesh.material = newMat;
|
||||
} else {
|
||||
s.mesh.material = baseMaterial;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update(deltaTime) {
|
||||
updateScreenEffect();
|
||||
|
||||
// Wobble Logic
|
||||
if (this.mesh && this.originalPositions) {
|
||||
const time = state.clock.getElapsedTime();
|
||||
const waveSpeed = 0.5;
|
||||
const waveFrequency = 1.2;
|
||||
const waveAmplitude = 0.3;
|
||||
// same as stage-curtain ^^^
|
||||
|
||||
const positions = this.mesh.geometry.attributes.position;
|
||||
|
||||
const time = state.clock.getElapsedTime();
|
||||
const waveSpeed = 0.5;
|
||||
const waveFrequency = 1.2;
|
||||
const waveAmplitude = 0.3;
|
||||
|
||||
this.screens.forEach(screenObj => {
|
||||
const positions = screenObj.mesh.geometry.attributes.position;
|
||||
const originalPositions = screenObj.originalPositions;
|
||||
|
||||
for (let i = 0; i < positions.count; i++) {
|
||||
const originalX = this.originalPositions.getX(i);
|
||||
const originalZ = this.originalPositions.getZ(i);
|
||||
|
||||
const originalX = originalPositions.getX(i);
|
||||
const originalZ = originalPositions.getZ(i);
|
||||
const zOffset = Math.sin(originalX * waveFrequency + time * waveSpeed) * waveAmplitude;
|
||||
|
||||
positions.setZ(i, originalZ + zOffset);
|
||||
}
|
||||
positions.needsUpdate = true;
|
||||
this.mesh.geometry.computeVertexNormals();
|
||||
}
|
||||
screenObj.mesh.geometry.computeVertexNormals();
|
||||
});
|
||||
|
||||
if (this.isVisualizerActive && state.tvScreen.material.uniforms) {
|
||||
state.tvScreen.material.uniforms.u_time.value = state.clock.getElapsedTime();
|
||||
if (this.isVisualizerActive) {
|
||||
const beat = (state.music && state.music.beatIntensity) ? state.music.beatIntensity : 0.0;
|
||||
state.tvScreen.material.uniforms.u_beat.value = beat;
|
||||
|
||||
// Sync light to beat
|
||||
state.screenLight.intensity = state.originalScreenIntensity * (0.5 + beat * 0.5);
|
||||
}
|
||||
|
||||
this.screens.forEach(s => {
|
||||
if (s.mesh.material && s.mesh.material.uniforms && s.mesh.material.uniforms.u_time) {
|
||||
s.mesh.material.uniforms.u_time.value = state.clock.getElapsedTime();
|
||||
s.mesh.material.uniforms.u_beat.value = beat;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onPartyStart() {
|
||||
@ -227,13 +265,13 @@ export class ProjectionScreen extends SceneFeature {
|
||||
|
||||
activateVisualizer() {
|
||||
this.isVisualizerActive = true;
|
||||
state.tvScreen.visible = true;
|
||||
state.tvScreenPowered = true;
|
||||
state.tvScreen.material = new THREE.ShaderMaterial({
|
||||
const material = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
u_time: { value: 0.0 },
|
||||
u_beat: { value: 0.0 },
|
||||
u_opacity: { value: state.screenOpacity }
|
||||
u_opacity: { value: state.screenOpacity },
|
||||
u_resolution: { value: new THREE.Vector2(1, 1) } // Placeholder, set in applyMaterialToAll
|
||||
},
|
||||
vertexShader: screenVertexShader,
|
||||
fragmentShader: visualizerFragmentShader,
|
||||
@ -241,6 +279,9 @@ export class ProjectionScreen extends SceneFeature {
|
||||
transparent: true,
|
||||
derivatives: true
|
||||
});
|
||||
|
||||
this.applyMaterialToAll(material);
|
||||
this.setAllVisible(true);
|
||||
state.screenLight.intensity = state.originalScreenIntensity;
|
||||
}
|
||||
|
||||
@ -296,14 +337,12 @@ export function showStandbyScreen() {
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
|
||||
if (state.tvScreen.material) state.tvScreen.material.dispose();
|
||||
|
||||
state.tvScreen.material = new THREE.MeshBasicMaterial({
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
state.tvScreen.visible = true;
|
||||
state.tvScreenPowered = true;
|
||||
if (projectionScreenInstance) projectionScreenInstance.applyMaterialToAll(material);
|
||||
if (projectionScreenInstance) projectionScreenInstance.setAllVisible(true);
|
||||
|
||||
state.screenLight.intensity = 0.5;
|
||||
}
|
||||
@ -311,20 +350,15 @@ export function showStandbyScreen() {
|
||||
export function turnTvScreenOn() {
|
||||
if (projectionScreenInstance) projectionScreenInstance.isVisualizerActive = false;
|
||||
|
||||
if (state.tvScreen.material) {
|
||||
state.tvScreen.material.dispose();
|
||||
}
|
||||
|
||||
state.tvScreen.visible = true;
|
||||
|
||||
// Switch to ShaderMaterial for video playback
|
||||
state.tvScreen.material = new THREE.ShaderMaterial({
|
||||
const material = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
videoTexture: { value: state.videoTexture },
|
||||
u_effect_type: { value: 0.0 },
|
||||
u_effect_strength: { value: 0.0 },
|
||||
u_time: { value: 0.0 },
|
||||
u_opacity: { value: state.screenOpacity !== undefined ? state.screenOpacity : 0.7 }
|
||||
u_opacity: { value: state.screenOpacity !== undefined ? state.screenOpacity : 0.7 },
|
||||
u_resolution: { value: new THREE.Vector2(1, 1) } // Placeholder
|
||||
},
|
||||
vertexShader: screenVertexShader,
|
||||
fragmentShader: screenFragmentShader,
|
||||
@ -333,7 +367,8 @@ export function turnTvScreenOn() {
|
||||
derivatives: true
|
||||
});
|
||||
|
||||
state.tvScreen.material.needsUpdate = true;
|
||||
if (projectionScreenInstance) projectionScreenInstance.applyMaterialToAll(material);
|
||||
if (projectionScreenInstance) projectionScreenInstance.setAllVisible(true);
|
||||
|
||||
if (!state.tvScreenPowered) {
|
||||
state.tvScreenPowered = true;
|
||||
@ -346,7 +381,8 @@ export function turnTvScreenOff() {
|
||||
state.tvScreenPowered = false;
|
||||
setScreenEffect(2, () => {
|
||||
// Revert to black material or hide
|
||||
state.tvScreen.material = new THREE.MeshBasicMaterial({ color: 0x000000 });
|
||||
const blackMat = new THREE.MeshBasicMaterial({ color: 0x000000 });
|
||||
if (projectionScreenInstance) projectionScreenInstance.applyMaterialToAll(blackMat);
|
||||
state.screenLight.intensity = 0.0;
|
||||
});
|
||||
}
|
||||
@ -365,34 +401,35 @@ export function setScreenEffect(effectType, onComplete) {
|
||||
export function updateScreenEffect() {
|
||||
if (!state.screenEffect || !state.screenEffect.active) return;
|
||||
|
||||
const material = state.tvScreen.material;
|
||||
if (!material || !material.uniforms) return;
|
||||
|
||||
// Update time uniform for noise
|
||||
material.uniforms.u_time.value = state.clock.getElapsedTime();
|
||||
|
||||
const elapsedTime = (state.clock.getElapsedTime() * 1000) - state.screenEffect.startTime;
|
||||
const progress = Math.min(elapsedTime / state.screenEffect.duration, 1.0);
|
||||
|
||||
// Simple linear fade for effect strength
|
||||
// Type 1 (On): 1.0 -> 0.0
|
||||
// Type 2 (Off): 0.0 -> 1.0
|
||||
let strength = progress;
|
||||
if (state.screenEffect.type === 1) {
|
||||
strength = 1.0 - progress;
|
||||
}
|
||||
|
||||
material.uniforms.u_effect_type.value = state.screenEffect.type;
|
||||
material.uniforms.u_effect_strength.value = strength;
|
||||
if (projectionScreenInstance) {
|
||||
projectionScreenInstance.screens.forEach(s => {
|
||||
const material = s.mesh.material;
|
||||
if (!material || !material.uniforms) return;
|
||||
|
||||
material.uniforms.u_time.value = state.clock.getElapsedTime();
|
||||
material.uniforms.u_effect_type.value = state.screenEffect.type;
|
||||
material.uniforms.u_effect_strength.value = strength;
|
||||
|
||||
if (progress >= 1.0) {
|
||||
material.uniforms.u_effect_type.value = 0.0;
|
||||
material.uniforms.u_effect_strength.value = 0.0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (progress >= 1.0) {
|
||||
state.screenEffect.active = false;
|
||||
if (state.screenEffect.onComplete) {
|
||||
state.screenEffect.onComplete();
|
||||
}
|
||||
// Reset effect uniforms
|
||||
material.uniforms.u_effect_type.value = 0.0;
|
||||
material.uniforms.u_effect_strength.value = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user