Compare commits
No commits in common. "887d3970ab77eafb136e022224a9f59ef33504f7" and "a6c8d9c8c631252c46403ff1e5de3ac6d47e4dc0" have entirely different histories.
887d3970ab
...
a6c8d9c8c6
@ -5,7 +5,6 @@ import sceneFeatureManager from './SceneFeatureManager.js';
|
|||||||
import { MediaStorage } from '../core/media-storage.js';
|
import { MediaStorage } from '../core/media-storage.js';
|
||||||
import { showStandbyScreen } from './projection-screen.js';
|
import { showStandbyScreen } from './projection-screen.js';
|
||||||
import { showToast } from '../core/ui-utils.js';
|
import { showToast } from '../core/ui-utils.js';
|
||||||
import { visualizerLibrary } from '../visualizers/index.js';
|
|
||||||
|
|
||||||
export class ConfigUI extends SceneFeature {
|
export class ConfigUI extends SceneFeature {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -111,54 +110,6 @@ export class ConfigUI extends SceneFeature {
|
|||||||
if (debugPanel) debugPanel.setVisibility(enabled);
|
if (debugPanel) debugPanel.setVisibility(enabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Visualizer Selection
|
|
||||||
const vizRow = document.createElement('div');
|
|
||||||
vizRow.style.display = 'flex';
|
|
||||||
vizRow.style.alignItems = 'center';
|
|
||||||
vizRow.style.justifyContent = 'space-between';
|
|
||||||
vizRow.style.marginTop = '10px';
|
|
||||||
|
|
||||||
const vizLabel = document.createElement('label');
|
|
||||||
vizLabel.innerText = 'Visualizer';
|
|
||||||
|
|
||||||
const vizSelect = document.createElement('select');
|
|
||||||
visualizerLibrary.forEach(viz => {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = viz.getName();
|
|
||||||
opt.innerText = viz.getName();
|
|
||||||
if (opt.value === state.config.visualizerType) opt.selected = true;
|
|
||||||
vizSelect.appendChild(opt);
|
|
||||||
});
|
|
||||||
vizSelect.onchange = (e) => {
|
|
||||||
state.config.visualizerType = e.target.value;
|
|
||||||
saveConfig();
|
|
||||||
};
|
|
||||||
vizRow.appendChild(vizLabel);
|
|
||||||
vizRow.appendChild(vizSelect);
|
|
||||||
rightContainer.appendChild(vizRow);
|
|
||||||
|
|
||||||
// Visualizer Seed
|
|
||||||
const seedRow = document.createElement('div');
|
|
||||||
seedRow.style.display = 'flex';
|
|
||||||
seedRow.style.alignItems = 'center';
|
|
||||||
seedRow.style.justifyContent = 'space-between';
|
|
||||||
|
|
||||||
const seedLabel = document.createElement('label');
|
|
||||||
seedLabel.innerText = 'Visualizer Seed';
|
|
||||||
|
|
||||||
const seedInput = document.createElement('input');
|
|
||||||
seedInput.type = 'number';
|
|
||||||
seedInput.step = '0.001';
|
|
||||||
seedInput.value = state.config.visualizerSeed || 0;
|
|
||||||
seedInput.style.width = '80px';
|
|
||||||
seedInput.onchange = (e) => {
|
|
||||||
state.config.visualizerSeed = parseFloat(e.target.value);
|
|
||||||
saveConfig();
|
|
||||||
};
|
|
||||||
seedRow.appendChild(seedLabel);
|
|
||||||
seedRow.appendChild(seedInput);
|
|
||||||
rightContainer.appendChild(seedRow);
|
|
||||||
|
|
||||||
// Blackout Toggle
|
// Blackout Toggle
|
||||||
createToggle('BLACKOUT', 'blackout');
|
createToggle('BLACKOUT', 'blackout');
|
||||||
|
|
||||||
@ -445,41 +396,6 @@ export class ConfigUI extends SceneFeature {
|
|||||||
statusContainer.appendChild(loadTapesBtn);
|
statusContainer.appendChild(loadTapesBtn);
|
||||||
state.loadTapeButton = loadTapesBtn;
|
state.loadTapeButton = loadTapesBtn;
|
||||||
|
|
||||||
// Load Slideshow Button
|
|
||||||
const loadSlideshowBtn = document.createElement('button');
|
|
||||||
loadSlideshowBtn.innerText = 'Load Slideshow';
|
|
||||||
Object.assign(loadSlideshowBtn.style, {
|
|
||||||
marginTop: '10px',
|
|
||||||
padding: '8px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor: '#ff9800',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
fontSize: '14px'
|
|
||||||
});
|
|
||||||
|
|
||||||
const slideshowInput = document.createElement('input');
|
|
||||||
slideshowInput.type = 'file';
|
|
||||||
slideshowInput.multiple = true;
|
|
||||||
slideshowInput.accept = 'image/*';
|
|
||||||
slideshowInput.style.display = 'none';
|
|
||||||
slideshowInput.onchange = (e) => {
|
|
||||||
const files = Array.from(e.target.files);
|
|
||||||
if (files.length > 0) {
|
|
||||||
if (state.imageUrls) state.imageUrls.forEach(url => URL.revokeObjectURL(url));
|
|
||||||
state.imageUrls = files.map(f => URL.createObjectURL(f));
|
|
||||||
state.imageFilenames = files.map(f => f.name);
|
|
||||||
state.isSlideshowLoaded = true;
|
|
||||||
this.updateStatus();
|
|
||||||
showToast(`Loaded ${files.length} images`, 'success');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.body.appendChild(slideshowInput);
|
|
||||||
loadSlideshowBtn.onclick = () => slideshowInput.click();
|
|
||||||
this.loadSlideshowBtn = loadSlideshowBtn;
|
|
||||||
statusContainer.appendChild(loadSlideshowBtn);
|
|
||||||
|
|
||||||
// Choose Song Button
|
// Choose Song Button
|
||||||
const chooseSongBtn = document.createElement('button');
|
const chooseSongBtn = document.createElement('button');
|
||||||
chooseSongBtn.innerText = 'Choose Song';
|
chooseSongBtn.innerText = 'Choose Song';
|
||||||
@ -548,12 +464,6 @@ export class ConfigUI extends SceneFeature {
|
|||||||
state.videoFilenames = [];
|
state.videoFilenames = [];
|
||||||
state.isVideoLoaded = false;
|
state.isVideoLoaded = false;
|
||||||
state.currentVideoIndex = -1;
|
state.currentVideoIndex = -1;
|
||||||
if (state.imageUrls) {
|
|
||||||
state.imageUrls.forEach(url => URL.revokeObjectURL(url));
|
|
||||||
}
|
|
||||||
state.imageUrls = [];
|
|
||||||
state.imageFilenames = [];
|
|
||||||
state.isSlideshowLoaded = false;
|
|
||||||
if (state.loadTapeButton) {
|
if (state.loadTapeButton) {
|
||||||
state.loadTapeButton.innerText = 'Load Tapes';
|
state.loadTapeButton.innerText = 'Load Tapes';
|
||||||
state.loadTapeButton.classList.remove('hidden');
|
state.loadTapeButton.classList.remove('hidden');
|
||||||
@ -588,9 +498,7 @@ export class ConfigUI extends SceneFeature {
|
|||||||
guestCount: 150,
|
guestCount: 150,
|
||||||
blackout: true,
|
blackout: true,
|
||||||
djHat: 'None',
|
djHat: 'None',
|
||||||
debugPanelEnabled: false,
|
debugPanelEnabled: false
|
||||||
visualizerType: 'Classic Wave',
|
|
||||||
visualizerSeed: Math.random() * 1000
|
|
||||||
};
|
};
|
||||||
for (const key in defaults) {
|
for (const key in defaults) {
|
||||||
state.config[key] = defaults[key];
|
state.config[key] = defaults[key];
|
||||||
@ -678,13 +586,8 @@ export class ConfigUI extends SceneFeature {
|
|||||||
state.loadTapeButton.style.backgroundColor = hasTapes ? green : orange;
|
state.loadTapeButton.style.backgroundColor = hasTapes ? green : orange;
|
||||||
state.loadTapeButton.innerText = hasTapes ? 'Change Tapes' : 'Load Tapes';
|
state.loadTapeButton.innerText = hasTapes ? 'Change Tapes' : 'Load Tapes';
|
||||||
}
|
}
|
||||||
if (this.loadSlideshowBtn) {
|
|
||||||
const hasImages = state.imageUrls && state.imageUrls.length > 0;
|
|
||||||
this.loadSlideshowBtn.style.backgroundColor = hasImages ? green : orange;
|
|
||||||
this.loadSlideshowBtn.innerText = hasImages ? 'Change Slideshow' : 'Load Slideshow';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Media List
|
// Update Tape List
|
||||||
this.tapeList.innerHTML = '';
|
this.tapeList.innerHTML = '';
|
||||||
if (state.videoUrls && state.videoUrls.length > 0) {
|
if (state.videoUrls && state.videoUrls.length > 0) {
|
||||||
state.videoUrls.forEach((url, index) => {
|
state.videoUrls.forEach((url, index) => {
|
||||||
@ -702,29 +605,9 @@ export class ConfigUI extends SceneFeature {
|
|||||||
}
|
}
|
||||||
this.tapeList.appendChild(li);
|
this.tapeList.appendChild(li);
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (state.imageUrls && state.imageUrls.length > 0) {
|
|
||||||
state.imageUrls.forEach((url, index) => {
|
|
||||||
const li = document.createElement('li');
|
|
||||||
let name = `Image ${index + 1}`;
|
|
||||||
if (state.imageFilenames && state.imageFilenames[index]) {
|
|
||||||
name = state.imageFilenames[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.length > 25) {
|
|
||||||
li.innerText = name.substring(0, 22) + '...';
|
|
||||||
li.title = name;
|
|
||||||
} else {
|
|
||||||
li.innerText = name;
|
|
||||||
}
|
|
||||||
this.tapeList.appendChild(li);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!state.videoUrls || state.videoUrls.length === 0) && (!state.imageUrls || state.imageUrls.length === 0)) {
|
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.innerText = '(No media loaded)';
|
li.innerText = '(No tapes loaded)';
|
||||||
this.tapeList.appendChild(li);
|
this.tapeList.appendChild(li);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import * as THREE from 'three';
|
|||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
import { SceneFeature } from './SceneFeature.js';
|
import { SceneFeature } from './SceneFeature.js';
|
||||||
import sceneFeatureManager from './SceneFeatureManager.js';
|
import sceneFeatureManager from './SceneFeatureManager.js';
|
||||||
import { visualizerLibrary } from '../visualizers/index.js';
|
|
||||||
|
|
||||||
// --- Shaders for Screen Effects ---
|
// --- Shaders for Screen Effects ---
|
||||||
const screenVertexShader = `
|
const screenVertexShader = `
|
||||||
@ -60,6 +59,68 @@ void main() {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const visualizerFragmentShader = `
|
||||||
|
uniform float u_time;
|
||||||
|
uniform float u_beat;
|
||||||
|
uniform float u_opacity;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform vec3 u_colors[16];
|
||||||
|
uniform int u_colorCount;
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
vec3 hsv2rgb(vec3 c) {
|
||||||
|
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||||
|
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||||
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float ledCountX = u_resolution.x;
|
||||||
|
float ledCountY = u_resolution.y;
|
||||||
|
|
||||||
|
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
||||||
|
|
||||||
|
vec2 gridDeriv = fwidth(gridUV);
|
||||||
|
float gridDensity = max(gridDeriv.x, gridDeriv.y);
|
||||||
|
float blurFactor = smoothstep(0.3, 0.8, gridDensity);
|
||||||
|
|
||||||
|
vec2 cell = fract(gridUV);
|
||||||
|
vec2 uv = mix((floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY), vUv, blurFactor);
|
||||||
|
|
||||||
|
float dist = distance(cell, vec2(0.5));
|
||||||
|
float edgeSoftness = clamp(gridDensity * 1.5, 0.0, 0.5);
|
||||||
|
float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, dist);
|
||||||
|
mask = mix(mask, 1.0, blurFactor);
|
||||||
|
|
||||||
|
float d = length(uv - 0.5);
|
||||||
|
float angle = atan(uv.y - 0.5, uv.x - 0.5);
|
||||||
|
|
||||||
|
float wave = sin(d * 20.0 - u_time * 2.0);
|
||||||
|
float beatWave = sin(angle * 5.0 + u_time) * u_beat;
|
||||||
|
|
||||||
|
float hue = fract(u_time * 0.1 + d * 0.2);
|
||||||
|
float val = 0.5 + 0.5 * sin(wave + beatWave);
|
||||||
|
|
||||||
|
vec3 finalColor;
|
||||||
|
if (u_colorCount > 0) {
|
||||||
|
float indexFloat = hue * float(u_colorCount);
|
||||||
|
int index = int(mod(indexFloat, float(u_colorCount)));
|
||||||
|
vec3 c = vec3(0.0);
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
if (i == index) {
|
||||||
|
c = u_colors[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalColor = c * val;
|
||||||
|
} else {
|
||||||
|
finalColor = hsv2rgb(vec3(hue, 0.8, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_FragColor = vec4(finalColor, mask * u_opacity);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
let projectionScreenInstance = null;
|
let projectionScreenInstance = null;
|
||||||
|
|
||||||
export class ProjectionScreen extends SceneFeature {
|
export class ProjectionScreen extends SceneFeature {
|
||||||
@ -67,11 +128,6 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
super();
|
super();
|
||||||
projectionScreenInstance = this;
|
projectionScreenInstance = this;
|
||||||
this.isVisualizerActive = false;
|
this.isVisualizerActive = false;
|
||||||
this.isSlideshowActive = false;
|
|
||||||
this.slideshowTimer = 0;
|
|
||||||
this.currentImageIndex = 0;
|
|
||||||
this.slideshowDuration = 5.0; // seconds per image
|
|
||||||
this.imageTextures = {};
|
|
||||||
this.screens = [];
|
this.screens = [];
|
||||||
this.blackoutLerp = 0;
|
this.blackoutLerp = 0;
|
||||||
this.colorBuffer = new Float32Array(16 * 3);
|
this.colorBuffer = new Float32Array(16 * 3);
|
||||||
@ -91,10 +147,6 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
state.imageUrls = state.imageUrls || [];
|
|
||||||
state.imageFilenames = state.imageFilenames || [];
|
|
||||||
state.isSlideshowLoaded = state.isSlideshowLoaded || false;
|
|
||||||
|
|
||||||
// --- Initialize State ---
|
// --- Initialize State ---
|
||||||
state.tvScreenPowered = false;
|
state.tvScreenPowered = false;
|
||||||
state.screenEffect = {
|
state.screenEffect = {
|
||||||
@ -195,18 +247,6 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
update(deltaTime) {
|
update(deltaTime) {
|
||||||
updateScreenEffect();
|
updateScreenEffect();
|
||||||
|
|
||||||
// --- Slideshow Cycling Logic ---
|
|
||||||
if (this.isSlideshowActive && state.imageUrls && state.imageUrls.length > 0) {
|
|
||||||
this.slideshowTimer += deltaTime;
|
|
||||||
if (this.slideshowTimer >= this.slideshowDuration) {
|
|
||||||
this.slideshowTimer = 0;
|
|
||||||
if (state.imageUrls.length > 1) {
|
|
||||||
this.currentImageIndex = (this.currentImageIndex + 1) % state.imageUrls.length;
|
|
||||||
this.updateSlideshowTexture();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Blackout Transition Logic ---
|
// --- Blackout Transition Logic ---
|
||||||
const targetBlackout = state.blackoutMode ? 1.0 : 0.0;
|
const targetBlackout = state.blackoutMode ? 1.0 : 0.0;
|
||||||
const enterBlackoutSpeed = 0.5; // slow light up
|
const enterBlackoutSpeed = 0.5; // slow light up
|
||||||
@ -272,13 +312,8 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
if (s.mesh.material.uniforms.u_colorCount) {
|
if (s.mesh.material.uniforms.u_colorCount) {
|
||||||
s.mesh.material.uniforms.u_colorCount.value = colorCount;
|
s.mesh.material.uniforms.u_colorCount.value = colorCount;
|
||||||
}
|
}
|
||||||
if (s.mesh.material.uniforms.u_seed) {
|
|
||||||
s.mesh.material.uniforms.u_seed.value = state.config.visualizerSeed || 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (this.isSlideshowActive) {
|
|
||||||
state.screenLight.intensity = state.originalScreenIntensity * dimFactor;
|
|
||||||
} else {
|
} else {
|
||||||
// Video Mode
|
// Video Mode
|
||||||
state.screenLight.intensity = state.originalScreenIntensity * dimFactor;
|
state.screenLight.intensity = state.originalScreenIntensity * dimFactor;
|
||||||
@ -303,13 +338,9 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
// Hide load button during playback
|
// Hide load button during playback
|
||||||
if (state.loadTapeButton) state.loadTapeButton.classList.add('hidden');
|
if (state.loadTapeButton) state.loadTapeButton.classList.add('hidden');
|
||||||
|
|
||||||
// Priority: Video > Slideshow > Visualizer
|
// If no video loaded, start visualizer
|
||||||
if (!state.isVideoLoaded) {
|
if (!state.isVideoLoaded) {
|
||||||
if (state.isSlideshowLoaded) {
|
this.activateVisualizer();
|
||||||
this.activateSlideshow();
|
|
||||||
} else {
|
|
||||||
this.activateVisualizer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,19 +351,11 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
if (this.isVisualizerActive) {
|
if (this.isVisualizerActive) {
|
||||||
this.deactivateVisualizer();
|
this.deactivateVisualizer();
|
||||||
}
|
}
|
||||||
if (this.isSlideshowActive) {
|
|
||||||
this.deactivateSlideshow();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activateVisualizer() {
|
activateVisualizer() {
|
||||||
this.isVisualizerActive = true;
|
this.isVisualizerActive = true;
|
||||||
state.tvScreenPowered = true;
|
state.tvScreenPowered = true;
|
||||||
|
|
||||||
const visualizerType = state.config.visualizerType || 'Classic Wave';
|
|
||||||
const visualizer = visualizerLibrary.find(v => v.getName() === visualizerType) || visualizerLibrary[0];
|
|
||||||
const fragShader = visualizer.getFragmentShader();
|
|
||||||
|
|
||||||
const material = new THREE.ShaderMaterial({
|
const material = new THREE.ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
u_time: { value: 0.0 },
|
u_time: { value: 0.0 },
|
||||||
@ -340,11 +363,10 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
u_opacity: { value: state.screenOpacity },
|
u_opacity: { value: state.screenOpacity },
|
||||||
u_resolution: { value: new THREE.Vector2(1, 1) }, // Placeholder, set in applyMaterialToAll
|
u_resolution: { value: new THREE.Vector2(1, 1) }, // Placeholder, set in applyMaterialToAll
|
||||||
u_colors: { value: this.colorBuffer },
|
u_colors: { value: this.colorBuffer },
|
||||||
u_colorCount: { value: 0 },
|
u_colorCount: { value: 0 }
|
||||||
u_seed: { value: state.config.visualizerSeed || 0 }
|
|
||||||
},
|
},
|
||||||
vertexShader: screenVertexShader,
|
vertexShader: screenVertexShader,
|
||||||
fragmentShader: fragShader,
|
fragmentShader: visualizerFragmentShader,
|
||||||
side: THREE.DoubleSide,
|
side: THREE.DoubleSide,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
derivatives: true
|
derivatives: true
|
||||||
@ -360,54 +382,6 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
turnTvScreenOff();
|
turnTvScreenOff();
|
||||||
}
|
}
|
||||||
|
|
||||||
activateSlideshow() {
|
|
||||||
this.isSlideshowActive = true;
|
|
||||||
this.isVisualizerActive = false;
|
|
||||||
this.currentImageIndex = 0;
|
|
||||||
this.slideshowTimer = 0;
|
|
||||||
state.tvScreenPowered = true;
|
|
||||||
|
|
||||||
const material = new THREE.ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
videoTexture: { value: null },
|
|
||||||
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_resolution: { value: new THREE.Vector2(1, 1) }
|
|
||||||
},
|
|
||||||
vertexShader: screenVertexShader,
|
|
||||||
fragmentShader: screenFragmentShader,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
derivatives: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.applyMaterialToAll(material);
|
|
||||||
this.updateSlideshowTexture();
|
|
||||||
this.setAllVisible(true);
|
|
||||||
setScreenEffect(1); // Power on effect
|
|
||||||
}
|
|
||||||
|
|
||||||
deactivateSlideshow() {
|
|
||||||
this.isSlideshowActive = false;
|
|
||||||
turnTvScreenOff();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSlideshowTexture() {
|
|
||||||
if (!state.imageUrls || state.imageUrls.length === 0) return;
|
|
||||||
const url = state.imageUrls[this.currentImageIndex];
|
|
||||||
if (!this.imageTextures[url]) {
|
|
||||||
this.imageTextures[url] = new THREE.TextureLoader().load(url);
|
|
||||||
this.imageTextures[url].colorSpace = THREE.SRGBColorSpace;
|
|
||||||
}
|
|
||||||
this.screens.forEach(s => {
|
|
||||||
if (s.mesh.material && s.mesh.material.uniforms && s.mesh.material.uniforms.videoTexture) {
|
|
||||||
s.mesh.material.uniforms.videoTexture.value = this.imageTextures[url];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
activateStandby() {
|
activateStandby() {
|
||||||
this.isVisualizerActive = false;
|
this.isVisualizerActive = false;
|
||||||
|
|
||||||
@ -533,10 +507,7 @@ export function showStandbyScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function turnTvScreenOn() {
|
export function turnTvScreenOn() {
|
||||||
if (projectionScreenInstance) {
|
if (projectionScreenInstance) projectionScreenInstance.isVisualizerActive = false;
|
||||||
projectionScreenInstance.isVisualizerActive = false;
|
|
||||||
projectionScreenInstance.isSlideshowActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to ShaderMaterial for video playback
|
// Switch to ShaderMaterial for video playback
|
||||||
const material = new THREE.ShaderMaterial({
|
const material = new THREE.ShaderMaterial({
|
||||||
|
|||||||
@ -16,9 +16,7 @@ export function initState() {
|
|||||||
guestCount: 150,
|
guestCount: 150,
|
||||||
blackout: false,
|
blackout: false,
|
||||||
djHat: 'None', // 'None', 'Santa', 'Top Hat'
|
djHat: 'None', // 'None', 'Santa', 'Top Hat'
|
||||||
debugPanelEnabled: false,
|
debugPanelEnabled: false
|
||||||
visualizerType: 'Classic Wave',
|
|
||||||
visualizerSeed: Math.random() * 1000
|
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem('partyConfig');
|
const saved = localStorage.getItem('partyConfig');
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
import { VisualizerInterface } from './VisualizerInterface.js';
|
|
||||||
|
|
||||||
const fragmentShader = `
|
|
||||||
uniform float u_time;
|
|
||||||
uniform float u_beat;
|
|
||||||
uniform float u_opacity;
|
|
||||||
uniform vec2 u_resolution;
|
|
||||||
uniform vec3 u_colors[16];
|
|
||||||
uniform int u_colorCount;
|
|
||||||
uniform float u_seed;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
vec3 hsv2rgb(vec3 c) {
|
|
||||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
|
||||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
|
||||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float ledCountX = u_resolution.x;
|
|
||||||
float ledCountY = u_resolution.y;
|
|
||||||
|
|
||||||
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
|
||||||
|
|
||||||
vec2 gridDeriv = fwidth(gridUV);
|
|
||||||
float gridDensity = max(gridDeriv.x, gridDeriv.y);
|
|
||||||
float blurFactor = smoothstep(0.3, 0.8, gridDensity);
|
|
||||||
|
|
||||||
vec2 cell = fract(gridUV);
|
|
||||||
vec2 uv = mix((floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY), vUv, blurFactor);
|
|
||||||
|
|
||||||
float dist = distance(cell, vec2(0.5));
|
|
||||||
float edgeSoftness = clamp(gridDensity * 1.5, 0.0, 0.5);
|
|
||||||
float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, dist);
|
|
||||||
mask = mix(mask, 1.0, blurFactor);
|
|
||||||
|
|
||||||
float d = length(uv - 0.5);
|
|
||||||
float angle = atan(uv.y - 0.5, uv.x - 0.5);
|
|
||||||
|
|
||||||
float wave = sin(d * 20.0 - u_time * 2.0 + u_seed);
|
|
||||||
float beatWave = sin(angle * 5.0 + u_time + u_seed * 0.1) * u_beat;
|
|
||||||
|
|
||||||
float hue = fract(u_time * 0.1 + d * 0.2 + u_seed * 0.01);
|
|
||||||
float val = 0.5 + 0.5 * sin(wave + beatWave);
|
|
||||||
|
|
||||||
vec3 finalColor;
|
|
||||||
if (u_colorCount > 0) {
|
|
||||||
float indexFloat = hue * float(u_colorCount);
|
|
||||||
int index = int(mod(indexFloat, float(u_colorCount)));
|
|
||||||
vec3 c = vec3(0.0);
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
if (i == index) {
|
|
||||||
c = u_colors[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finalColor = c * val;
|
|
||||||
} else {
|
|
||||||
finalColor = hsv2rgb(vec3(hue, 0.8, val));
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_FragColor = vec4(finalColor, mask * u_opacity);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class ClassicVisualizer extends VisualizerInterface {
|
|
||||||
getName() {
|
|
||||||
return "Classic Wave";
|
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentShader() {
|
|
||||||
return fragmentShader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
import { VisualizerInterface } from './VisualizerInterface.js';
|
|
||||||
|
|
||||||
const fragmentShader = `
|
|
||||||
uniform float u_time;
|
|
||||||
uniform float u_beat;
|
|
||||||
uniform float u_opacity;
|
|
||||||
uniform vec2 u_resolution;
|
|
||||||
uniform vec3 u_colors[16];
|
|
||||||
uniform int u_colorCount;
|
|
||||||
uniform float u_seed;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
float sdCircle(vec2 p, float r) {
|
|
||||||
return length(p) - r;
|
|
||||||
}
|
|
||||||
|
|
||||||
float sdBox(vec2 p, vec2 b) {
|
|
||||||
vec2 d = abs(p) - b;
|
|
||||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 rotate(vec2 p, float a) {
|
|
||||||
float s = sin(a);
|
|
||||||
float c = cos(a);
|
|
||||||
return mat2(c, -s, s, c) * p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// LED Grid Setup
|
|
||||||
float ledCountX = u_resolution.x;
|
|
||||||
float ledCountY = u_resolution.y;
|
|
||||||
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
|
||||||
vec2 gridDeriv = fwidth(gridUV);
|
|
||||||
float gridDensity = max(gridDeriv.x, gridDeriv.y);
|
|
||||||
float blurFactor = smoothstep(0.3, 0.8, gridDensity);
|
|
||||||
vec2 cell = fract(gridUV);
|
|
||||||
vec2 uv = mix((floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY), vUv, blurFactor);
|
|
||||||
|
|
||||||
float distMask = distance(cell, vec2(0.5));
|
|
||||||
float edgeSoftness = clamp(gridDensity * 1.5, 0.0, 0.5);
|
|
||||||
float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, distMask);
|
|
||||||
mask = mix(mask, 1.0, blurFactor);
|
|
||||||
|
|
||||||
// Aspect correction for shapes
|
|
||||||
vec2 p = (uv - 0.5);
|
|
||||||
p.x *= u_resolution.x / u_resolution.y;
|
|
||||||
|
|
||||||
float t = u_time * 0.4 + u_seed;
|
|
||||||
float beatImpact = u_beat * 0.1;
|
|
||||||
|
|
||||||
// Background color shift
|
|
||||||
vec3 colorA = vec3(0.05, 0.05, 0.1);
|
|
||||||
vec3 colorB = vec3(0.1, 0.0, 0.2);
|
|
||||||
if (u_colorCount >= 2) {
|
|
||||||
colorA = u_colors[0] * 0.2;
|
|
||||||
colorB = u_colors[int(mod(1.0 + floor(t * 0.1), float(u_colorCount)))] * 0.3;
|
|
||||||
}
|
|
||||||
vec3 finalColor = mix(colorA, colorB, sin(t) * 0.5 + 0.5);
|
|
||||||
|
|
||||||
// Render shapes
|
|
||||||
float shapes = 0.0;
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
float fi = float(i);
|
|
||||||
float shapeSeed = u_seed + fi * 123.456;
|
|
||||||
|
|
||||||
// Motion
|
|
||||||
vec2 pos = vec2(
|
|
||||||
sin(t * 0.5 + shapeSeed) * 0.8,
|
|
||||||
cos(t * 0.3 + shapeSeed * 1.1) * 0.4
|
|
||||||
);
|
|
||||||
|
|
||||||
vec2 sp = p - pos;
|
|
||||||
sp = rotate(sp, t * (0.5 + fract(shapeSeed)));
|
|
||||||
|
|
||||||
float d;
|
|
||||||
float size = 0.1 + fract(shapeSeed * 0.7) * 0.2 + beatImpact;
|
|
||||||
|
|
||||||
if (mod(fi, 2.0) == 0.0) {
|
|
||||||
d = sdCircle(sp, size);
|
|
||||||
} else {
|
|
||||||
d = sdBox(sp, vec2(size * 0.8));
|
|
||||||
}
|
|
||||||
|
|
||||||
float intensity = smoothstep(0.01, 0.0, d);
|
|
||||||
|
|
||||||
// Pick a color for the shape
|
|
||||||
vec3 shapeColor = vec3(1.0);
|
|
||||||
if (u_colorCount > 0) {
|
|
||||||
int colorIdx = int(mod(fi + floor(t), float(u_colorCount)));
|
|
||||||
shapeColor = u_colors[colorIdx];
|
|
||||||
}
|
|
||||||
|
|
||||||
finalColor = mix(finalColor, shapeColor, intensity * 0.8);
|
|
||||||
|
|
||||||
// Add a slight glow/aura
|
|
||||||
finalColor += shapeColor * (1.0 - smoothstep(0.0, size * 2.0, abs(d))) * 0.2 * (0.5 + u_beat);
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_FragColor = vec4(finalColor, mask * u_opacity);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class FloatingShapesVisualizer extends VisualizerInterface {
|
|
||||||
getName() {
|
|
||||||
return "Floating Geometry";
|
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentShader() {
|
|
||||||
return fragmentShader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
import { VisualizerInterface } from './VisualizerInterface.js';
|
|
||||||
|
|
||||||
const fragmentShader = `
|
|
||||||
uniform float u_time;
|
|
||||||
uniform float u_beat;
|
|
||||||
uniform float u_opacity;
|
|
||||||
uniform vec2 u_resolution;
|
|
||||||
uniform vec3 u_colors[16];
|
|
||||||
uniform int u_colorCount;
|
|
||||||
uniform float u_seed;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float ledCountX = u_resolution.x;
|
|
||||||
float ledCountY = u_resolution.y;
|
|
||||||
|
|
||||||
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
|
||||||
|
|
||||||
vec2 gridDeriv = fwidth(gridUV);
|
|
||||||
float gridDensity = max(gridDeriv.x, gridDeriv.y);
|
|
||||||
float blurFactor = smoothstep(0.3, 0.8, gridDensity);
|
|
||||||
|
|
||||||
vec2 cell = fract(gridUV);
|
|
||||||
vec2 uv = mix((floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY), vUv, blurFactor);
|
|
||||||
|
|
||||||
float dist = distance(cell, vec2(0.5));
|
|
||||||
float edgeSoftness = clamp(gridDensity * 1.5, 0.0, 0.5);
|
|
||||||
float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, dist);
|
|
||||||
mask = mix(mask, 1.0, blurFactor);
|
|
||||||
|
|
||||||
vec2 p = (uv - 0.5) * 2.0;
|
|
||||||
float r = length(p);
|
|
||||||
float a = atan(p.y, p.x);
|
|
||||||
|
|
||||||
float t = u_time * 0.5 + u_seed;
|
|
||||||
|
|
||||||
float noise = sin(p.x * 10.0 + t) * cos(p.y * 10.0 - t) * 0.5 + 0.5;
|
|
||||||
float nebula = sin(r * 15.0 - t * 2.0 + noise * 5.0) * 0.5 + 0.5;
|
|
||||||
|
|
||||||
vec3 color1 = vec3(0.1, 0.0, 0.3);
|
|
||||||
vec3 color2 = vec3(0.8, 0.2, 0.5);
|
|
||||||
|
|
||||||
if (u_colorCount >= 2) {
|
|
||||||
color1 = u_colors[0];
|
|
||||||
color2 = u_colors[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 finalColor = mix(color1, color2, nebula);
|
|
||||||
|
|
||||||
// Add some pulsing highlights based on beat
|
|
||||||
float ring = smoothstep(0.4, 0.5, abs(fract(r * 2.0 - t * 4.0) - 0.5));
|
|
||||||
finalColor += color2 * ring * u_beat * 0.5;
|
|
||||||
|
|
||||||
// Edge glow
|
|
||||||
float edge = 1.0 - smoothstep(0.3, 0.5, r);
|
|
||||||
finalColor += color1 * edge * (0.2 + u_beat * 0.3);
|
|
||||||
|
|
||||||
finalColor *= (0.4 + u_beat * 0.6);
|
|
||||||
|
|
||||||
gl_FragColor = vec4(finalColor, mask * u_opacity);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class NebulaVisualizer extends VisualizerInterface {
|
|
||||||
getName() {
|
|
||||||
return "Deep Nebula";
|
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentShader() {
|
|
||||||
return fragmentShader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
import { VisualizerInterface } from './VisualizerInterface.js';
|
|
||||||
|
|
||||||
const fragmentShader = `
|
|
||||||
uniform float u_time;
|
|
||||||
uniform float u_beat;
|
|
||||||
uniform float u_opacity;
|
|
||||||
uniform vec2 u_resolution;
|
|
||||||
uniform vec3 u_colors[16];
|
|
||||||
uniform int u_colorCount;
|
|
||||||
uniform float u_seed;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
float hash(float n) { return fract(sin(n) * 43758.5453123); }
|
|
||||||
|
|
||||||
float noise(vec2 p) {
|
|
||||||
vec2 i = floor(p);
|
|
||||||
vec2 f = fract(p);
|
|
||||||
f = f * f * (3.0 - 2.0 * f);
|
|
||||||
float n = i.x + i.y * 57.0;
|
|
||||||
return mix(mix(hash(n + 0.0), hash(n + 1.0), f.x),
|
|
||||||
mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// LED Grid Setup
|
|
||||||
float ledCountX = u_resolution.x;
|
|
||||||
float ledCountY = u_resolution.y;
|
|
||||||
vec2 gridUV = vec2(vUv.x * ledCountX, vUv.y * ledCountY);
|
|
||||||
vec2 gridDeriv = fwidth(gridUV);
|
|
||||||
float gridDensity = max(gridDeriv.x, gridDeriv.y);
|
|
||||||
float blurFactor = smoothstep(0.3, 0.8, gridDensity);
|
|
||||||
vec2 cell = fract(gridUV);
|
|
||||||
vec2 uv = mix((floor(gridUV) + 0.5) / vec2(ledCountX, ledCountY), vUv, blurFactor);
|
|
||||||
|
|
||||||
float distMask = distance(cell, vec2(0.5));
|
|
||||||
float edgeSoftness = clamp(gridDensity * 1.5, 0.0, 0.5);
|
|
||||||
float mask = 1.0 - smoothstep(0.35 - edgeSoftness, 0.45 + edgeSoftness, distMask);
|
|
||||||
mask = mix(mask, 1.0, blurFactor);
|
|
||||||
|
|
||||||
// --- Synthwave Logic ---
|
|
||||||
vec2 p = uv * 2.0 - 1.0;
|
|
||||||
p.x *= u_resolution.x / u_resolution.y;
|
|
||||||
|
|
||||||
float t = u_time * 1.5 + u_seed;
|
|
||||||
float beat = u_beat;
|
|
||||||
|
|
||||||
// Colors
|
|
||||||
vec3 colorMain = vec3(1.0, 0.0, 0.8); // Pink/Magenta
|
|
||||||
vec3 colorSec = vec3(0.0, 0.8, 1.0); // Cyan
|
|
||||||
vec3 colorBg = vec3(0.02, 0.0, 0.05);
|
|
||||||
|
|
||||||
if (u_colorCount >= 2) {
|
|
||||||
colorMain = u_colors[0];
|
|
||||||
colorSec = u_colors[int(mod(1.0, float(u_colorCount)))];
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 finalColor = colorBg;
|
|
||||||
|
|
||||||
// Horizon
|
|
||||||
float horizon = 0.0;
|
|
||||||
|
|
||||||
// 1. Perspective Grid (Ground)
|
|
||||||
if (p.y < horizon) {
|
|
||||||
float perspective = 1.0 / (horizon - p.y + 0.05);
|
|
||||||
vec2 gridP = vec2(p.x * perspective, perspective + t * 2.0);
|
|
||||||
|
|
||||||
float gridLines = smoothstep(0.05, 0.0, abs(fract(gridP.x + 0.5) - 0.5)) +
|
|
||||||
smoothstep(0.05, 0.0, abs(fract(gridP.y + 0.5) - 0.5));
|
|
||||||
|
|
||||||
finalColor = mix(finalColor, colorSec, gridLines * 0.5 * (0.5 + beat));
|
|
||||||
|
|
||||||
// Road highlight
|
|
||||||
if (abs(p.x * perspective) < 0.8) {
|
|
||||||
finalColor += colorMain * 0.2 * perspective;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Wireframe Mountains
|
|
||||||
float mountainHeight = 0.0;
|
|
||||||
if (p.y > horizon - 0.1) {
|
|
||||||
float side = sign(p.x);
|
|
||||||
float mountainX = abs(p.x) - 0.5;
|
|
||||||
if (mountainX > 0.0) {
|
|
||||||
float mNoise = noise(vec2(mountainX * 2.0 + t * 0.1, u_seed));
|
|
||||||
mountainHeight = mNoise * 0.6 * smoothstep(0.0, 0.5, mountainX);
|
|
||||||
|
|
||||||
if (p.y - horizon < mountainHeight) {
|
|
||||||
float distToEdge = abs(p.y - horizon - mountainHeight);
|
|
||||||
float wireframe = smoothstep(0.02, 0.0, distToEdge);
|
|
||||||
|
|
||||||
// Internal wireframe lines
|
|
||||||
wireframe += smoothstep(0.01, 0.0, abs(fract((p.y - horizon) * 10.0) - 0.5));
|
|
||||||
wireframe += smoothstep(0.01, 0.0, abs(fract(mountainX * 10.0) - 0.5));
|
|
||||||
|
|
||||||
finalColor = mix(finalColor, colorMain, wireframe * 0.4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Retro Sun
|
|
||||||
vec2 sunPos = vec2(0.0, 0.1);
|
|
||||||
float sunRad = 0.4;
|
|
||||||
float distToSun = length(p - sunPos);
|
|
||||||
if (distToSun < sunRad && p.y > horizon) {
|
|
||||||
// Scanlines / Cutouts
|
|
||||||
float scanline = sin((p.y - t * 0.1) * 50.0);
|
|
||||||
if (scanline < mix(0.8, -1.0, (p.y - sunPos.y + sunRad) / (sunRad * 2.0))) {
|
|
||||||
float grad = 1.0 - (distToSun / sunRad);
|
|
||||||
vec3 sunCol = mix(colorMain, vec3(1.0, 0.8, 0.0), p.y * 2.0);
|
|
||||||
finalColor = mix(finalColor, sunCol, 0.8 + beat * 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Car Silhouette
|
|
||||||
float carDrift = sin(t * 0.1) * 0.2;
|
|
||||||
float carVibration = sin(t * 30.0) * 0.001;
|
|
||||||
vec2 carPos = vec2(carDrift, -0.78 + carVibration);
|
|
||||||
vec2 cp = p - carPos;
|
|
||||||
float cpX = abs(cp.x);
|
|
||||||
|
|
||||||
float mainBody = step(cpX, 0.35) * step(abs(cp.y), 0.12);
|
|
||||||
float rearGlass = step(cpX, 0.22) * step(abs(cp.y - 0.18), 0.07);
|
|
||||||
float wingTop = step(cpX, 0.32) * step(abs(cp.y - 0.20), 0.025);
|
|
||||||
float wingSides = step(abs(cpX - 0.30), 0.03) * step(abs(cp.y - 0.12), 0.1);
|
|
||||||
|
|
||||||
if (mainBody + rearGlass + wingTop + wingSides > 0.0) {
|
|
||||||
finalColor = mix(finalColor, vec3(0.015), 0.95); // Dark silhouette
|
|
||||||
|
|
||||||
// Iconic quad round taillights
|
|
||||||
float tlY = 0.03;
|
|
||||||
float tlRadius = 0.025;
|
|
||||||
float l1 = smoothstep(tlRadius, tlRadius * 0.6, length(vec2(cpX - 0.21, cp.y - tlY)));
|
|
||||||
float l2 = smoothstep(tlRadius, tlRadius * 0.6, length(vec2(cpX - 0.29, cp.y - tlY)));
|
|
||||||
vec3 lightColor = vec3(1.0, 0.0, 0.0) * (0.6 + beat * 1.4);
|
|
||||||
finalColor = mix(finalColor, lightColor, clamp(l1 + l2, 0.0, 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_FragColor = vec4(finalColor, mask * u_opacity);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class SynthwaveRunVisualizer extends VisualizerInterface {
|
|
||||||
getName() {
|
|
||||||
return "Synthwave Run";
|
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentShader() {
|
|
||||||
return fragmentShader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
export class VisualizerInterface {
|
|
||||||
getName() {
|
|
||||||
return "Generic Visualizer";
|
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentShader() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import { ClassicVisualizer } from './ClassicVisualizer.js';
|
|
||||||
import { NebulaVisualizer } from './NebulaVisualizer.js';
|
|
||||||
import { FloatingShapesVisualizer } from './FloatingShapesVisualizer.js';
|
|
||||||
import { SynthwaveRunVisualizer } from './SynthwaveRunVisualizer.js';
|
|
||||||
|
|
||||||
export const visualizerLibrary = [
|
|
||||||
new ClassicVisualizer(),
|
|
||||||
new NebulaVisualizer(),
|
|
||||||
new FloatingShapesVisualizer(),
|
|
||||||
new SynthwaveRunVisualizer(),
|
|
||||||
];
|
|
||||||
|
|
||||||
export default visualizerLibrary;
|
|
||||||
Loading…
Reference in New Issue
Block a user