170 lines
5.6 KiB
JavaScript
170 lines
5.6 KiB
JavaScript
import { ButtonEffect } from '../effect-base.js';
|
|
|
|
/**
|
|
* Hologram effect
|
|
* Creates a futuristic holographic appearance with glitches and scan lines
|
|
*/
|
|
export class HologramEffect extends ButtonEffect {
|
|
constructor() {
|
|
super({
|
|
id: 'hologram',
|
|
name: 'Hologram',
|
|
type: 'general',
|
|
category: 'Visual Effects',
|
|
renderOrder: 88 // Near the end, after most other effects
|
|
});
|
|
}
|
|
|
|
defineControls() {
|
|
return [
|
|
{
|
|
id: 'animate-hologram',
|
|
type: 'checkbox',
|
|
label: 'Hologram Effect',
|
|
defaultValue: false,
|
|
description: 'Futuristic holographic appearance'
|
|
},
|
|
{
|
|
id: 'hologram-intensity',
|
|
type: 'range',
|
|
label: 'Effect Intensity',
|
|
defaultValue: 50,
|
|
min: 10,
|
|
max: 100,
|
|
step: 5,
|
|
showWhen: 'animate-hologram',
|
|
description: 'Strength of hologram effect'
|
|
},
|
|
{
|
|
id: 'hologram-glitch-freq',
|
|
type: 'range',
|
|
label: 'Glitch Frequency',
|
|
defaultValue: 30,
|
|
min: 0,
|
|
max: 100,
|
|
step: 10,
|
|
showWhen: 'animate-hologram',
|
|
description: 'How often glitches occur'
|
|
},
|
|
{
|
|
id: 'hologram-color',
|
|
type: 'color',
|
|
label: 'Hologram Tint',
|
|
defaultValue: '#00ffff',
|
|
showWhen: 'animate-hologram',
|
|
description: 'Color tint for hologram effect'
|
|
}
|
|
];
|
|
}
|
|
|
|
isEnabled(controlValues) {
|
|
return controlValues['animate-hologram'] === true;
|
|
}
|
|
|
|
apply(context, controlValues, animState, renderData) {
|
|
if (!animState) return;
|
|
|
|
const intensity = (controlValues['hologram-intensity'] || 50) / 100;
|
|
const glitchFreq = (controlValues['hologram-glitch-freq'] || 30) / 100;
|
|
const color = controlValues['hologram-color'] || '#00ffff';
|
|
|
|
// Get current canvas content
|
|
const imageData = context.getImageData(0, 0, renderData.width, renderData.height);
|
|
const data = imageData.data;
|
|
|
|
// Parse hologram color for tinting
|
|
const hexColor = color.replace('#', '');
|
|
const r = parseInt(hexColor.substr(0, 2), 16);
|
|
const g = parseInt(hexColor.substr(2, 2), 16);
|
|
const b = parseInt(hexColor.substr(4, 2), 16);
|
|
|
|
// Apply holographic tint
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
// Mix with hologram color
|
|
data[i] = data[i] * (1 - intensity * 0.3) + r * intensity * 0.3; // Red
|
|
data[i + 1] = data[i + 1] * (1 - intensity * 0.5) + g * intensity * 0.5; // Green (more cyan)
|
|
data[i + 2] = data[i + 2] * (1 - intensity * 0.5) + b * intensity * 0.5; // Blue (more cyan)
|
|
}
|
|
|
|
context.putImageData(imageData, 0, 0);
|
|
|
|
// Add horizontal scan lines
|
|
context.globalAlpha = 0.05 * intensity;
|
|
context.fillStyle = '#000000';
|
|
for (let y = 0; y < renderData.height; y += 2) {
|
|
context.fillRect(0, y, renderData.width, 1);
|
|
}
|
|
context.globalAlpha = 1.0;
|
|
|
|
// Add moving highlight scan line
|
|
const scanY = (animState.progress * renderData.height) % renderData.height;
|
|
const gradient = context.createLinearGradient(0, scanY - 3, 0, scanY + 3);
|
|
gradient.addColorStop(0, 'rgba(0, 255, 255, 0)');
|
|
gradient.addColorStop(0.5, `rgba(${r}, ${g}, ${b}, ${0.3 * intensity})`);
|
|
gradient.addColorStop(1, 'rgba(0, 255, 255, 0)');
|
|
context.fillStyle = gradient;
|
|
context.fillRect(0, scanY - 3, renderData.width, 6);
|
|
|
|
// Random glitches
|
|
if (Math.random() < glitchFreq * 0.1) {
|
|
const glitchY = Math.floor(Math.random() * renderData.height);
|
|
const glitchHeight = Math.floor(2 + Math.random() * 4);
|
|
const offset = (Math.random() - 0.5) * 6 * intensity;
|
|
|
|
const sliceData = context.getImageData(0, glitchY, renderData.width, glitchHeight);
|
|
context.putImageData(sliceData, offset, glitchY);
|
|
}
|
|
|
|
// Add chromatic aberration on edges
|
|
if (intensity > 0.3) {
|
|
const originalImage = context.getImageData(0, 0, renderData.width, renderData.height);
|
|
const aberration = 2 * intensity;
|
|
|
|
// Slight red shift right
|
|
const redShift = context.getImageData(0, 0, renderData.width, renderData.height);
|
|
for (let i = 0; i < redShift.data.length; i += 4) {
|
|
const pixelIndex = i / 4;
|
|
const x = pixelIndex % renderData.width;
|
|
if (x < 3 || x > renderData.width - 3) {
|
|
const sourceIndex = ((pixelIndex + Math.floor(aberration)) * 4);
|
|
if (sourceIndex < originalImage.data.length) {
|
|
redShift.data[i] = originalImage.data[sourceIndex];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Slight blue shift left
|
|
const blueShift = context.getImageData(0, 0, renderData.width, renderData.height);
|
|
for (let i = 0; i < blueShift.data.length; i += 4) {
|
|
const pixelIndex = i / 4;
|
|
const x = pixelIndex % renderData.width;
|
|
if (x < 3 || x > renderData.width - 3) {
|
|
const sourceIndex = ((pixelIndex - Math.floor(aberration)) * 4);
|
|
if (sourceIndex >= 0 && sourceIndex < originalImage.data.length) {
|
|
blueShift.data[i + 2] = originalImage.data[sourceIndex + 2];
|
|
}
|
|
}
|
|
}
|
|
|
|
context.putImageData(redShift, 0, 0);
|
|
context.globalCompositeOperation = 'screen';
|
|
context.globalAlpha = 0.3;
|
|
context.putImageData(blueShift, 0, 0);
|
|
context.globalCompositeOperation = 'source-over';
|
|
context.globalAlpha = 1.0;
|
|
}
|
|
|
|
// Add flickering effect
|
|
if (Math.random() < 0.05) {
|
|
context.globalAlpha = 0.9 + Math.random() * 0.1;
|
|
context.fillStyle = 'rgba(255, 255, 255, 0.05)';
|
|
context.fillRect(0, 0, renderData.width, renderData.height);
|
|
context.globalAlpha = 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Auto-register effect
|
|
export function register(generator) {
|
|
generator.registerEffect(new HologramEffect());
|
|
}
|