small size update with tons of rendering modes, palettes and keybindings
This commit is contained in:
@ -8,6 +8,7 @@ interface WorkerMessage {
|
||||
time?: number;
|
||||
renderMode?: string;
|
||||
valueMode?: string; // 'integer' or 'float'
|
||||
hueShift?: number; // Hue shift in degrees (0-360)
|
||||
startY?: number; // Y offset for tile rendering
|
||||
fullWidth?: number; // Full canvas width for center calculations
|
||||
fullHeight?: number; // Full canvas height for center calculations
|
||||
@ -34,6 +35,7 @@ interface WorkerMessage {
|
||||
bassLevel?: number;
|
||||
midLevel?: number;
|
||||
trebleLevel?: number;
|
||||
bpm?: number;
|
||||
}
|
||||
|
||||
interface WorkerResponse {
|
||||
@ -46,7 +48,7 @@ interface WorkerResponse {
|
||||
|
||||
import { LRUCache } from './utils/LRUCache';
|
||||
import { calculateColorDirect } from './utils/colorModes';
|
||||
import { PERFORMANCE, COLOR_TABLE_SIZE, RENDER_MODES, RENDER_MODE_INDEX } from './utils/constants';
|
||||
import { PERFORMANCE } from './utils/constants';
|
||||
|
||||
type ShaderFunction = (...args: number[]) => number;
|
||||
|
||||
@ -59,7 +61,6 @@ class ShaderWorker {
|
||||
private compilationCache: LRUCache<string, ShaderFunction> = new LRUCache(
|
||||
PERFORMANCE.COMPILATION_CACHE_SIZE
|
||||
);
|
||||
private colorTables: Uint8Array[] = [];
|
||||
private feedbackBuffer: Float32Array | null = null;
|
||||
|
||||
constructor() {
|
||||
@ -67,28 +68,6 @@ class ShaderWorker {
|
||||
this.handleMessage(e.data);
|
||||
};
|
||||
|
||||
this.initializeColorTables();
|
||||
}
|
||||
|
||||
private initializeColorTables(): void {
|
||||
const tableSize = COLOR_TABLE_SIZE;
|
||||
|
||||
// Pre-compute color tables for each render mode using array indexing
|
||||
this.colorTables = new Array(RENDER_MODES.length);
|
||||
|
||||
for (let modeIndex = 0; modeIndex < RENDER_MODES.length; modeIndex++) {
|
||||
const mode = RENDER_MODES[modeIndex];
|
||||
const colorTable = new Uint8Array(tableSize * 3); // RGB triplets
|
||||
|
||||
for (let i = 0; i < tableSize; i++) {
|
||||
const [r, g, b] = calculateColorDirect(i, mode);
|
||||
colorTable[i * 3] = r;
|
||||
colorTable[i * 3 + 1] = g;
|
||||
colorTable[i * 3 + 2] = b;
|
||||
}
|
||||
|
||||
this.colorTables[modeIndex] = colorTable;
|
||||
}
|
||||
}
|
||||
|
||||
private handleMessage(message: WorkerMessage): void {
|
||||
@ -183,6 +162,7 @@ class ShaderWorker {
|
||||
'bassLevel',
|
||||
'midLevel',
|
||||
'trebleLevel',
|
||||
'bpm',
|
||||
`
|
||||
// Timeout protection
|
||||
const startTime = performance.now();
|
||||
@ -221,7 +201,7 @@ class ShaderWorker {
|
||||
|
||||
private isStaticExpression(code: string): boolean {
|
||||
// Check if code contains any variables using regex for better accuracy
|
||||
const variablePattern = /\b(x|y|t|i|r|a|u|v|c|f|d|n|b|mouse[XY]|mousePressed|mouseV[XY]|mouseClickTime|touchCount|touch[01][XY]|pinchScale|pinchRotation|accel[XYZ]|gyro[XYZ]|audioLevel|bassLevel|midLevel|trebleLevel)\b/;
|
||||
const variablePattern = /\b(x|y|t|i|r|a|u|v|c|f|d|n|b|bpm|mouse[XY]|mousePressed|mouseV[XY]|mouseClickTime|touchCount|touch[01][XY]|pinchScale|pinchRotation|accel[XYZ]|gyro[XYZ]|audioLevel|bassLevel|midLevel|trebleLevel)\b/;
|
||||
|
||||
return !variablePattern.test(code);
|
||||
}
|
||||
@ -422,13 +402,15 @@ class ShaderWorker {
|
||||
message.audioLevel || 0,
|
||||
message.bassLevel || 0,
|
||||
message.midLevel || 0,
|
||||
message.trebleLevel || 0
|
||||
message.trebleLevel || 0,
|
||||
message.bpm || 120
|
||||
);
|
||||
const safeValue = isFinite(value) ? value : 0;
|
||||
const [r, g, b] = this.calculateColor(
|
||||
safeValue,
|
||||
renderMode,
|
||||
valueMode,
|
||||
message.hueShift || 0,
|
||||
x,
|
||||
actualY,
|
||||
fullWidth,
|
||||
@ -489,6 +471,7 @@ class ShaderWorker {
|
||||
value: number,
|
||||
renderMode: string,
|
||||
valueMode: string = 'integer',
|
||||
hueShift: number = 0,
|
||||
x: number = 0,
|
||||
y: number = 0,
|
||||
width: number = 1,
|
||||
@ -788,22 +771,137 @@ class ShaderWorker {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'spiral': {
|
||||
// Creates logarithmic spirals based on the shader value
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const dx = x - centerX;
|
||||
const dy = y - centerY;
|
||||
const radius = Math.sqrt(dx * dx + dy * dy);
|
||||
const spiralTightness = 1 + Math.abs(value) * 0.01;
|
||||
const spiralValue = Math.atan2(dy, dx) + Math.log(Math.max(radius, 1)) * spiralTightness;
|
||||
processedValue = Math.floor((Math.sin(spiralValue) * 0.5 + 0.5) * 255);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'turbulence': {
|
||||
// Multi-octave turbulence with value-controlled chaos
|
||||
let turbulence = 0;
|
||||
const chaos = Math.abs(value) * 0.001;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const freq = Math.pow(2, i) * (0.01 + chaos);
|
||||
turbulence += Math.abs(Math.sin(x * freq) * Math.cos(y * freq)) / Math.pow(2, i);
|
||||
}
|
||||
processedValue = Math.floor(Math.min(turbulence, 1) * 255);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 'crystal': {
|
||||
// Crystalline lattice patterns
|
||||
const latticeSize = 32 + Math.abs(value) * 0.1;
|
||||
const gridX = Math.floor(x / latticeSize);
|
||||
const gridY = Math.floor(y / latticeSize);
|
||||
const crystal = Math.sin(gridX + gridY + Math.abs(value) * 0.01) *
|
||||
Math.cos(gridX * gridY + Math.abs(value) * 0.005);
|
||||
processedValue = Math.floor((crystal * 0.5 + 0.5) * 255);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'marble': {
|
||||
// Marble-like veining patterns
|
||||
const noiseFreq = 0.005 + Math.abs(value) * 0.00001;
|
||||
const turbulence = Math.sin(x * noiseFreq) * Math.cos(y * noiseFreq) +
|
||||
Math.sin(x * noiseFreq * 2) * Math.cos(y * noiseFreq * 2) * 0.5;
|
||||
const marble = Math.sin((x + turbulence * 50) * 0.02 + Math.abs(value) * 0.001);
|
||||
processedValue = Math.floor((marble * 0.5 + 0.5) * 255);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 'quantum': {
|
||||
// Quantum uncertainty visualization
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const uncertainty = Math.abs(value) * 0.001;
|
||||
const probability = Math.exp(-(
|
||||
(x - centerX) ** 2 + (y - centerY) ** 2
|
||||
) / (2 * (100 + uncertainty * 1000) ** 2));
|
||||
const quantum = probability * (1 + Math.sin(x * y * uncertainty) * 0.5);
|
||||
processedValue = Math.floor(Math.min(quantum, 1) * 255);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'logarithmic': {
|
||||
// Simple mathematical transform: logarithmic scaling
|
||||
const logValue = Math.log(1 + Math.abs(value));
|
||||
processedValue = Math.floor((logValue / Math.log(256)) * 255);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'mirror': {
|
||||
// Mirror/kaleidoscope effect - creates symmetrical patterns
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const dx = Math.abs(x - centerX);
|
||||
const dy = Math.abs(y - centerY);
|
||||
const mirrorX = centerX + (dx % centerX);
|
||||
const mirrorY = centerY + (dy % centerY);
|
||||
const mirrorDistance = Math.sqrt(mirrorX * mirrorX + mirrorY * mirrorY);
|
||||
const mirrorValue = (Math.abs(value) + mirrorDistance) % 256;
|
||||
processedValue = mirrorValue;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'rings': {
|
||||
// Concentric rings with value-controlled spacing and interference
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
|
||||
const ringSpacing = 20 + Math.abs(value) * 0.1;
|
||||
const rings = Math.sin((distance / ringSpacing) * Math.PI * 2);
|
||||
const interference = Math.sin((distance + Math.abs(value)) * 0.05);
|
||||
processedValue = Math.floor(((rings * interference) * 0.5 + 0.5) * 255);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'mesh': {
|
||||
// Grid/mesh patterns with value-controlled density and rotation
|
||||
const angle = Math.abs(value) * 0.001;
|
||||
const rotX = x * Math.cos(angle) - y * Math.sin(angle);
|
||||
const rotY = x * Math.sin(angle) + y * Math.cos(angle);
|
||||
const gridSize = 16 + Math.abs(value) * 0.05;
|
||||
const gridX = Math.sin((rotX / gridSize) * Math.PI * 2);
|
||||
const gridY = Math.sin((rotY / gridSize) * Math.PI * 2);
|
||||
const mesh = Math.max(Math.abs(gridX), Math.abs(gridY));
|
||||
processedValue = Math.floor(mesh * 255);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'glitch': {
|
||||
// Digital glitch/corruption effects
|
||||
const seed = Math.floor(x + y * width + Math.abs(value));
|
||||
const random = ((seed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
|
||||
const glitchThreshold = 0.95 - Math.abs(value) * 0.0001;
|
||||
let glitchValue = Math.abs(value) % 256;
|
||||
|
||||
if (random > glitchThreshold) {
|
||||
// Digital corruption: bit shifts, XOR, scrambling
|
||||
glitchValue = (glitchValue << 1) ^ (glitchValue >> 3) ^ ((x + y) & 0xFF);
|
||||
}
|
||||
|
||||
processedValue = glitchValue % 256;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Integer mode: treat value as 0-255 (original behavior)
|
||||
processedValue = Math.abs(value) % 256;
|
||||
break;
|
||||
}
|
||||
|
||||
// Use pre-computed color table with O(1) array indexing
|
||||
const modeIndex = RENDER_MODE_INDEX[renderMode];
|
||||
if (modeIndex !== undefined && this.colorTables[modeIndex]) {
|
||||
const colorTable = this.colorTables[modeIndex];
|
||||
const index = Math.floor(processedValue) * 3;
|
||||
return [colorTable[index], colorTable[index + 1], colorTable[index + 2]];
|
||||
}
|
||||
|
||||
// Fallback to direct calculation for unknown render modes
|
||||
return calculateColorDirect(processedValue, renderMode);
|
||||
// Use direct calculation to support hue shift
|
||||
return calculateColorDirect(processedValue, renderMode, hueShift);
|
||||
}
|
||||
|
||||
private sanitizeCode(code: string): string {
|
||||
|
||||
Reference in New Issue
Block a user