optimisations

This commit is contained in:
2025-07-15 00:52:00 +02:00
parent 3eeafc1277
commit 2cee4084c0
11 changed files with 2067 additions and 722 deletions

View File

@ -1,4 +1,5 @@
import { WorkerMessage, WorkerResponse } from './shader/types'; import { WorkerMessage, WorkerResponse } from './shader/types';
import { TIMING, WORKER, DEFAULTS } from './utils/constants';
export class FakeShader { export class FakeShader {
private canvas: HTMLCanvasElement; private canvas: HTMLCanvasElement;
@ -15,8 +16,8 @@ export class FakeShader {
private renderMode: string = 'classic'; private renderMode: string = 'classic';
private valueMode: string = 'integer'; private valueMode: string = 'integer';
private hueShift: number = 0; private hueShift: number = 0;
private timeSpeed: number = 1.0; private timeSpeed: number = DEFAULTS.TIME_SPEED;
private currentBPM: number = 120; private currentBPM: number = TIMING.DEFAULT_BPM;
// ID generation optimization // ID generation optimization
private idCounter: number = 0; private idCounter: number = 0;
@ -56,7 +57,7 @@ export class FakeShader {
bassLevel: 0, bassLevel: 0,
midLevel: 0, midLevel: 0,
trebleLevel: 0, trebleLevel: 0,
bpm: 120, bpm: TIMING.DEFAULT_BPM,
startY: 0, startY: 0,
}; };
@ -76,7 +77,7 @@ export class FakeShader {
private touch0Y: number = 0; private touch0Y: number = 0;
private touch1X: number = 0; private touch1X: number = 0;
private touch1Y: number = 0; private touch1Y: number = 0;
private pinchScale: number = 1; private pinchScale: number = WORKER.DEFAULT_PINCH_SCALE;
private pinchRotation: number = 0; private pinchRotation: number = 0;
private accelX: number = 0; private accelX: number = 0;
private accelY: number = 0; private accelY: number = 0;
@ -90,8 +91,8 @@ export class FakeShader {
private trebleLevel: number = 0; private trebleLevel: number = 0;
// Frame rate limiting // Frame rate limiting
private targetFPS: number = 30; private targetFPS: number = TIMING.DEFAULT_FPS;
private frameInterval: number = 1000 / this.targetFPS; private frameInterval: number = TIMING.MILLISECONDS_PER_SECOND / this.targetFPS;
private lastFrameTime: number = 0; private lastFrameTime: number = 0;
constructor(canvas: HTMLCanvasElement, code: string = 'x^y') { constructor(canvas: HTMLCanvasElement, code: string = 'x^y') {
@ -103,10 +104,10 @@ export class FakeShader {
this.initializeOffscreenCanvas(); this.initializeOffscreenCanvas();
// Always use maximum available cores // Always use maximum available cores
this.workerCount = navigator.hardwareConcurrency || 4; this.workerCount = navigator.hardwareConcurrency || WORKER.FALLBACK_CORE_COUNT;
// Some browsers report logical processors (hyperthreading), which is good // Some browsers report logical processors (hyperthreading), which is good
// But cap at a reasonable maximum to avoid overhead // But cap at a reasonable maximum to avoid overhead
this.workerCount = Math.min(this.workerCount, 32); this.workerCount = Math.min(this.workerCount, WORKER.MAX_WORKERS);
console.log( console.log(
`Auto-detected ${this.workerCount} CPU cores, using all for maximum performance` `Auto-detected ${this.workerCount} CPU cores, using all for maximum performance`
); );
@ -221,7 +222,7 @@ export class FakeShader {
this.isRendering = true; this.isRendering = true;
// this._currentRenderID = id; // Removed unused property // this._currentRenderID = id; // Removed unused property
const currentTime = (Date.now() - this.startTime) / 1000 * this.timeSpeed; const currentTime = (Date.now() - this.startTime) / TIMING.MILLISECONDS_PER_SECOND * this.timeSpeed;
// Always use multiple workers if available // Always use multiple workers if available
if (this.workerCount > 1) { if (this.workerCount > 1) {
@ -366,8 +367,8 @@ export class FakeShader {
} }
setTargetFPS(fps: number): void { setTargetFPS(fps: number): void {
this.targetFPS = Math.max(1, Math.min(120, fps)); // Clamp between 1-120 FPS this.targetFPS = Math.max(TIMING.MIN_FPS, Math.min(TIMING.MAX_FPS, fps)); // Clamp between 1-120 FPS
this.frameInterval = 1000 / this.targetFPS; this.frameInterval = TIMING.MILLISECONDS_PER_SECOND / this.targetFPS;
} }
setRenderMode(mode: string): void { setRenderMode(mode: string): void {

281
src/RefactoredShader.ts Normal file
View File

@ -0,0 +1,281 @@
import { WorkerMessage } from './shader/types';
import { InputManager } from './shader/core/InputManager';
import { WorkerPool } from './shader/core/WorkerPool';
import { RenderController } from './shader/core/RenderController';
/**
* Refactored shader renderer with separated concerns
* Demonstrates the benefits of extracting responsibilities from the God class
*/
export class RefactoredShader {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private code: string;
// Extracted components with single responsibilities
private inputManager: InputManager;
private workerPool: WorkerPool;
private renderController: RenderController;
// Render state
private compiled: boolean = false;
private renderMode: string = 'classic';
private valueMode: string = 'integer';
private hueShift: number = 0;
// Reusable message object for performance
private reusableMessage: WorkerMessage = {
id: '',
type: 'render',
width: 0,
height: 0,
fullWidth: 0,
fullHeight: 0,
time: 0,
renderMode: 'classic',
valueMode: 'integer',
hueShift: 0,
mouseX: 0,
mouseY: 0,
mousePressed: false,
mouseVX: 0,
mouseVY: 0,
mouseClickTime: 0,
touchCount: 0,
touch0X: 0,
touch0Y: 0,
touch1X: 0,
touch1Y: 0,
pinchScale: 1,
pinchRotation: 0,
accelX: 0,
accelY: 0,
accelZ: 0,
gyroX: 0,
gyroY: 0,
gyroZ: 0,
audioLevel: 0,
bassLevel: 0,
midLevel: 0,
trebleLevel: 0,
bpm: 120,
startY: 0,
};
constructor(canvas: HTMLCanvasElement, code: string = 'x^y') {
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
this.code = code;
// Initialize separated components
this.inputManager = new InputManager();
this.workerPool = new WorkerPool();
this.renderController = new RenderController();
this.setupEventHandlers();
this.compile();
}
private setupEventHandlers(): void {
// Set up render controller callback
this.renderController.setRenderFrameHandler((time, renderId) => {
this.render(renderId, time);
});
// Set up worker pool callbacks
this.workerPool.setRenderCompleteHandler((imageData) => {
this.ctx.putImageData(imageData, 0, 0);
this.renderController.setRenderingState(false);
});
this.workerPool.setErrorHandler((error) => {
console.error('Rendering error:', error);
this.renderController.setRenderingState(false);
});
}
async compile(): Promise<void> {
try {
await this.workerPool.compile(this.code);
this.compiled = true;
console.log('Shader compiled successfully');
} catch (error) {
console.error('Compilation failed:', error);
this.compiled = false;
throw error;
}
}
private render(id: string, currentTime: number): void {
if (!this.compiled || this.renderController.isCurrentlyRendering()) {
return;
}
this.renderController.setRenderingState(true);
this.renderController.addPendingRender(id);
// Update reusable message to avoid allocations
this.reusableMessage.id = id;
this.reusableMessage.width = this.canvas.width;
this.reusableMessage.height = this.canvas.height;
this.reusableMessage.fullWidth = this.canvas.width;
this.reusableMessage.fullHeight = this.canvas.height;
this.reusableMessage.time = currentTime;
this.reusableMessage.renderMode = this.renderMode;
this.reusableMessage.valueMode = this.valueMode;
this.reusableMessage.hueShift = this.hueShift;
// Populate input data from InputManager
this.inputManager.populateWorkerMessage(this.reusableMessage);
// Choose rendering strategy based on worker count
if (this.workerPool.getWorkerCount() > 1) {
this.workerPool.renderMultiWorker(
this.reusableMessage,
this.canvas.width,
this.canvas.height
);
} else {
this.workerPool.renderSingleWorker(this.reusableMessage);
}
}
// Public API methods
start(): void {
if (!this.compiled) {
console.warn('Cannot start rendering: shader not compiled');
return;
}
this.renderController.start();
}
stop(): void {
this.renderController.stop();
}
setCode(code: string): Promise<void> {
this.code = code;
this.compiled = false;
return this.compile();
}
setRenderMode(mode: string): void {
this.renderMode = mode;
}
setValueMode(mode: string): void {
this.valueMode = mode;
}
setHueShift(shift: number): void {
this.hueShift = shift;
}
setTargetFPS(fps: number): void {
this.renderController.setTargetFPS(fps);
}
setTimeSpeed(speed: number): void {
this.renderController.setTimeSpeed(speed);
}
// Input methods - delegated to InputManager
setMousePosition(x: number, y: number): void {
this.inputManager.setMousePosition(x, y);
}
setMousePressed(pressed: boolean): void {
this.inputManager.setMousePressed(pressed);
}
setMouseVelocity(vx: number, vy: number): void {
this.inputManager.setMouseVelocity(vx, vy);
}
setTouchData(
count: number,
x0: number = 0,
y0: number = 0,
x1: number = 0,
y1: number = 0,
scale: number = 1,
rotation: number = 0
): void {
this.inputManager.setTouchData(count, x0, y0, x1, y1, scale, rotation);
}
setAccelerometer(x: number, y: number, z: number): void {
this.inputManager.setAccelerometer(x, y, z);
}
setGyroscope(x: number, y: number, z: number): void {
this.inputManager.setGyroscope(x, y, z);
}
setAudioLevels(
level: number,
bass: number,
mid: number,
treble: number,
bpm: number
): void {
this.inputManager.setAudioLevels(level, bass, mid, treble, bpm);
}
// Getters
isCompiled(): boolean {
return this.compiled;
}
isAnimating(): boolean {
return this.renderController.isAnimating();
}
getCurrentTime(): number {
return this.renderController.getCurrentTime();
}
getFrameRate(): number {
return this.renderController.getFrameRate();
}
getWorkerCount(): number {
return this.workerPool.getWorkerCount();
}
// Cleanup
destroy(): void {
this.renderController.stop();
this.workerPool.destroy();
}
// Helper methods for generating example shader code
static getExamples(): string[] {
return [
'x^y',
'x|y',
'(x+y+t*10)%256',
'((x>>4)^(y>>4))<<4',
'(x^y^(x*y))%256',
'd * t / 2.0',
'((x&y)|(x^y))%256',
'(x+y)&255',
'x%y',
'((x*t)^y)%256',
'(x&(y|t*8))%256',
'a+d*t',
'n*t*400',
'((x>>2)|(y<<2))%88',
'(x*y*t)%256',
'(x+y*t)%256',
'(x^y^(t*16))%256',
'((x*t)&(y*t))%256',
'(x+(y<<(t%4)))%256',
'((x*t%128)^y)%256',
'(x^(y*t*2))%256',
'((x+t)*(y+t))%256',
'(x&y&(t*8))%256',
'((x|t)^(y|t))%256',
];
}
}

View File

@ -3,6 +3,7 @@ import {
STORAGE_KEYS, STORAGE_KEYS,
PERFORMANCE, PERFORMANCE,
DEFAULTS, DEFAULTS,
FORMAT,
ValueMode, ValueMode,
} from './utils/constants'; } from './utils/constants';
@ -146,12 +147,12 @@ export class Storage {
} }
private static generateId(): string { private static generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2); return Date.now().toString(FORMAT.ID_RADIX) + Math.random().toString(FORMAT.ID_RADIX).substr(FORMAT.ID_SUBSTRING_START);
} }
static exportShaders(): string { static exportShaders(): string {
const shaders = this.getShaders(); const shaders = this.getShaders();
return JSON.stringify(shaders, null, 2); return JSON.stringify(shaders, null, FORMAT.JSON_INDENT);
} }
static importShaders(jsonData: string): boolean { static importShaders(jsonData: string): boolean {

View File

@ -0,0 +1,188 @@
/**
* Manages all input tracking (mouse, touch, accelerometer, audio)
* Extracted from FakeShader to follow Single Responsibility Principle
*/
export class InputManager {
// Mouse state
private mouseX: number = 0;
private mouseY: number = 0;
private mousePressed: boolean = false;
private mouseVX: number = 0;
private mouseVY: number = 0;
private mouseClickTime: number = 0;
// Touch state
private touchCount: number = 0;
private touch0X: number = 0;
private touch0Y: number = 0;
private touch1X: number = 0;
private touch1Y: number = 0;
private pinchScale: number = 1;
private pinchRotation: number = 0;
// Accelerometer state
private accelX: number = 0;
private accelY: number = 0;
private accelZ: number = 0;
// Gyroscope state
private gyroX: number = 0;
private gyroY: number = 0;
private gyroZ: number = 0;
// Audio state
private audioLevel: number = 0;
private bassLevel: number = 0;
private midLevel: number = 0;
private trebleLevel: number = 0;
private currentBPM: number = 120;
setMousePosition(x: number, y: number): void {
this.mouseX = x;
this.mouseY = y;
}
setMousePressed(pressed: boolean): void {
this.mousePressed = pressed;
if (pressed) {
this.mouseClickTime = Date.now();
}
}
setMouseVelocity(vx: number, vy: number): void {
this.mouseVX = vx;
this.mouseVY = vy;
}
setTouchData(
count: number,
x0: number = 0,
y0: number = 0,
x1: number = 0,
y1: number = 0,
scale: number = 1,
rotation: number = 0
): void {
this.touchCount = count;
this.touch0X = x0;
this.touch0Y = y0;
this.touch1X = x1;
this.touch1Y = y1;
this.pinchScale = scale;
this.pinchRotation = rotation;
}
setAccelerometer(x: number, y: number, z: number): void {
this.accelX = x;
this.accelY = y;
this.accelZ = z;
}
setGyroscope(x: number, y: number, z: number): void {
this.gyroX = x;
this.gyroY = y;
this.gyroZ = z;
}
setAudioLevels(
level: number,
bass: number,
mid: number,
treble: number,
bpm: number
): void {
this.audioLevel = level;
this.bassLevel = bass;
this.midLevel = mid;
this.trebleLevel = treble;
this.currentBPM = bpm;
}
// Getters for all input values
getMouseData() {
return {
x: this.mouseX,
y: this.mouseY,
pressed: this.mousePressed,
vx: this.mouseVX,
vy: this.mouseVY,
clickTime: this.mouseClickTime,
};
}
getTouchData() {
return {
count: this.touchCount,
x0: this.touch0X,
y0: this.touch0Y,
x1: this.touch1X,
y1: this.touch1Y,
scale: this.pinchScale,
rotation: this.pinchRotation,
};
}
getAccelerometerData() {
return {
x: this.accelX,
y: this.accelY,
z: this.accelZ,
};
}
getGyroscopeData() {
return {
x: this.gyroX,
y: this.gyroY,
z: this.gyroZ,
};
}
getAudioData() {
return {
level: this.audioLevel,
bass: this.bassLevel,
mid: this.midLevel,
treble: this.trebleLevel,
bpm: this.currentBPM,
};
}
// Helper method to populate worker message with all input data
populateWorkerMessage(message: any): void {
const mouse = this.getMouseData();
const touch = this.getTouchData();
const accel = this.getAccelerometerData();
const gyro = this.getGyroscopeData();
const audio = this.getAudioData();
message.mouseX = mouse.x;
message.mouseY = mouse.y;
message.mousePressed = mouse.pressed;
message.mouseVX = mouse.vx;
message.mouseVY = mouse.vy;
message.mouseClickTime = mouse.clickTime;
message.touchCount = touch.count;
message.touch0X = touch.x0;
message.touch0Y = touch.y0;
message.touch1X = touch.x1;
message.touch1Y = touch.y1;
message.pinchScale = touch.scale;
message.pinchRotation = touch.rotation;
message.accelX = accel.x;
message.accelY = accel.y;
message.accelZ = accel.z;
message.gyroX = gyro.x;
message.gyroY = gyro.y;
message.gyroZ = gyro.z;
message.audioLevel = audio.level;
message.bassLevel = audio.bass;
message.midLevel = audio.mid;
message.trebleLevel = audio.treble;
message.bpm = audio.bpm;
}
}

View File

@ -0,0 +1,114 @@
import { TIMING } from '../../utils/constants';
/**
* Manages animation timing and frame rate control
* Extracted from FakeShader for better separation of concerns
*/
export class RenderController {
private animationId: number | null = null;
private startTime: number = Date.now();
private targetFPS: number = TIMING.DEFAULT_FPS;
private frameInterval: number = TIMING.MILLISECONDS_PER_SECOND / this.targetFPS;
private lastFrameTime: number = 0;
private timeSpeed: number = 1.0;
private isRendering: boolean = false;
private pendingRenders: string[] = [];
private idCounter: number = 0;
private onRenderFrame?: (time: number, renderId: string) => void;
setRenderFrameHandler(handler: (time: number, renderId: string) => void): void {
this.onRenderFrame = handler;
}
start(): void {
if (this.animationId !== null) return;
const animate = (timestamp: number) => {
if (timestamp - this.lastFrameTime >= this.frameInterval) {
const currentTime = (Date.now() - this.startTime) / TIMING.MILLISECONDS_PER_SECOND * this.timeSpeed;
const renderId = this.generateId();
this.onRenderFrame?.(currentTime, renderId);
this.lastFrameTime = timestamp;
}
this.animationId = requestAnimationFrame(animate);
};
this.animationId = requestAnimationFrame(animate);
}
stop(): void {
if (this.animationId !== null) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
setTargetFPS(fps: number): void {
this.targetFPS = Math.max(TIMING.MIN_FPS, Math.min(TIMING.MAX_FPS, fps));
this.frameInterval = TIMING.MILLISECONDS_PER_SECOND / this.targetFPS;
}
setTimeSpeed(speed: number): void {
this.timeSpeed = speed;
}
getTimeSpeed(): number {
return this.timeSpeed;
}
getCurrentTime(): number {
return (Date.now() - this.startTime) / TIMING.MILLISECONDS_PER_SECOND * this.timeSpeed;
}
isAnimating(): boolean {
return this.animationId !== null;
}
generateId(): string {
return `render_${this.idCounter++}_${Date.now()}`;
}
setRenderingState(isRendering: boolean): void {
this.isRendering = isRendering;
}
isCurrentlyRendering(): boolean {
return this.isRendering;
}
addPendingRender(renderId: string): void {
this.pendingRenders.push(renderId);
// Keep only the latest render to avoid backlog
if (this.pendingRenders.length > 3) {
const latestId = this.pendingRenders[this.pendingRenders.length - 1];
this.pendingRenders = [latestId];
}
}
removePendingRender(renderId: string): void {
const index = this.pendingRenders.indexOf(renderId);
if (index !== -1) {
this.pendingRenders.splice(index, 1);
}
}
getPendingRenders(): string[] {
return [...this.pendingRenders];
}
clearPendingRenders(): void {
this.pendingRenders = [];
}
getFrameRate(): number {
return this.targetFPS;
}
getFrameInterval(): number {
return this.frameInterval;
}
}

View File

@ -0,0 +1,188 @@
import { WorkerMessage, WorkerResponse } from '../types';
import { WORKER } from '../../utils/constants';
/**
* Manages worker lifecycle and multi-worker rendering
* Extracted from FakeShader for better separation of concerns
*/
export class WorkerPool {
private workers: Worker[] = [];
private workerCount: number;
private tileResults: Map<number, ImageData> = new Map();
private tilesCompleted: number = 0;
private totalTiles: number = 0;
private onRenderComplete?: (imageData: ImageData) => void;
private onError?: (error: any) => void;
constructor() {
this.workerCount = navigator.hardwareConcurrency || WORKER.FALLBACK_CORE_COUNT;
this.workerCount = Math.min(this.workerCount, WORKER.MAX_WORKERS);
console.log(`WorkerPool: Using ${this.workerCount} workers for rendering`);
this.initializeWorkers();
}
private initializeWorkers(): void {
for (let i = 0; i < this.workerCount; i++) {
const worker = new Worker(new URL('../worker/ShaderWorker.ts', import.meta.url), {
type: 'module',
});
worker.onmessage = (event) => {
this.handleWorkerMessage(event.data, i);
};
worker.onerror = (error) => {
console.error(`Worker ${i} error:`, error);
this.onError?.(error);
};
this.workers.push(worker);
}
}
private handleWorkerMessage(response: WorkerResponse, workerIndex: number): void {
switch (response.type) {
case 'compiled':
// Handle compilation response if needed
break;
case 'rendered':
if (this.workerCount > 1) {
this.handleTileResult(response, workerIndex);
} else {
this.onRenderComplete?.(response.imageData!);
}
break;
case 'error':
console.error(`Worker ${workerIndex} error:`, response.error);
this.onError?.(response.error);
break;
}
}
private handleTileResult(response: WorkerResponse, workerIndex: number): void {
if (!response.imageData || response.tileIndex === undefined) return;
this.tileResults.set(response.tileIndex, response.imageData);
this.tilesCompleted++;
if (this.tilesCompleted >= this.totalTiles) {
this.assembleTiles();
}
}
private assembleTiles(): void {
if (this.tileResults.size === 0) return;
const firstTile = this.tileResults.get(0);
if (!firstTile) return;
const tileWidth = firstTile.width;
const tileHeight = firstTile.height;
const tilesPerRow = Math.ceil(Math.sqrt(this.totalTiles));
const totalWidth = tileWidth;
const totalHeight = tileHeight * this.totalTiles;
const canvas = new OffscreenCanvas(totalWidth, totalHeight);
const ctx = canvas.getContext('2d')!;
const finalImageData = ctx.createImageData(totalWidth, totalHeight);
for (let i = 0; i < this.totalTiles; i++) {
const tileData = this.tileResults.get(i);
if (!tileData) continue;
const startY = i * tileHeight;
const sourceData = tileData.data;
const targetData = finalImageData.data;
for (let y = 0; y < tileHeight; y++) {
for (let x = 0; x < tileWidth; x++) {
const sourceIndex = (y * tileWidth + x) * 4;
const targetIndex = ((startY + y) * totalWidth + x) * 4;
targetData[targetIndex] = sourceData[sourceIndex];
targetData[targetIndex + 1] = sourceData[sourceIndex + 1];
targetData[targetIndex + 2] = sourceData[sourceIndex + 2];
targetData[targetIndex + 3] = sourceData[sourceIndex + 3];
}
}
}
this.onRenderComplete?.(finalImageData);
this.tileResults.clear();
this.tilesCompleted = 0;
}
compile(code: string): Promise<void> {
return new Promise((resolve, reject) => {
const worker = this.workers[0]; // Use first worker for compilation
const compileMessage = {
type: 'compile',
code,
};
const handleResponse = (event: MessageEvent) => {
const response = event.data;
if (response.type === 'compiled') {
worker.removeEventListener('message', handleResponse);
resolve();
} else if (response.type === 'error') {
worker.removeEventListener('message', handleResponse);
reject(response.error);
}
};
worker.addEventListener('message', handleResponse);
worker.postMessage(compileMessage);
});
}
renderSingleWorker(message: WorkerMessage): void {
const worker = this.workers[0];
worker.postMessage(message);
}
renderMultiWorker(baseMessage: WorkerMessage, width: number, height: number): void {
this.tileResults.clear();
this.tilesCompleted = 0;
this.totalTiles = this.workerCount;
const tileHeight = Math.ceil(height / this.totalTiles);
for (let i = 0; i < this.totalTiles; i++) {
const worker = this.workers[i];
const startY = i * tileHeight;
const endY = Math.min((i + 1) * tileHeight, height);
const actualTileHeight = endY - startY;
const tileMessage: WorkerMessage = {
...baseMessage,
startY,
height: actualTileHeight,
tileIndex: i,
};
worker.postMessage(tileMessage);
}
}
setRenderCompleteHandler(handler: (imageData: ImageData) => void): void {
this.onRenderComplete = handler;
}
setErrorHandler(handler: (error: any) => void): void {
this.onError = handler;
}
getWorkerCount(): number {
return this.workerCount;
}
destroy(): void {
this.workers.forEach(worker => worker.terminate());
this.workers = [];
this.tileResults.clear();
}
}

View File

@ -1,3 +1,5 @@
import { LUMINANCE_WEIGHTS } from '../../utils/constants';
/** /**
* Manages feedback buffers for shader rendering * Manages feedback buffers for shader rendering
*/ */
@ -35,7 +37,7 @@ export class FeedbackSystem {
if (!this.feedbackBuffer) return; if (!this.feedbackBuffer) return;
// Use the actual displayed luminance as feedback (0-255 range) // Use the actual displayed luminance as feedback (0-255 range)
const luminance = (r * 0.299 + g * 0.587 + b * 0.114); const luminance = (r * LUMINANCE_WEIGHTS.RED + g * LUMINANCE_WEIGHTS.GREEN + b * LUMINANCE_WEIGHTS.BLUE);
// Frame rate independent decay // Frame rate independent decay
const decayFactor = Math.pow(0.95, deltaTime * 60); // 5% decay at 60fps const decayFactor = Math.pow(0.95, deltaTime * 60); // 5% decay at 60fps

View File

@ -1,6 +1,10 @@
import { ShaderFunction, ShaderContext, WorkerMessage } from '../types'; import { ShaderFunction, ShaderContext, WorkerMessage } from '../types';
import { FeedbackSystem } from './FeedbackSystem'; import { FeedbackSystem } from './FeedbackSystem';
import { calculateColorDirect } from '../../utils/colorModes'; import { calculateColorDirect } from '../../utils/colorModes';
import {
ValueModeProcessorRegistry,
PixelContext,
} from './ValueModeProcessor';
/** /**
* Handles pixel-level rendering operations * Handles pixel-level rendering operations
@ -8,10 +12,12 @@ import { calculateColorDirect } from '../../utils/colorModes';
export class PixelRenderer { export class PixelRenderer {
private feedbackSystem: FeedbackSystem; private feedbackSystem: FeedbackSystem;
private shaderContext: ShaderContext; private shaderContext: ShaderContext;
private valueModeRegistry: ValueModeProcessorRegistry;
constructor(feedbackSystem: FeedbackSystem, shaderContext: ShaderContext) { constructor(feedbackSystem: FeedbackSystem, shaderContext: ShaderContext) {
this.feedbackSystem = feedbackSystem; this.feedbackSystem = feedbackSystem;
this.shaderContext = shaderContext; this.shaderContext = shaderContext;
this.valueModeRegistry = ValueModeProcessorRegistry.getInstance();
} }
/** /**
@ -75,21 +81,25 @@ export class PixelRenderer {
// Optimize Manhattan distance using absolute values of pre-computed deltas // Optimize Manhattan distance using absolute values of pre-computed deltas
const manhattanDistance = Math.abs(dx) + Math.abs(dy); const manhattanDistance = Math.abs(dx) + Math.abs(dy);
// Optimize noise calculation with cached sin/cos values // Pre-compute noise factors
const noise = (Math.sin(x * 0.1) * Math.cos(actualY * 0.1) + 1) * 0.5; const sinX01 = Math.sin(x * 0.1);
const cosY01 = Math.cos(actualY * 0.1);
const noise = (sinX01 * cosY01 + 1) * 0.5;
// Cache canvas dimensions
const canvasWidth = message.fullWidth || width;
const canvasHeight = message.fullHeight || message.height! + (message.startY || 0);
// Get feedback values // Get feedback values
const feedbackValue = this.feedbackSystem.getFeedback(pixelIndex); const feedbackValue = this.feedbackSystem.getFeedback(pixelIndex);
const neighbors = this.feedbackSystem.getNeighborFeedback(pixelIndex, x, y, width, message.fullHeight || message.height! + (message.startY || 0)); const neighbors = this.feedbackSystem.getNeighborFeedback(pixelIndex, x, y, width, canvasHeight);
const momentum = this.feedbackSystem.getMomentum(pixelIndex); const momentum = this.feedbackSystem.getMomentum(pixelIndex);
const laplacian = this.feedbackSystem.getLaplacian(pixelIndex, x, y, width, message.fullHeight || message.height! + (message.startY || 0)); const laplacian = this.feedbackSystem.getLaplacian(pixelIndex, x, y, width, canvasHeight);
const curvature = this.feedbackSystem.getCurvature(pixelIndex, x, y, width, message.fullHeight || message.height! + (message.startY || 0)); const curvature = this.feedbackSystem.getCurvature(pixelIndex, x, y, width, canvasHeight);
const stateValue = this.feedbackSystem.getState(pixelIndex, feedbackValue, deltaTime); const stateValue = this.feedbackSystem.getState(pixelIndex, feedbackValue, deltaTime);
const echoValue = this.feedbackSystem.getEcho(pixelIndex, time); const echoValue = this.feedbackSystem.getEcho(pixelIndex, time);
// Calculate other variables // Calculate other variables
const canvasWidth = message.fullWidth || width;
const canvasHeight = message.fullHeight || message.height! + (message.startY || 0);
const pseudoZ = Math.sin(radius * 0.01 + time) * 50; const pseudoZ = Math.sin(radius * 0.01 + time) * 50;
const jitter = ((x * 73856093 + actualY * 19349663) % 256) / 255; const jitter = ((x * 73856093 + actualY * 19349663) % 256) / 255;
const oscillation = Math.sin(timeTwoPi + radius * 0.1); const oscillation = Math.sin(timeTwoPi + radius * 0.1);
@ -170,7 +180,7 @@ export class PixelRenderer {
const safeValue = isFinite(value) ? value : 0; const safeValue = isFinite(value) ? value : 0;
// Calculate color // Calculate color
const [r, g, b] = this.calculateColor( const color = this.calculateColor(
safeValue, safeValue,
renderMode, renderMode,
valueMode, valueMode,
@ -182,13 +192,13 @@ export class PixelRenderer {
); );
// Set pixel data // Set pixel data
data[i] = r; data[i] = color[0];
data[i + 1] = g; data[i + 1] = color[1];
data[i + 2] = b; data[i + 2] = color[2];
data[i + 3] = 255; data[i + 3] = 255;
// Update feedback system // Update feedback system
this.feedbackSystem.updateFeedback(pixelIndex, r, g, b, deltaTime); this.feedbackSystem.updateFeedback(pixelIndex, color[0], color[1], color[2], deltaTime);
this.feedbackSystem.updateState(pixelIndex, stateValue); this.feedbackSystem.updateState(pixelIndex, stateValue);
} catch (error) { } catch (error) {
@ -213,584 +223,20 @@ export class PixelRenderer {
width: number = 1, width: number = 1,
height: number = 1 height: number = 1
): [number, number, number] { ): [number, number, number] {
// Use optimized strategy pattern for ALL modes
const context: PixelContext = { x, y, width, height, value };
const processor = this.valueModeRegistry.getProcessor(valueMode);
let processedValue: number; let processedValue: number;
switch (valueMode) { if (processor) {
case 'float': const precomputed = ValueModeProcessorRegistry.precomputeContext(context);
// Float mode: treat value as 0.0-1.0, invert it (like original bitfield shaders) processedValue = processor(context, precomputed);
processedValue = Math.max(0, Math.min(1, Math.abs(value))); // Clamp to 0-1 } else {
processedValue = 1 - processedValue; // Invert (like original) // Fallback for unknown modes
processedValue = Math.floor(processedValue * 255); // Convert to 0-255 processedValue = Math.abs(value) % 256;
break;
case 'polar': {
// Polar mode: angular patterns with value-based rotation and radius influence
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 angle = Math.atan2(dy, dx); // -π to π
const normalizedAngle = (angle + Math.PI) / (2 * Math.PI); // 0 to 1
// Combine angle with radius and value for complex patterns
const radiusNorm = radius / Math.max(centerX, centerY);
const spiralEffect =
(normalizedAngle + radiusNorm * 0.5 + Math.abs(value) * 0.02) % 1;
const polarValue = Math.sin(spiralEffect * Math.PI * 8) * 0.5 + 0.5; // Create wave pattern
processedValue = Math.floor(polarValue * 255);
break;
}
case 'distance': {
// Distance mode: concentric patterns with value-based frequency and phase
const distCenterX = width / 2;
const distCenterY = height / 2;
const distance = Math.sqrt(
(x - distCenterX) ** 2 + (y - distCenterY) ** 2
);
const maxDistance = Math.sqrt(distCenterX ** 2 + distCenterY ** 2);
const normalizedDistance = distance / maxDistance; // 0 to 1
// Create concentric waves with value-controlled frequency and phase
const frequency = 8 + Math.abs(value) * 0.1; // Variable frequency
const phase = Math.abs(value) * 0.05; // Value affects phase shift
const concentricWave =
Math.sin(normalizedDistance * Math.PI * frequency + phase) * 0.5 +
0.5;
// Add some radial falloff for more interesting patterns
const falloff = 1 - Math.pow(normalizedDistance, 0.8);
const distanceValue = concentricWave * falloff;
processedValue = Math.floor(distanceValue * 255);
break;
}
case 'wave': {
// Wave mode: interference patterns from multiple wave sources
const baseFreq = 0.08;
const valueScale = Math.abs(value) * 0.001 + 1; // Scale frequency by value
let waveSum = 0;
// Create wave sources at strategic positions for interesting interference
const sources = [
{ x: width * 0.3, y: height * 0.3 },
{ x: width * 0.7, y: height * 0.3 },
{ x: width * 0.5, y: height * 0.7 },
{ x: width * 0.2, y: height * 0.8 },
];
for (const source of sources) {
const dist = Math.sqrt((x - source.x) ** 2 + (y - source.y) ** 2);
const wave = Math.sin(
dist * baseFreq * valueScale + Math.abs(value) * 0.02
);
const amplitude = 1 / (1 + dist * 0.002); // Distance-based amplitude falloff
waveSum += wave * amplitude;
}
// Normalize and enhance contrast
const waveValue = Math.tanh(waveSum) * 0.5 + 0.5; // tanh for better contrast
processedValue = Math.floor(waveValue * 255);
break;
}
case 'fractal': {
// Fractal mode: recursive pattern generation
const scale = 0.01;
let fractalValue = 0;
let amplitude = 1;
const octaves = 4;
for (let i = 0; i < octaves; i++) {
const frequency = Math.pow(2, i) * scale;
const noise =
Math.sin((x + Math.abs(value) * 0.1) * frequency) *
Math.cos((y + Math.abs(value) * 0.1) * frequency);
fractalValue += noise * amplitude;
amplitude *= 0.5;
}
processedValue = Math.floor((fractalValue + 1) * 0.5 * 255);
break;
}
case 'cellular': {
// Cellular automata-inspired patterns
const cellSize = 16;
const cellX = Math.floor(x / cellSize);
const cellY = Math.floor(y / cellSize);
const cellHash =
(cellX * 73856093) ^ (cellY * 19349663) ^ Math.floor(Math.abs(value));
// Generate cellular pattern based on neighbors
let neighbors = 0;
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
if (dx === 0 && dy === 0) continue;
const neighborHash =
((cellX + dx) * 73856093) ^
((cellY + dy) * 19349663) ^
Math.floor(Math.abs(value));
if (neighborHash % 256 > 128) neighbors++;
}
}
const cellState = cellHash % 256 > 128 ? 1 : 0;
const evolution = neighbors >= 3 && neighbors <= 5 ? 1 : cellState;
processedValue = evolution * 255;
break;
}
case 'noise': {
// Perlin-like noise pattern
const noiseScale = 0.02;
const nx = x * noiseScale + Math.abs(value) * 0.001;
const ny = y * noiseScale + Math.abs(value) * 0.001;
// Simple noise approximation using sine waves
const noise1 = Math.sin(nx * 6.28) * Math.cos(ny * 6.28);
const noise2 = Math.sin(nx * 12.56) * Math.cos(ny * 12.56) * 0.5;
const noise3 = Math.sin(nx * 25.12) * Math.cos(ny * 25.12) * 0.25;
const combinedNoise = (noise1 + noise2 + noise3) / 1.75;
processedValue = Math.floor((combinedNoise + 1) * 0.5 * 255);
break;
}
case 'warp': {
// Warp mode: space deformation based on value
const centerX = width / 2;
const centerY = height / 2;
// Create warping field based on value
const warpStrength = Math.abs(value) * 0.001;
const warpFreq = 0.02;
// Calculate warped coordinates
const warpX =
x +
Math.sin(y * warpFreq + Math.abs(value) * 0.01) * warpStrength * 100;
const warpY =
y +
Math.cos(x * warpFreq + Math.abs(value) * 0.01) * warpStrength * 100;
// Create barrel/lens distortion
const dx = warpX - centerX;
const dy = warpY - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
const normDist = dist / maxDist;
// Apply non-linear space deformation
const deform =
1 + Math.sin(normDist * Math.PI + Math.abs(value) * 0.05) * 0.3;
const deformedX = centerX + dx * deform;
const deformedY = centerY + dy * deform;
// Sample from deformed space
const finalValue = (deformedX + deformedY + Math.abs(value)) % 256;
processedValue = Math.floor(Math.abs(finalValue));
break;
}
case 'flow': {
// Flow field mode: large-scale fluid dynamics simulation
const centerX = width / 2;
const centerY = height / 2;
// Create multiple flow sources influenced by value
const flowSources = [
{
x: centerX + Math.sin(Math.abs(value) * 0.01) * 200,
y: centerY + Math.cos(Math.abs(value) * 0.01) * 200,
strength: 1 + Math.abs(value) * 0.01,
},
{
x: centerX + Math.cos(Math.abs(value) * 0.015) * 150,
y: centerY + Math.sin(Math.abs(value) * 0.015) * 150,
strength: -0.8 + Math.sin(Math.abs(value) * 0.02) * 0.5,
},
{
x: centerX + Math.sin(Math.abs(value) * 0.008) * 300,
y: centerY + Math.cos(Math.abs(value) * 0.012) * 250,
strength: 0.6 + Math.cos(Math.abs(value) * 0.018) * 0.4,
},
];
// Calculate flow field at this point
let flowX = 0;
let flowY = 0;
for (const source of flowSources) {
const dx = x - source.x;
const dy = y - source.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const normalizedDist = Math.max(distance, 1); // Avoid division by zero
// Create flow vectors (potential field + curl)
const flowStrength = source.strength / (normalizedDist * 0.01);
// Radial component (attraction/repulsion)
flowX += (dx / normalizedDist) * flowStrength;
flowY += (dy / normalizedDist) * flowStrength;
// Curl component (rotation) - creates vortices
const curlStrength = source.strength * 0.5;
flowX += ((-dy / normalizedDist) * curlStrength) / normalizedDist;
flowY += ((dx / normalizedDist) * curlStrength) / normalizedDist;
}
// Add global flow influenced by value
const globalFlowAngle = Math.abs(value) * 0.02;
flowX += Math.cos(globalFlowAngle) * (Math.abs(value) * 0.1);
flowY += Math.sin(globalFlowAngle) * (Math.abs(value) * 0.1);
// Add turbulence
const turbScale = 0.05;
const turbulence =
Math.sin(x * turbScale + Math.abs(value) * 0.01) *
Math.cos(y * turbScale + Math.abs(value) * 0.015) *
(Math.abs(value) * 0.02);
flowX += turbulence;
flowY += turbulence * 0.7;
// Simulate particle flowing through the field
let particleX = x;
let particleY = y;
// Multiple flow steps for more interesting trajectories
for (let step = 0; step < 5; step++) {
// Sample flow field at current particle position
let localFlowX = 0;
let localFlowY = 0;
for (const source of flowSources) {
const dx = particleX - source.x;
const dy = particleY - source.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const normalizedDist = Math.max(distance, 1);
const flowStrength = source.strength / (normalizedDist * 0.01);
localFlowX += (dx / normalizedDist) * flowStrength;
localFlowY += (dy / normalizedDist) * flowStrength;
// Curl
const curlStrength = source.strength * 0.5;
localFlowX +=
((-dy / normalizedDist) * curlStrength) / normalizedDist;
localFlowY +=
((dx / normalizedDist) * curlStrength) / normalizedDist;
}
// Move particle
const stepSize = 0.5;
particleX += localFlowX * stepSize;
particleY += localFlowY * stepSize;
}
// Calculate final value based on particle's final position and flow magnitude
const flowMagnitude = Math.sqrt(flowX * flowX + flowY * flowY);
const particleDistance = Math.sqrt(
(particleX - x) * (particleX - x) + (particleY - y) * (particleY - y)
);
// Combine flow magnitude with particle trajectory
const flowValue = (flowMagnitude * 10 + particleDistance * 2) % 256;
const enhanced =
Math.sin(flowValue * 0.05 + Math.abs(value) * 0.01) * 0.5 + 0.5;
processedValue = Math.floor(enhanced * 255);
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;
}
case 'diffusion': {
// Heat diffusion simulation - simple rule creates complex emergent patterns
const diffusionRate = 0.1 + Math.abs(value) * 0.0001;
const kernelSize = 3;
const halfKernel = Math.floor(kernelSize / 2);
// Create heat sources based on value
const heatSource = Math.abs(value) * 0.01;
let totalHeat = heatSource;
let sampleCount = 1;
// Sample neighboring pixels and diffuse heat
for (let dy = -halfKernel; dy <= halfKernel; dy++) {
for (let dx = -halfKernel; dx <= halfKernel; dx++) {
if (dx === 0 && dy === 0) continue;
const neighborX = x + dx;
const neighborY = y + dy;
// Check bounds
if (neighborX >= 0 && neighborX < width && neighborY >= 0 && neighborY < height) {
// Use position-based pseudo-random for neighbor heat
const neighborSeed = neighborX + neighborY * width;
const neighborHeat = ((neighborSeed * 1103515245 + 12345) % 256) / 256;
// Distance-based diffusion weight
const distance = Math.sqrt(dx * dx + dy * dy);
const weight = Math.exp(-distance * distance * 0.5);
totalHeat += neighborHeat * weight * diffusionRate;
sampleCount += weight;
}
}
}
// Average heat with temporal decay
const averageHeat = totalHeat / sampleCount;
const decay = 0.95 + Math.sin(Math.abs(value) * 0.01) * 0.04;
// Add convection currents for more complex patterns
const convectionX = Math.sin(x * 0.01 + Math.abs(value) * 0.001) * 0.1;
const convectionY = Math.cos(y * 0.01 + Math.abs(value) * 0.001) * 0.1;
const convection = (convectionX + convectionY) * 0.5 + 0.5;
// Final heat value with non-linear response
const finalHeat = (averageHeat * decay + convection * 0.3) % 1;
const enhancedHeat = Math.pow(finalHeat, 1.2); // Gamma correction for contrast
processedValue = Math.floor(enhancedHeat * 255);
break;
}
case 'cascade': {
// Cascade system - avalanche-like propagation with multiple scales of complexity
const centerX = width / 2;
const centerY = height / 2;
// Create multiple cascade trigger points based on value
const triggerPoints = [
{
x: centerX + Math.sin(Math.abs(value) * 0.01) * 150,
y: centerY + Math.cos(Math.abs(value) * 0.01) * 150,
threshold: 100 + Math.abs(value) * 0.05,
strength: 1.0 + Math.abs(value) * 0.001
},
{
x: centerX + Math.cos(Math.abs(value) * 0.015) * 200,
y: centerY + Math.sin(Math.abs(value) * 0.018) * 120,
threshold: 80 + Math.abs(value) * 0.08,
strength: 0.8 + Math.sin(Math.abs(value) * 0.02) * 0.4
},
{
x: centerX + Math.sin(Math.abs(value) * 0.012) * 180,
y: centerY + Math.cos(Math.abs(value) * 0.008) * 160,
threshold: 120 + Math.abs(value) * 0.03,
strength: 0.6 + Math.cos(Math.abs(value) * 0.025) * 0.3
}
];
let cascadeValue = 0;
const baseValue = Math.abs(value) % 256;
// Process each trigger point
for (const trigger of triggerPoints) {
const dx = x - trigger.x;
const dy = y - trigger.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const maxDistance = Math.sqrt(width * width + height * height);
const normalizedDistance = distance / maxDistance;
// Check if this point would trigger cascade
if (baseValue > trigger.threshold) {
// Create expanding wave from trigger point
const waveFreq = 0.1 + Math.abs(value) * 0.0001;
const wave = Math.sin(distance * waveFreq + Math.abs(value) * 0.02);
// Distance-based amplitude with non-linear falloff
const amplitude = trigger.strength * Math.exp(-distance * distance * 0.000001);
const cascadeWave = wave * amplitude;
// Add interference with perpendicular waves
const perpWave = Math.cos(distance * waveFreq * 1.3 + Math.abs(value) * 0.015);
const interference = cascadeWave + perpWave * amplitude * 0.3;
cascadeValue += interference;
}
}
// Add multi-scale turbulence for organic complexity
let turbulence = 0;
const turbFreq = 0.02 + Math.abs(value) * 0.00005;
for (let octave = 0; octave < 4; octave++) {
const freq = turbFreq * Math.pow(2, octave);
const amplitude = 1.0 / Math.pow(2, octave);
turbulence += Math.sin(x * freq + Math.abs(value) * 0.01) *
Math.cos(y * freq + Math.abs(value) * 0.012) * amplitude;
}
// Create threshold-based cascading effects
const combinedValue = baseValue + cascadeValue * 50 + turbulence * 30;
let finalValue = combinedValue;
// Multi-level cascade thresholds
const thresholds = [150, 100, 200, 175];
for (const threshold of thresholds) {
if (Math.abs(combinedValue) > threshold) {
// Cascade triggered - create amplification
const amplification = (Math.abs(combinedValue) - threshold) * 0.5;
finalValue += amplification;
// Add local distortion when cascade triggers
const distortionX = Math.sin(y * 0.05 + Math.abs(value) * 0.01) * amplification * 0.1;
const distortionY = Math.cos(x * 0.05 + Math.abs(value) * 0.015) * amplification * 0.1;
finalValue += distortionX + distortionY;
}
}
// Add feedback loops for complexity
const feedback = Math.sin(finalValue * 0.02 + Math.abs(value) * 0.005) * 20;
finalValue += feedback;
// Non-linear response for richer patterns
const nonLinear = Math.tanh(finalValue * 0.01) * 128 + 128;
// Final enhancement with edge detection
const edgeDetection = Math.abs(
Math.sin(x * 0.1 + Math.abs(value) * 0.001) -
Math.sin(y * 0.1 + Math.abs(value) * 0.001)
) * 30;
processedValue = Math.floor(Math.max(0, Math.min(255, nonLinear + edgeDetection)));
break;
}
default:
// Integer mode: treat value as 0-255 (original behavior)
processedValue = Math.abs(value) % 256;
break;
} }
return calculateColorDirect(processedValue, renderMode, hueShift); return calculateColorDirect(processedValue, renderMode, hueShift);
} }
} }

View File

@ -0,0 +1,872 @@
import { RGB, MATH } from '../../utils/constants';
export interface PixelContext {
x: number;
y: number;
width: number;
height: number;
value: number;
}
export interface PrecomputedContext {
centerX: number;
centerY: number;
dx: number;
dy: number;
distance: number;
angle: number;
normalizedDistance: number;
normalizedAngle: number;
}
export type ValueModeProcessor = (
context: PixelContext,
precomputed: PrecomputedContext
) => number;
export class ValueModeProcessorRegistry {
private static instance: ValueModeProcessorRegistry;
private processors: Map<string, ValueModeProcessor> = new Map();
private constructor() {
this.initializeProcessors();
}
static getInstance(): ValueModeProcessorRegistry {
if (!ValueModeProcessorRegistry.instance) {
ValueModeProcessorRegistry.instance = new ValueModeProcessorRegistry();
}
return ValueModeProcessorRegistry.instance;
}
getProcessor(mode: string): ValueModeProcessor | undefined {
return this.processors.get(mode);
}
private initializeProcessors(): void {
this.processors.set('integer', this.integerMode);
this.processors.set('float', this.floatMode);
this.processors.set('polar', this.polarMode);
this.processors.set('distance', this.distanceMode);
this.processors.set('wave', this.waveMode);
this.processors.set('fractal', this.fractalMode);
this.processors.set('cellular', this.cellularMode);
this.processors.set('noise', this.noiseMode);
this.processors.set('warp', this.warpMode);
this.processors.set('flow', this.flowMode);
this.processors.set('spiral', this.spiralMode);
this.processors.set('turbulence', this.turbulenceMode);
this.processors.set('crystal', this.crystalMode);
this.processors.set('marble', this.marbleMode);
this.processors.set('quantum', this.quantumMode);
this.processors.set('logarithmic', this.logarithmicMode);
this.processors.set('mirror', this.mirrorMode);
this.processors.set('rings', this.ringsMode);
this.processors.set('mesh', this.meshMode);
this.processors.set('glitch', this.glitchMode);
this.processors.set('diffusion', this.diffusionMode);
this.processors.set('cascade', this.cascadeMode);
this.processors.set('echo', this.echoMode);
this.processors.set('mosh', this.moshMode);
this.processors.set('fold', this.foldMode);
}
static precomputeContext(context: PixelContext): PrecomputedContext {
const centerX = context.width * 0.5;
const centerY = context.height * 0.5;
const dx = context.x - centerX;
const dy = context.y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
const normalizedDistance = distance / maxDistance;
const normalizedAngle = (angle + Math.PI) / MATH.TWO_PI;
return {
centerX,
centerY,
dx,
dy,
distance,
angle,
normalizedDistance,
normalizedAngle,
};
}
private integerMode = (context: PixelContext): number => {
return Math.abs(context.value);
};
private floatMode = (context: PixelContext): number => {
let processedValue = Math.max(0, Math.min(1, Math.abs(context.value)));
processedValue = 1 - processedValue;
return Math.floor(processedValue * RGB.MAX_VALUE);
};
private polarMode = (
context: PixelContext,
precomputed: PrecomputedContext
): number => {
const radiusNorm = precomputed.normalizedDistance;
const spiralEffect =
(precomputed.normalizedAngle + radiusNorm * 0.5 + Math.abs(context.value) * 0.02) % 1;
const polarValue = Math.sin(spiralEffect * Math.PI * 8) * 0.5 + 0.5;
return Math.floor(polarValue * RGB.MAX_VALUE);
};
private distanceMode = (
context: PixelContext,
precomputed: PrecomputedContext
): number => {
const frequency = 8 + Math.abs(context.value) * 0.1;
const phase = Math.abs(context.value) * 0.05;
const concentricWave =
Math.sin(precomputed.normalizedDistance * Math.PI * frequency + phase) * 0.5 + 0.5;
const falloff = 1 - Math.pow(precomputed.normalizedDistance, 0.8);
const distanceValue = concentricWave * falloff;
return Math.floor(distanceValue * RGB.MAX_VALUE);
};
private waveMode = (
context: PixelContext,
precomputed: PrecomputedContext
): number => {
const baseFreq = 0.08;
const valueScale = Math.abs(context.value) * 0.001 + 1;
let waveSum = 0;
const sources = WaveConstants.SOURCES;
const scaledSources = sources.map(source => ({
x: context.width * source.x,
y: context.height * source.y,
}));
for (const source of scaledSources) {
const dx = context.x - source.x;
const dy = context.y - source.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const wave = Math.sin(dist * baseFreq * valueScale + Math.abs(context.value) * 0.02);
const amplitude = 1 / (1 + dist * 0.002);
waveSum += wave * amplitude;
}
const waveValue = Math.tanh(waveSum) * 0.5 + 0.5;
return Math.floor(waveValue * RGB.MAX_VALUE);
};
private fractalMode = (context: PixelContext, _precomputed: PrecomputedContext): number => {
const scale = 0.01;
let fractalValue = 0;
let frequency = 1;
let amplitude = 1;
for (let i = 0; i < 4; i++) {
const nx = context.x * scale * frequency + Math.abs(context.value) * 0.01;
const ny = context.y * scale * frequency;
const noise = this.simplexNoise(nx, ny);
fractalValue += noise * amplitude;
frequency *= 2;
amplitude *= 0.5;
}
fractalValue = (fractalValue + 1) * 0.5;
return Math.floor(fractalValue * RGB.MAX_VALUE);
};
private cellularMode = (context: PixelContext): number => {
const cellSize = 8;
const cellX = Math.floor(context.x / cellSize);
const cellY = Math.floor(context.y / cellSize);
let liveNeighbors = 0;
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
if (dx === 0 && dy === 0) continue;
const nx = cellX + dx;
const ny = cellY + dy;
const neighborValue = Math.abs(
this.pseudoRandom(nx * 73856093 + ny * 19349663 + Math.floor(Math.abs(context.value)))
);
if (neighborValue > 0.5) liveNeighbors++;
}
}
const cellValue = liveNeighbors >= 4 ? 1 : 0;
return Math.floor(cellValue * RGB.MAX_VALUE);
};
private simplexNoise(x: number, y: number): number {
const F2 = 0.5 * (Math.sqrt(3) - 1);
const G2 = (3 - Math.sqrt(3)) / 6;
const s = (x + y) * F2;
const i = Math.floor(x + s);
const j = Math.floor(y + s);
const t = (i + j) * G2;
const X0 = i - t;
const Y0 = j - t;
const x0 = x - X0;
const y0 = y - Y0;
const i1 = x0 > y0 ? 1 : 0;
const j1 = x0 > y0 ? 0 : 1;
const x1 = x0 - i1 + G2;
const y1 = y0 - j1 + G2;
const x2 = x0 - 1 + 2 * G2;
const y2 = y0 - 1 + 2 * G2;
const ii = i & 255;
const jj = j & 255;
const gi0 = this.permMod12[ii + this.perm[jj]] % 12;
const gi1 = this.permMod12[ii + i1 + this.perm[jj + j1]] % 12;
const gi2 = this.permMod12[ii + 1 + this.perm[jj + 1]] % 12;
let n0, n1, n2;
let t0 = 0.5 - x0 * x0 - y0 * y0;
if (t0 < 0) n0 = 0;
else {
t0 *= t0;
n0 = t0 * t0 * this.dot(this.grad3[gi0], x0, y0);
}
let t1 = 0.5 - x1 * x1 - y1 * y1;
if (t1 < 0) n1 = 0;
else {
t1 *= t1;
n1 = t1 * t1 * this.dot(this.grad3[gi1], x1, y1);
}
let t2 = 0.5 - x2 * x2 - y2 * y2;
if (t2 < 0) n2 = 0;
else {
t2 *= t2;
n2 = t2 * t2 * this.dot(this.grad3[gi2], x2, y2);
}
return 70 * (n0 + n1 + n2);
}
private pseudoRandom(seed: number): number {
const x = Math.sin(seed) * 10000;
return x - Math.floor(x);
}
private dot(g: number[], x: number, y: number): number {
return g[0] * x + g[1] * y;
}
private grad3 = [
[1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0],
[1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1],
[0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1]
];
private perm = [
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,
247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,
57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,
60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,
65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,
200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64,
52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212,
207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,
119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,
218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,
81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,
247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,
57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,
60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,
65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,
200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64,
52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212,
207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,
119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,
218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,
81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
];
private permMod12 = this.perm.map(p => p % 12);
private noiseMode = (context: PixelContext): number => {
const noiseScale = 0.02;
const nx = context.x * noiseScale + Math.abs(context.value) * 0.001;
const ny = context.y * noiseScale + Math.abs(context.value) * 0.001;
const noise1 = Math.sin(nx * 6.28) * Math.cos(ny * 6.28);
const noise2 = Math.sin(nx * 12.56) * Math.cos(ny * 12.56) * 0.5;
const noise3 = Math.sin(nx * 25.12) * Math.cos(ny * 25.12) * 0.25;
const combinedNoise = (noise1 + noise2 + noise3) / 1.75;
return Math.floor((combinedNoise + 1) * 0.5 * RGB.MAX_VALUE);
};
private warpMode = (context: PixelContext, precomputed: PrecomputedContext): number => {
const warpStrength = Math.abs(context.value) * 0.001;
const warpFreq = 0.02;
const warpX = context.x + Math.sin(context.y * warpFreq + Math.abs(context.value) * 0.01) * warpStrength * 100;
const warpY = context.y + Math.cos(context.x * warpFreq + Math.abs(context.value) * 0.01) * warpStrength * 100;
const dx = warpX - precomputed.centerX;
const dy = warpY - precomputed.centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
const maxDist = Math.sqrt(precomputed.centerX * precomputed.centerX + precomputed.centerY * precomputed.centerY);
const normDist = dist / maxDist;
const deform = 1 + Math.sin(normDist * Math.PI + Math.abs(context.value) * 0.05) * 0.3;
const deformedX = precomputed.centerX + dx * deform;
const deformedY = precomputed.centerY + dy * deform;
const finalValue = (deformedX + deformedY + Math.abs(context.value)) % 256;
return Math.floor(Math.abs(finalValue));
};
private flowMode = (context: PixelContext, precomputed: PrecomputedContext): number => {
const flowSources = [
{
x: precomputed.centerX + Math.sin(Math.abs(context.value) * 0.01) * 200,
y: precomputed.centerY + Math.cos(Math.abs(context.value) * 0.01) * 200,
strength: 1 + Math.abs(context.value) * 0.01,
},
{
x: precomputed.centerX + Math.cos(Math.abs(context.value) * 0.015) * 150,
y: precomputed.centerY + Math.sin(Math.abs(context.value) * 0.015) * 150,
strength: -0.8 + Math.sin(Math.abs(context.value) * 0.02) * 0.5,
},
{
x: precomputed.centerX + Math.sin(Math.abs(context.value) * 0.008) * 300,
y: precomputed.centerY + Math.cos(Math.abs(context.value) * 0.012) * 250,
strength: 0.6 + Math.cos(Math.abs(context.value) * 0.018) * 0.4,
},
];
let flowX = 0;
let flowY = 0;
for (const source of flowSources) {
const dx = context.x - source.x;
const dy = context.y - source.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const normalizedDist = Math.max(distance, 1);
const flowStrength = source.strength / (normalizedDist * 0.01);
flowX += (dx / normalizedDist) * flowStrength;
flowY += (dy / normalizedDist) * flowStrength;
const curlStrength = source.strength * 0.5;
flowX += ((-dy / normalizedDist) * curlStrength) / normalizedDist;
flowY += ((dx / normalizedDist) * curlStrength) / normalizedDist;
}
const globalFlowAngle = Math.abs(context.value) * 0.02;
flowX += Math.cos(globalFlowAngle) * (Math.abs(context.value) * 0.1);
flowY += Math.sin(globalFlowAngle) * (Math.abs(context.value) * 0.1);
const turbScale = 0.05;
const turbulence = Math.sin(context.x * turbScale + Math.abs(context.value) * 0.01) *
Math.cos(context.y * turbScale + Math.abs(context.value) * 0.015) *
(Math.abs(context.value) * 0.02);
flowX += turbulence;
flowY += turbulence * 0.7;
let particleX = context.x;
let particleY = context.y;
for (let step = 0; step < 5; step++) {
let localFlowX = 0;
let localFlowY = 0;
for (const source of flowSources) {
const dx = particleX - source.x;
const dy = particleY - source.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const normalizedDist = Math.max(distance, 1);
const flowStrength = source.strength / (normalizedDist * 0.01);
localFlowX += (dx / normalizedDist) * flowStrength;
localFlowY += (dy / normalizedDist) * flowStrength;
const curlStrength = source.strength * 0.5;
localFlowX += ((-dy / normalizedDist) * curlStrength) / normalizedDist;
localFlowY += ((dx / normalizedDist) * curlStrength) / normalizedDist;
}
const stepSize = 0.5;
particleX += localFlowX * stepSize;
particleY += localFlowY * stepSize;
}
const flowMagnitude = Math.sqrt(flowX * flowX + flowY * flowY);
const particleDistance = Math.sqrt(
(particleX - context.x) * (particleX - context.x) + (particleY - context.y) * (particleY - context.y)
);
const flowValue = (flowMagnitude * 10 + particleDistance * 2) % 256;
const enhanced = Math.sin(flowValue * 0.05 + Math.abs(context.value) * 0.01) * 0.5 + 0.5;
return Math.floor(enhanced * RGB.MAX_VALUE);
};
private spiralMode = (context: PixelContext, precomputed: PrecomputedContext): number => {
const spiralTightness = 1 + Math.abs(context.value) * 0.01;
const spiralValue = precomputed.angle + Math.log(Math.max(precomputed.distance, 1)) * spiralTightness;
return Math.floor((Math.sin(spiralValue) * 0.5 + 0.5) * RGB.MAX_VALUE);
};
private turbulenceMode = (context: PixelContext): number => {
let turbulence = 0;
const chaos = Math.abs(context.value) * 0.001;
for (let i = 0; i < 4; i++) {
const freq = Math.pow(2, i) * (0.01 + chaos);
turbulence += Math.abs(Math.sin(context.x * freq) * Math.cos(context.y * freq)) / Math.pow(2, i);
}
return Math.floor(Math.min(turbulence, 1) * RGB.MAX_VALUE);
};
private crystalMode = (context: PixelContext): number => {
const latticeSize = 32 + Math.abs(context.value) * 0.1;
const gridX = Math.floor(context.x / latticeSize);
const gridY = Math.floor(context.y / latticeSize);
const crystal = Math.sin(gridX + gridY + Math.abs(context.value) * 0.01) *
Math.cos(gridX * gridY + Math.abs(context.value) * 0.005);
return Math.floor((crystal * 0.5 + 0.5) * RGB.MAX_VALUE);
};
private marbleMode = (context: PixelContext): number => {
const noiseFreq = 0.005 + Math.abs(context.value) * 0.00001;
const turbulence = Math.sin(context.x * noiseFreq) * Math.cos(context.y * noiseFreq) +
Math.sin(context.x * noiseFreq * 2) * Math.cos(context.y * noiseFreq * 2) * 0.5;
const marble = Math.sin((context.x + turbulence * 50) * 0.02 + Math.abs(context.value) * 0.001);
return Math.floor((marble * 0.5 + 0.5) * RGB.MAX_VALUE);
};
private quantumMode = (context: PixelContext, precomputed: PrecomputedContext): number => {
const uncertainty = Math.abs(context.value) * 0.001;
const distSquared = precomputed.dx * precomputed.dx + precomputed.dy * precomputed.dy;
const sigmaSquared = (100 + uncertainty * 1000);
const probability = Math.exp(-distSquared / (2 * sigmaSquared * sigmaSquared));
const quantum = probability * (1 + Math.sin(context.x * context.y * uncertainty) * 0.5);
return Math.floor(Math.min(quantum, 1) * RGB.MAX_VALUE);
};
private logarithmicMode = (context: PixelContext): number => {
const logValue = Math.log(1 + Math.abs(context.value));
return Math.floor((logValue / Math.log(256)) * RGB.MAX_VALUE);
};
private mirrorMode = (context: PixelContext, precomputed: PrecomputedContext): number => {
const dx = Math.abs(context.x - precomputed.centerX);
const dy = Math.abs(context.y - precomputed.centerY);
const mirrorX = precomputed.centerX + (dx % precomputed.centerX);
const mirrorY = precomputed.centerY + (dy % precomputed.centerY);
const mirrorDistance = Math.sqrt(mirrorX * mirrorX + mirrorY * mirrorY);
const mirrorValue = (Math.abs(context.value) + mirrorDistance) % 256;
return mirrorValue;
};
private ringsMode = (context: PixelContext, precomputed: PrecomputedContext): number => {
const ringSpacing = 20 + Math.abs(context.value) * 0.1;
const rings = Math.sin((precomputed.distance / ringSpacing) * Math.PI * 2);
const interference = Math.sin((precomputed.distance + Math.abs(context.value)) * 0.05);
return Math.floor(((rings * interference) * 0.5 + 0.5) * RGB.MAX_VALUE);
};
private meshMode = (context: PixelContext): number => {
const angle = Math.abs(context.value) * 0.001;
const rotX = context.x * Math.cos(angle) - context.y * Math.sin(angle);
const rotY = context.x * Math.sin(angle) + context.y * Math.cos(angle);
const gridSize = 16 + Math.abs(context.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));
return Math.floor(mesh * RGB.MAX_VALUE);
};
private glitchMode = (context: PixelContext): number => {
const seed = Math.floor(context.x + context.y * context.width + Math.abs(context.value));
const random = ((seed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff;
const glitchThreshold = 0.95 - Math.abs(context.value) * 0.0001;
let glitchValue = Math.abs(context.value) % 256;
if (random > glitchThreshold) {
glitchValue = (glitchValue << 1) ^ (glitchValue >> 3) ^ ((context.x + context.y) & 0xFF);
}
return glitchValue % 256;
};
private diffusionMode = (context: PixelContext): number => {
const diffusionRate = 0.1 + Math.abs(context.value) * 0.0001;
const kernelSize = 3;
const halfKernel = Math.floor(kernelSize / 2);
const heatSource = Math.abs(context.value) * 0.01;
let totalHeat = heatSource;
let sampleCount = 1;
for (let dy = -halfKernel; dy <= halfKernel; dy++) {
for (let dx = -halfKernel; dx <= halfKernel; dx++) {
if (dx === 0 && dy === 0) continue;
const neighborX = context.x + dx;
const neighborY = context.y + dy;
if (neighborX >= 0 && neighborX < context.width && neighborY >= 0 && neighborY < context.height) {
const neighborSeed = neighborX + neighborY * context.width;
const neighborHeat = ((neighborSeed * 1103515245 + 12345) % 256) / 256;
const distance = Math.sqrt(dx * dx + dy * dy);
const weight = Math.exp(-distance * distance * 0.5);
totalHeat += neighborHeat * weight * diffusionRate;
sampleCount += weight;
}
}
}
const averageHeat = totalHeat / sampleCount;
const decay = 0.95 + Math.sin(Math.abs(context.value) * 0.01) * 0.04;
const convectionX = Math.sin(context.x * 0.01 + Math.abs(context.value) * 0.001) * 0.1;
const convectionY = Math.cos(context.y * 0.01 + Math.abs(context.value) * 0.001) * 0.1;
const convection = (convectionX + convectionY) * 0.5 + 0.5;
const finalHeat = (averageHeat * decay + convection * 0.3) % 1;
const enhancedHeat = Math.pow(finalHeat, 1.2);
return Math.floor(enhancedHeat * RGB.MAX_VALUE);
};
private cascadeMode = (context: PixelContext, precomputed: PrecomputedContext): number => {
const triggerPoints = [
{
x: precomputed.centerX + Math.sin(Math.abs(context.value) * 0.01) * 150,
y: precomputed.centerY + Math.cos(Math.abs(context.value) * 0.01) * 150,
threshold: 100 + Math.abs(context.value) * 0.05,
strength: 1.0 + Math.abs(context.value) * 0.001
},
{
x: precomputed.centerX + Math.cos(Math.abs(context.value) * 0.015) * 200,
y: precomputed.centerY + Math.sin(Math.abs(context.value) * 0.018) * 120,
threshold: 80 + Math.abs(context.value) * 0.08,
strength: 0.8 + Math.sin(Math.abs(context.value) * 0.02) * 0.4
},
{
x: precomputed.centerX + Math.sin(Math.abs(context.value) * 0.012) * 180,
y: precomputed.centerY + Math.cos(Math.abs(context.value) * 0.008) * 160,
threshold: 120 + Math.abs(context.value) * 0.03,
strength: 0.6 + Math.cos(Math.abs(context.value) * 0.025) * 0.3
}
];
let cascadeValue = 0;
const baseValue = Math.abs(context.value) % 256;
for (const trigger of triggerPoints) {
const dx = context.x - trigger.x;
const dy = context.y - trigger.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const maxDistance = Math.sqrt(context.width * context.width + context.height * context.height);
const normalizedDistance = distance / maxDistance;
if (baseValue > trigger.threshold) {
const waveFreq = 0.1 + Math.abs(context.value) * 0.0001;
const wave = Math.sin(distance * waveFreq + Math.abs(context.value) * 0.02);
const amplitude = trigger.strength * Math.exp(-distance * distance * 0.000001);
const cascadeWave = wave * amplitude;
const perpWave = Math.cos(distance * waveFreq * 1.3 + Math.abs(context.value) * 0.015);
const interference = cascadeWave + perpWave * amplitude * 0.3;
cascadeValue += interference;
}
}
let turbulence = 0;
const turbFreq = 0.02 + Math.abs(context.value) * 0.00005;
for (let octave = 0; octave < 4; octave++) {
const freq = turbFreq * Math.pow(2, octave);
const amplitude = 1.0 / Math.pow(2, octave);
turbulence += Math.sin(context.x * freq + Math.abs(context.value) * 0.01) *
Math.cos(context.y * freq + Math.abs(context.value) * 0.012) * amplitude;
}
const combinedValue = baseValue + cascadeValue * 50 + turbulence * 30;
let finalValue = combinedValue;
const thresholds = [150, 100, 200, 175];
for (const threshold of thresholds) {
if (Math.abs(combinedValue) > threshold) {
const amplification = (Math.abs(combinedValue) - threshold) * 0.5;
finalValue += amplification;
const distortionX = Math.sin(context.y * 0.05 + Math.abs(context.value) * 0.01) * amplification * 0.1;
const distortionY = Math.cos(context.x * 0.05 + Math.abs(context.value) * 0.015) * amplification * 0.1;
finalValue += distortionX + distortionY;
}
}
const feedback = Math.sin(finalValue * 0.02 + Math.abs(context.value) * 0.005) * 20;
finalValue += feedback;
const nonLinear = Math.tanh(finalValue * 0.01) * 128 + 128;
const edgeDetection = Math.abs(
Math.sin(context.x * 0.1 + Math.abs(context.value) * 0.001) -
Math.sin(context.y * 0.1 + Math.abs(context.value) * 0.001)
) * 30;
return Math.floor(Math.max(0, Math.min(RGB.MAX_VALUE, nonLinear + edgeDetection)));
};
private echoMode = (context: PixelContext): number => {
const baseValue = Math.abs(context.value) % 256;
const echoSources = [
{ delay: 0.1, decay: 0.8, spatial: 0.02, twist: 1.0 },
{ delay: 0.25, decay: 0.6, spatial: 0.05, twist: -0.7 },
{ delay: 0.4, decay: 0.45, spatial: 0.03, twist: 1.3 },
{ delay: 0.6, decay: 0.3, spatial: 0.08, twist: -0.9 }
];
let echoSum = baseValue;
let totalWeight = 1.0;
for (const echo of echoSources) {
const spatialOffsetX = Math.sin(context.x * echo.spatial + echo.twist) * echo.delay * 100;
const spatialOffsetY = Math.cos(context.y * echo.spatial + echo.twist * 0.7) * echo.delay * 80;
const offsetX = context.x + spatialOffsetX;
const offsetY = context.y + spatialOffsetY;
const offsetSeed = Math.floor(offsetX) + Math.floor(offsetY) * context.width;
const offsetNoise = ((offsetSeed * 1103515245 + 12345) % 256) / 256;
const delayedValue = (baseValue * (1 - echo.delay) + offsetNoise * 255 * echo.delay) % 256;
const harmonic = Math.sin(delayedValue * 0.05 + echo.twist) * 30;
const distortedEcho = delayedValue + harmonic;
const feedback = Math.sin(distortedEcho * 0.02 + context.x * 0.01 + context.y * 0.01) * echo.decay * 40;
const finalEcho = distortedEcho + feedback;
echoSum += finalEcho * echo.decay;
totalWeight += echo.decay;
}
const interference = Math.sin(echoSum * 0.03) * Math.cos(baseValue * 0.04) * 25;
echoSum += interference;
const compressed = Math.tanh(echoSum / totalWeight * 0.02) * RGB.MAX_VALUE;
return Math.floor(Math.max(0, Math.min(RGB.MAX_VALUE, compressed)));
};
private moshMode = (context: PixelContext): number => {
const baseValue = Math.abs(context.value) % 256;
const pseudoTime = (context.x + context.y + baseValue) * 0.1;
const temporalDrift = Math.floor(pseudoTime * 100) % 1024;
const microJitter = Math.sin(pseudoTime * 20) * 0.8;
const blockSize = 8;
const driftedX = context.x + Math.sin(temporalDrift * 0.01 + context.y * 0.1) * microJitter;
const driftedY = context.y + Math.cos(temporalDrift * 0.008 + context.x * 0.12) * microJitter;
const blockX = Math.floor(driftedX / blockSize);
const blockY = Math.floor(driftedY / blockSize);
const blockId = blockX + blockY * Math.floor(context.width / blockSize);
const corruptionLevel = (baseValue / 255.0) * 0.8 + 0.1;
const blockSeed = blockId * 1103515245 + baseValue + temporalDrift;
const blockRandom = ((blockSeed % 65536) / 65536);
let corruptedValue = baseValue;
if (blockRandom < corruptionLevel * 0.3) {
const motionX = ((blockSeed >> 8) & 7) - 4 + Math.sin(temporalDrift * 0.02) * 0.5;
const motionY = ((blockSeed >> 11) & 7) - 4 + Math.cos(temporalDrift * 0.018) * 0.5;
const sourceX = context.x + motionX * 2;
const sourceY = context.y + motionY * 2;
if (sourceX >= 0 && sourceX < context.width && sourceY >= 0 && sourceY < context.height) {
const sourceSeed = Math.floor(sourceX) + Math.floor(sourceY) * context.width;
const sourceNoise = ((sourceSeed * 1664525 + 1013904223) % 256) / 256;
corruptedValue = Math.floor(sourceNoise * 255);
}
}
else if (blockRandom < corruptionLevel * 0.6) {
const localX = context.x % blockSize;
const localY = context.y % blockSize;
const dctFreqX = Math.floor(localX / 2);
const dctFreqY = Math.floor(localY / 2);
const dctCoeff = Math.sin((dctFreqX + dctFreqY) * Math.PI / 4);
const corruption = dctCoeff * (blockRandom - 0.5) * 100;
corruptedValue = Math.floor(baseValue + corruption);
}
else if (blockRandom < corruptionLevel * 0.8) {
const bleedIntensity = (blockRandom - 0.6) * 5;
const temporalPhase = temporalDrift * 0.03;
const bleedX = Math.sin(context.x * 0.1 + baseValue * 0.05 + temporalPhase) * bleedIntensity;
const bleedY = Math.cos(context.y * 0.1 + baseValue * 0.03 + temporalPhase * 0.8) * bleedIntensity;
corruptedValue = Math.floor(baseValue + bleedX + bleedY);
}
else if (blockRandom < corruptionLevel) {
const quantLevels = 8 + Math.floor((1 - corruptionLevel) * 16);
const quantStep = 256 / quantLevels;
corruptedValue = Math.floor(baseValue / quantStep) * quantStep;
const quantNoise = ((blockSeed >> 16) & 15) - 8;
corruptedValue += quantNoise;
}
if (blockRandom > 0.95) {
const tempSeed = context.x * 73856093 + context.y * 19349663 + baseValue;
const tempNoise = ((tempSeed % 256) / 256);
const mixRatio = corruptionLevel * 0.7;
corruptedValue = Math.floor(corruptedValue * (1 - mixRatio) + tempNoise * 255 * mixRatio);
}
const ringFreq = 0.3 + corruptionLevel * 0.5;
const temporalRingPhase = temporalDrift * 0.01;
const ringing = Math.sin(context.x * ringFreq + temporalRingPhase) * Math.cos(context.y * ringFreq + temporalRingPhase * 0.7) * corruptionLevel * 15;
corruptedValue += ringing;
const edgeDetect = Math.abs(Math.sin(context.x * 0.2 + temporalDrift * 0.005)) + Math.abs(Math.sin(context.y * 0.2 + temporalDrift * 0.007));
if (edgeDetect > 1.5) {
const mosquitoNoise = ((blockSeed >> 20) & 31) - 16 + Math.sin(temporalDrift * 0.1) * 3;
corruptedValue += mosquitoNoise * corruptionLevel;
}
return Math.floor(Math.max(0, Math.min(RGB.MAX_VALUE, corruptedValue)));
};
private foldMode = (context: PixelContext, precomputed: PrecomputedContext): number => {
const baseValue = Math.abs(context.value) % 256;
const normalizedValue = baseValue / 255.0;
const foldLines = [
{
position: 0.2 + Math.sin(context.x * 0.01 + normalizedValue * 4) * 0.15,
angle: Math.PI * 0.25 + normalizedValue * Math.PI * 0.5,
strength: 1.0,
type: 'valley'
},
{
position: 0.5 + Math.cos(context.y * 0.008 + normalizedValue * 3) * 0.2,
angle: Math.PI * 0.75 + Math.sin(normalizedValue * 6) * Math.PI * 0.3,
strength: 0.8,
type: 'mountain'
},
{
position: 0.75 + Math.sin((context.x + context.y) * 0.005 + normalizedValue * 2) * 0.1,
angle: Math.PI * 1.1 + Math.cos(normalizedValue * 8) * Math.PI * 0.4,
strength: 0.6,
type: 'valley'
},
{
position: 0.35 + Math.cos(context.x * 0.012 - context.y * 0.008 + normalizedValue * 5) * 0.18,
angle: Math.PI * 1.5 + normalizedValue * Math.PI,
strength: 0.9,
type: 'mountain'
}
];
let foldedValue = normalizedValue;
let geometryComplexity = 1.0;
for (const fold of foldLines) {
const cos_a = Math.cos(fold.angle);
const sin_a = Math.sin(fold.angle);
const rotX = (context.x - precomputed.centerX) * cos_a + (context.y - precomputed.centerY) * sin_a;
const rotY = -(context.x - precomputed.centerX) * sin_a + (context.y - precomputed.centerY) * cos_a;
const foldDistance = Math.abs(rotY) / context.height;
const foldPosition = (rotX / context.width + 1) * 0.5;
const foldSide = Math.sign(rotY);
if (Math.abs(foldedValue - fold.position) < 0.3) {
const foldInfluence = Math.exp(-foldDistance * 8) * fold.strength;
if (fold.type === 'valley') {
if (foldedValue > fold.position) {
const excess = foldedValue - fold.position;
const foldedExcess = excess * (1 - foldInfluence) - excess * foldInfluence * 0.5;
foldedValue = fold.position + foldedExcess;
} else {
const deficit = fold.position - foldedValue;
const foldedDeficit = deficit * (1 - foldInfluence) - deficit * foldInfluence * 0.5;
foldedValue = fold.position - foldedDeficit;
}
} else {
if (foldedValue > fold.position) {
const excess = foldedValue - fold.position;
const expandedExcess = excess * (1 + foldInfluence * 0.8);
foldedValue = fold.position + expandedExcess;
} else {
const deficit = fold.position - foldedValue;
const expandedDeficit = deficit * (1 + foldInfluence * 0.8);
foldedValue = fold.position - expandedDeficit;
}
}
const creaseSharpness = Math.exp(-Math.abs(foldedValue - fold.position) * 20) * fold.strength;
const creaseEffect = Math.sin(foldPosition * Math.PI * 8 + fold.angle) * creaseSharpness * 0.1;
foldedValue += creaseEffect;
geometryComplexity *= (1 + foldInfluence * 0.3);
}
}
const recursiveFolds = 3;
for (let r = 0; r < recursiveFolds; r++) {
const recursiveScale = Math.pow(0.6, r);
const recursiveFreq = Math.pow(2, r + 2);
const recursiveFoldPos = 0.5 + Math.sin(foldedValue * Math.PI * recursiveFreq + r) * 0.2 * recursiveScale;
if (Math.abs(foldedValue - recursiveFoldPos) < 0.1 * recursiveScale) {
const recursiveInfluence = Math.exp(-Math.abs(foldedValue - recursiveFoldPos) * 30 / recursiveScale) * recursiveScale;
const microFold = (foldedValue - recursiveFoldPos) * (1 - recursiveInfluence * 0.7);
foldedValue = recursiveFoldPos + microFold;
const microCrease = Math.sin(foldedValue * Math.PI * recursiveFreq * 4) * recursiveInfluence * 0.05;
foldedValue += microCrease;
}
}
const distortion = Math.sin(foldedValue * Math.PI * geometryComplexity) * Math.cos(geometryComplexity * 2) * 0.15;
foldedValue += distortion;
const edgeDetection = Math.abs(Math.sin(foldedValue * Math.PI * 16)) * 0.2;
const edgeEnhancement = Math.pow(edgeDetection, 2) * geometryComplexity * 0.1;
foldedValue += edgeEnhancement;
const materialResponse = Math.tanh(foldedValue * 3) * 0.85 + 0.15;
const paperTexture = Math.sin(materialResponse * Math.PI * 32 + geometryComplexity) * 0.05;
const finalValue = (materialResponse + paperTexture) * RGB.MAX_VALUE;
return Math.floor(Math.max(0, Math.min(RGB.MAX_VALUE, finalValue)));
};
}
class WaveConstants {
static readonly SOURCES = [
{ x: 0.3, y: 0.3 },
{ x: 0.7, y: 0.3 },
{ x: 0.5, y: 0.7 },
{ x: 0.2, y: 0.8 },
];
}

View File

@ -1,7 +1,14 @@
import {
RGB,
HSV,
COLOR_TRANSITIONS,
COLOR_MODE_CONSTANTS,
} from './constants';
export function rgbToHsv(r: number, g: number, b: number): [number, number, number] { export function rgbToHsv(r: number, g: number, b: number): [number, number, number] {
r /= 255; r /= RGB.MAX_VALUE;
g /= 255; g /= RGB.MAX_VALUE;
b /= 255; b /= RGB.MAX_VALUE;
const max = Math.max(r, g, b); const max = Math.max(r, g, b);
const min = Math.min(r, g, b); const min = Math.min(r, g, b);
@ -13,13 +20,13 @@ export function rgbToHsv(r: number, g: number, b: number): [number, number, numb
if (delta !== 0) { if (delta !== 0) {
if (max === r) { if (max === r) {
h = ((g - b) / delta) % 6; h = ((g - b) / delta) % HSV.HUE_SECTORS;
} else if (max === g) { } else if (max === g) {
h = (b - r) / delta + 2; h = (b - r) / delta + HSV.SECTOR_OFFSETS.GREEN;
} else { } else {
h = (r - g) / delta + 4; h = (r - g) / delta + HSV.SECTOR_OFFSETS.BLUE;
} }
h /= 6; h /= HSV.HUE_SECTORS;
} }
if (h < 0) h += 1; if (h < 0) h += 1;
@ -33,30 +40,30 @@ export function hsvToRgb(
v: number v: number
): [number, number, number] { ): [number, number, number] {
const c = v * s; const c = v * s;
const x = c * (1 - Math.abs(((h * 6) % 2) - 1)); const x = c * (1 - Math.abs(((h * HSV.HUE_SECTORS) % 2) - 1));
const m = v - c; const m = v - c;
let r = 0, let r = 0,
g = 0, g = 0,
b = 0; b = 0;
if (h < 1 / 6) { if (h < HSV.SECTOR_BOUNDARIES.SIXTH) {
r = c; r = c;
g = x; g = x;
b = 0; b = 0;
} else if (h < 2 / 6) { } else if (h < HSV.SECTOR_BOUNDARIES.THIRD) {
r = x; r = x;
g = c; g = c;
b = 0; b = 0;
} else if (h < 3 / 6) { } else if (h < HSV.SECTOR_BOUNDARIES.HALF) {
r = 0; r = 0;
g = c; g = c;
b = x; b = x;
} else if (h < 4 / 6) { } else if (h < HSV.SECTOR_BOUNDARIES.TWO_THIRDS) {
r = 0; r = 0;
g = x; g = x;
b = c; b = c;
} else if (h < 5 / 6) { } else if (h < HSV.SECTOR_BOUNDARIES.FIVE_SIXTHS) {
r = x; r = x;
g = 0; g = 0;
b = c; b = c;
@ -67,9 +74,9 @@ export function hsvToRgb(
} }
return [ return [
Math.round((r + m) * 255), Math.round((r + m) * RGB.MAX_VALUE),
Math.round((g + m) * 255), Math.round((g + m) * RGB.MAX_VALUE),
Math.round((b + m) * 255), Math.round((b + m) * RGB.MAX_VALUE),
]; ];
} }
@ -87,7 +94,7 @@ export function applyHueShift(rgb: [number, number, number], hueShiftDegrees: nu
} }
export function rainbowColor(value: number): [number, number, number] { export function rainbowColor(value: number): [number, number, number] {
const phase = (value / 255.0) * 6; const phase = (value / RGB.MAX_VALUE) * COLOR_MODE_CONSTANTS.RAINBOW_PHASE_MULTIPLIER;
const segment = Math.floor(phase); const segment = Math.floor(phase);
const remainder = phase - segment; const remainder = phase - segment;
const t = remainder; const t = remainder;
@ -95,112 +102,142 @@ export function rainbowColor(value: number): [number, number, number] {
switch (segment % 6) { switch (segment % 6) {
case 0: case 0:
return [255, Math.round(t * 255), 0]; return [RGB.MAX_VALUE, Math.round(t * RGB.MAX_VALUE), 0];
case 1: case 1:
return [Math.round(q * 255), 255, 0]; return [Math.round(q * RGB.MAX_VALUE), RGB.MAX_VALUE, 0];
case 2: case 2:
return [0, 255, Math.round(t * 255)]; return [0, RGB.MAX_VALUE, Math.round(t * RGB.MAX_VALUE)];
case 3: case 3:
return [0, Math.round(q * 255), 255]; return [0, Math.round(q * RGB.MAX_VALUE), RGB.MAX_VALUE];
case 4: case 4:
return [Math.round(t * 255), 0, 255]; return [Math.round(t * RGB.MAX_VALUE), 0, RGB.MAX_VALUE];
case 5: case 5:
return [255, 0, Math.round(q * 255)]; return [RGB.MAX_VALUE, 0, Math.round(q * RGB.MAX_VALUE)];
default: default:
return [255, 255, 255]; return [RGB.MAX_VALUE, RGB.MAX_VALUE, RGB.MAX_VALUE];
} }
} }
export function thermalColor(value: number): [number, number, number] { export function thermalColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
if (t < 0.25) { if (t < 0.25) {
return [0, 0, Math.round(t * 4 * 255)]; return [0, 0, Math.round(t * 4 * RGB.MAX_VALUE)];
} else if (t < 0.5) { } else if (t < 0.5) {
return [0, Math.round((t - 0.25) * 4 * 255), 255]; return [0, Math.round((t - 0.25) * 4 * RGB.MAX_VALUE), RGB.MAX_VALUE];
} else if (t < 0.75) { } else if (t < 0.75) {
return [ return [
Math.round((t - 0.5) * 4 * 255), Math.round((t - 0.5) * 4 * RGB.MAX_VALUE),
255, RGB.MAX_VALUE,
Math.round((0.75 - t) * 4 * 255), Math.round((0.75 - t) * 4 * RGB.MAX_VALUE),
]; ];
} else { } else {
return [255, 255, Math.round((t - 0.75) * 4 * 255)]; return [RGB.MAX_VALUE, RGB.MAX_VALUE, Math.round((t - 0.75) * 4 * RGB.MAX_VALUE)];
} }
} }
export function neonColor(value: number): [number, number, number] { export function neonColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
const intensity = Math.pow(Math.sin(t * Math.PI), 2); const intensity = Math.pow(Math.sin(t * Math.PI), 2);
const glow = Math.pow(intensity, 0.5); const glow = Math.pow(intensity, 0.5);
return [ return [
Math.round(glow * 255), Math.round(glow * RGB.MAX_VALUE),
Math.round(intensity * 255), Math.round(intensity * RGB.MAX_VALUE),
Math.round(Math.pow(intensity, 2) * 255), Math.round(Math.pow(intensity, 2) * RGB.MAX_VALUE),
]; ];
} }
export function cyberpunkColor(value: number): [number, number, number] {
const t = value / RGB.MAX_VALUE;
const pulse = Math.sin(t * Math.PI * COLOR_TRANSITIONS.CYBERPUNK.PULSE_FREQUENCY) * COLOR_TRANSITIONS.CYBERPUNK.PULSE_AMPLITUDE + COLOR_TRANSITIONS.CYBERPUNK.PULSE_OFFSET;
if (t < COLOR_TRANSITIONS.CYBERPUNK.LOW) {
return [Math.round(t * 5 * 50), 0, Math.round(t * 5 * 100)];
} else if (t < COLOR_TRANSITIONS.CYBERPUNK.MID) {
const p = (t - COLOR_TRANSITIONS.CYBERPUNK.LOW) / COLOR_TRANSITIONS.CYBERPUNK.LOW;
return [
Math.round(50 + p * 205 * pulse),
Math.round(p * 50),
Math.round(100 + p * 155)
];
} else if (t < COLOR_TRANSITIONS.CYBERPUNK.HIGH) {
const p = (t - COLOR_TRANSITIONS.CYBERPUNK.MID) / (COLOR_TRANSITIONS.CYBERPUNK.HIGH - COLOR_TRANSITIONS.CYBERPUNK.MID);
return [
Math.round(RGB.MAX_VALUE * pulse),
Math.round(50 + p * 205 * pulse),
Math.round(RGB.MAX_VALUE - p * 100)
];
} else {
const p = (t - 0.7) / 0.3;
return [
Math.round((RGB.MAX_VALUE - p * 155) * pulse),
Math.round(RGB.MAX_VALUE * pulse),
Math.round(155 + p * 100 * pulse)
];
}
}
export function sunsetColor(value: number): [number, number, number] { export function sunsetColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
if (t < 0.3) { if (t < 0.3) {
return [Math.round(t * 3.33 * 255), 0, Math.round(t * 1.67 * 255)]; return [Math.round(t * 3.33 * RGB.MAX_VALUE), 0, Math.round(t * 1.67 * RGB.MAX_VALUE)];
} else if (t < 0.6) { } else if (t < 0.6) {
const p = (t - 0.3) / 0.3; const p = (t - 0.3) / 0.3;
return [255, Math.round(p * 100), Math.round(50 * (1 - p))]; return [RGB.MAX_VALUE, Math.round(p * 100), Math.round(50 * (1 - p))];
} else { } else {
const p = (t - 0.6) / 0.4; const p = (t - 0.6) / 0.4;
return [255, Math.round(100 + p * 155), Math.round(p * 100)]; return [RGB.MAX_VALUE, Math.round(100 + p * 155), Math.round(p * 100)];
} }
} }
export function oceanColor(value: number): [number, number, number] { export function oceanColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
if (t < 0.25) { if (t < 0.25) {
return [0, Math.round(t * 2 * 255), Math.round(100 + t * 4 * 155)]; return [0, Math.round(t * 2 * RGB.MAX_VALUE), Math.round(100 + t * 4 * 155)];
} else if (t < 0.5) { } else if (t < 0.5) {
const p = (t - 0.25) / 0.25; const p = (t - 0.25) / 0.25;
return [0, Math.round(128 + p * 127), 255]; return [0, Math.round(128 + p * 127), RGB.MAX_VALUE];
} else if (t < 0.75) { } else if (t < 0.75) {
const p = (t - 0.5) / 0.25; const p = (t - 0.5) / 0.25;
return [Math.round(p * 100), 255, Math.round(255 - p * 100)]; return [Math.round(p * 100), RGB.MAX_VALUE, Math.round(RGB.MAX_VALUE - p * 100)];
} else { } else {
const p = (t - 0.75) / 0.25; const p = (t - 0.75) / 0.25;
return [Math.round(100 + p * 155), 255, Math.round(155 + p * 100)]; return [Math.round(100 + p * 155), RGB.MAX_VALUE, Math.round(155 + p * 100)];
} }
} }
export function forestColor(value: number): [number, number, number] { export function forestColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
if (t < 0.3) { if (t < 0.3) {
return [Math.round(t * 2 * 255), Math.round(50 + t * 3 * 205), 0]; return [Math.round(t * 2 * RGB.MAX_VALUE), Math.round(50 + t * 3 * 205), 0];
} else if (t < 0.6) { } else if (t < 0.6) {
const p = (t - 0.3) / 0.3; const p = (t - 0.3) / 0.3;
return [Math.round(150 - p * 100), 255, Math.round(p * 100)]; return [Math.round(150 - p * 100), RGB.MAX_VALUE, Math.round(p * 100)];
} else { } else {
const p = (t - 0.6) / 0.4; const p = (t - 0.6) / 0.4;
return [Math.round(50 + p * 100), Math.round(255 - p * 100), Math.round(100 + p * 55)]; return [Math.round(50 + p * 100), Math.round(RGB.MAX_VALUE - p * 100), Math.round(100 + p * 55)];
} }
} }
export function copperColor(value: number): [number, number, number] { export function copperColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
if (t < 0.4) { if (t < 0.4) {
return [Math.round(t * 2.5 * 255), Math.round(t * 1.5 * 255), Math.round(t * 0.5 * 255)]; return [Math.round(t * 2.5 * RGB.MAX_VALUE), Math.round(t * 1.5 * RGB.MAX_VALUE), Math.round(t * 0.5 * RGB.MAX_VALUE)];
} else if (t < 0.7) { } else if (t < 0.7) {
const p = (t - 0.4) / 0.3; const p = (t - 0.4) / 0.3;
return [255, Math.round(153 + p * 102), Math.round(51 + p * 51)]; return [RGB.MAX_VALUE, Math.round(153 + p * 102), Math.round(51 + p * 51)];
} else { } else {
const p = (t - 0.7) / 0.3; const p = (t - 0.7) / 0.3;
return [255, 255, Math.round(102 + p * 153)]; return [RGB.MAX_VALUE, RGB.MAX_VALUE, Math.round(102 + p * 153)];
} }
} }
export function ditheredColor(value: number): [number, number, number] { export function ditheredColor(value: number): [number, number, number] {
const levels = 4; const levels = COLOR_MODE_CONSTANTS.DITHER_LEVELS;
const step = 255 / (levels - 1); const step = RGB.MAX_VALUE / (levels - 1);
const quantized = Math.round(value / step) * step; const quantized = Math.round(value / step) * step;
const error = value - quantized; const error = value - quantized;
const dither = (Math.random() - 0.5) * 32; const dither = (Math.random() - 0.5) * COLOR_MODE_CONSTANTS.DITHER_NOISE_AMPLITUDE;
const final = Math.max(0, Math.min(255, quantized + error + dither)); const final = Math.max(RGB.MIN_VALUE, Math.min(RGB.MAX_VALUE, quantized + error + dither));
return [final, final, final]; return [final, final, final];
} }
@ -210,12 +247,12 @@ export function paletteColor(value: number): [number, number, number] {
[87, 29, 149], [87, 29, 149],
[191, 82, 177], [191, 82, 177],
[249, 162, 162], [249, 162, 162],
[255, 241, 165], [RGB.MAX_VALUE, 241, 165],
[134, 227, 206], [134, 227, 206],
[29, 161, 242], [29, 161, 242],
[255, 255, 255], [RGB.MAX_VALUE, RGB.MAX_VALUE, RGB.MAX_VALUE],
]; ];
const index = Math.floor((value / 255.0) * (palette.length - 1)); const index = Math.floor((value / RGB.MAX_VALUE) * (palette.length - 1));
return palette[index] as [number, number, number]; return palette[index] as [number, number, number];
} }
@ -230,12 +267,12 @@ export function vintageColor(value: number): [number, number, number] {
[166, 124, 82], [166, 124, 82],
[245, 222, 179], [245, 222, 179],
]; ];
const index = Math.floor((value / 255.0) * (palette.length - 1)); const index = Math.floor((value / RGB.MAX_VALUE) * (palette.length - 1));
return palette[index] as [number, number, number]; return palette[index] as [number, number, number];
} }
export function plasmaColor(value: number): [number, number, number] { export function plasmaColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
const freq = 2.4; const freq = 2.4;
const phase1 = 0.0; const phase1 = 0.0;
const phase2 = 2.094; const phase2 = 2.094;
@ -246,50 +283,50 @@ export function plasmaColor(value: number): [number, number, number] {
const b = Math.sin(freq * t + phase3) * 0.5 + 0.5; const b = Math.sin(freq * t + phase3) * 0.5 + 0.5;
return [ return [
Math.round(r * 255), Math.round(r * RGB.MAX_VALUE),
Math.round(g * 255), Math.round(g * RGB.MAX_VALUE),
Math.round(b * 255) Math.round(b * RGB.MAX_VALUE)
]; ];
} }
export function fireColor(value: number): [number, number, number] { export function fireColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
if (t < 0.2) { if (t < 0.2) {
return [Math.round(t * 5 * 255), 0, 0]; return [Math.round(t * 5 * RGB.MAX_VALUE), 0, 0];
} else if (t < 0.5) { } else if (t < 0.5) {
const p = (t - 0.2) / 0.3; const p = (t - 0.2) / 0.3;
return [255, Math.round(p * 165), 0]; return [RGB.MAX_VALUE, Math.round(p * 165), 0];
} else if (t < 0.8) { } else if (t < 0.8) {
const p = (t - 0.5) / 0.3; const p = (t - 0.5) / 0.3;
return [255, Math.round(165 + p * 90), Math.round(p * 100)]; return [RGB.MAX_VALUE, Math.round(165 + p * 90), Math.round(p * 100)];
} else { } else {
const p = (t - 0.8) / 0.2; const p = (t - 0.8) / 0.2;
return [255, 255, Math.round(100 + p * 155)]; return [RGB.MAX_VALUE, RGB.MAX_VALUE, Math.round(100 + p * 155)];
} }
} }
export function iceColor(value: number): [number, number, number] { export function iceColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
if (t < 0.25) { if (t < 0.25) {
return [Math.round(t * 2 * 255), Math.round(t * 3 * 255), 255]; return [Math.round(t * 2 * RGB.MAX_VALUE), Math.round(t * 3 * RGB.MAX_VALUE), RGB.MAX_VALUE];
} else if (t < 0.5) { } else if (t < 0.5) {
const p = (t - 0.25) / 0.25; const p = (t - 0.25) / 0.25;
return [Math.round(128 + p * 127), Math.round(192 + p * 63), 255]; return [Math.round(128 + p * 127), Math.round(192 + p * 63), RGB.MAX_VALUE];
} else if (t < 0.75) { } else if (t < 0.75) {
const p = (t - 0.5) / 0.25; const p = (t - 0.5) / 0.25;
return [255, 255, Math.round(255 - p * 100)]; return [RGB.MAX_VALUE, RGB.MAX_VALUE, Math.round(RGB.MAX_VALUE - p * 100)];
} else { } else {
const p = (t - 0.75) / 0.25; const p = (t - 0.75) / 0.25;
return [255, 255, Math.round(155 + p * 100)]; return [RGB.MAX_VALUE, RGB.MAX_VALUE, Math.round(155 + p * 100)];
} }
} }
export function infraredColor(value: number): [number, number, number] { export function infraredColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
const intensity = Math.pow(t, 0.6); const intensity = Math.pow(t, 0.6);
const heat = Math.sin(t * Math.PI * 1.5) * 0.5 + 0.5; const heat = Math.sin(t * Math.PI * 1.5) * 0.5 + 0.5;
const r = Math.round(255 * intensity); const r = Math.round(RGB.MAX_VALUE * intensity);
const g = Math.round(128 * heat * intensity); const g = Math.round(128 * heat * intensity);
const b = Math.round(64 * (1 - intensity) * heat); const b = Math.round(64 * (1 - intensity) * heat);
@ -297,12 +334,12 @@ export function infraredColor(value: number): [number, number, number] {
} }
export function xrayColor(value: number): [number, number, number] { export function xrayColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
const inverted = 1.0 - t; const inverted = 1.0 - t;
const contrast = Math.pow(inverted, 1.8); const contrast = Math.pow(inverted, 1.8);
const glow = Math.sin(t * Math.PI) * 0.3; const glow = Math.sin(t * Math.PI) * 0.3;
const intensity = Math.round(contrast * 255); const intensity = Math.round(contrast * RGB.MAX_VALUE);
const cyan = Math.round((contrast + glow) * 180); const cyan = Math.round((contrast + glow) * 180);
const blue = Math.round((contrast + glow * 0.5) * 120); const blue = Math.round((contrast + glow * 0.5) * 120);
@ -310,7 +347,7 @@ export function xrayColor(value: number): [number, number, number] {
} }
export function spectrumColor(value: number): [number, number, number] { export function spectrumColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
const hue = t * 360; const hue = t * 360;
const saturation = 0.7; const saturation = 0.7;
const lightness = 0.6 + (Math.sin(t * Math.PI * 4) * 0.2); const lightness = 0.6 + (Math.sin(t * Math.PI * 4) * 0.2);
@ -336,14 +373,14 @@ export function spectrumColor(value: number): [number, number, number] {
} }
return [ return [
Math.round((r + m) * 255), Math.round((r + m) * RGB.MAX_VALUE),
Math.round((g + m) * 255), Math.round((g + m) * RGB.MAX_VALUE),
Math.round((b + m) * 255) Math.round((b + m) * RGB.MAX_VALUE)
]; ];
} }
export function acidColor(value: number): [number, number, number] { export function acidColor(value: number): [number, number, number] {
const t = value / 255.0; const t = value / RGB.MAX_VALUE;
const phase = t * Math.PI * 2; const phase = t * Math.PI * 2;
const r = Math.sin(phase) * 0.5 + 0.5; const r = Math.sin(phase) * 0.5 + 0.5;
@ -354,28 +391,115 @@ export function acidColor(value: number): [number, number, number] {
const glow = Math.sin(t * Math.PI * 6) * 0.2 + 0.8; const glow = Math.sin(t * Math.PI * 6) * 0.2 + 0.8;
return [ return [
Math.round(r * intensity * glow * 255), Math.round(r * intensity * glow * RGB.MAX_VALUE),
Math.round(g * intensity * glow * 255), Math.round(g * intensity * glow * RGB.MAX_VALUE),
Math.round(b * intensity * glow * 255) Math.round(b * intensity * glow * RGB.MAX_VALUE)
]; ];
} }
export function palette16Color(value: number): [number, number, number] {
const palette = [
[0, 0, 0], // Black
[RGB.MAX_VALUE, RGB.MAX_VALUE, RGB.MAX_VALUE], // White
[RGB.MAX_VALUE, 0, 0], // Red
[0, RGB.MAX_VALUE, 0], // Green
[0, 0, RGB.MAX_VALUE], // Blue
[RGB.MAX_VALUE, RGB.MAX_VALUE, 0], // Yellow
[RGB.MAX_VALUE, 0, RGB.MAX_VALUE], // Magenta
[0, RGB.MAX_VALUE, RGB.MAX_VALUE], // Cyan
[RGB.MAX_VALUE, 128, 0], // Orange
[128, 0, RGB.MAX_VALUE], // Purple
[0, RGB.MAX_VALUE, 128], // Spring Green
[RGB.MAX_VALUE, 0, 128], // Pink
[128, RGB.MAX_VALUE, 0], // Lime
[0, 128, RGB.MAX_VALUE], // Sky Blue
[128, 128, 128], // Gray
[192, 192, 192] // Light Gray
];
const index = Math.floor((value / RGB.MAX_VALUE) * (palette.length - 1));
return palette[index] as [number, number, number];
}
export function quantumColor(value: number): [number, number, number] {
const palette = [
[0, 0, 0], // Void Black
[128, 0, RGB.MAX_VALUE], // Quantum Purple
[0, RGB.MAX_VALUE, 128], // Energy Green
[RGB.MAX_VALUE, RGB.MAX_VALUE, RGB.MAX_VALUE] // Pure White
];
const index = Math.floor((value / RGB.MAX_VALUE) * (palette.length - 1));
return palette[index] as [number, number, number];
}
export function neonStrikeColor(value: number): [number, number, number] {
const palette = [
[10, 0, 20], // Deep Dark
[RGB.MAX_VALUE, 20, 147], // Hot Pink
[0, RGB.MAX_VALUE, RGB.MAX_VALUE], // Electric Cyan
[RGB.MAX_VALUE, RGB.MAX_VALUE, 0], // Neon Yellow
[RGB.MAX_VALUE, RGB.MAX_VALUE, RGB.MAX_VALUE] // Blinding White
];
const index = Math.floor((value / RGB.MAX_VALUE) * (palette.length - 1));
return palette[index] as [number, number, number];
}
export function eightBitColor(value: number): [number, number, number] {
const levels = 4;
const r = Math.floor((value / RGB.MAX_VALUE) * levels) * (RGB.MAX_VALUE / (levels - 1));
const g = Math.floor(((value * 2) % 256 / RGB.MAX_VALUE) * levels) * (RGB.MAX_VALUE / (levels - 1));
const b = Math.floor(((value * 3) % 256 / RGB.MAX_VALUE) * levels) * (RGB.MAX_VALUE / (levels - 1));
return [
Math.min(RGB.MAX_VALUE, Math.max(0, r)),
Math.min(RGB.MAX_VALUE, Math.max(0, g)),
Math.min(RGB.MAX_VALUE, Math.max(0, b))
];
}
export function silkColor(value: number): [number, number, number] {
const t = value / RGB.MAX_VALUE;
const smoothT = t * t * (3.0 - 2.0 * t);
const r = Math.sin(smoothT * Math.PI * 2.0) * 0.5 + 0.5;
const g = Math.sin(smoothT * Math.PI * 2.0 + Math.PI * 0.66) * 0.5 + 0.5;
const b = Math.sin(smoothT * Math.PI * 2.0 + Math.PI * 1.33) * 0.5 + 0.5;
const fade = Math.pow(smoothT, 0.3);
return [
Math.round(r * fade * RGB.MAX_VALUE),
Math.round(g * fade * RGB.MAX_VALUE),
Math.round(b * fade * RGB.MAX_VALUE)
];
}
export function binaryColor(value: number): [number, number, number] {
const threshold = COLOR_MODE_CONSTANTS.BINARY_THRESHOLD;
return value < threshold
? [RGB.MIN_VALUE, RGB.MIN_VALUE, RGB.MIN_VALUE] // Pure Black
: [RGB.MAX_VALUE, RGB.MAX_VALUE, RGB.MAX_VALUE]; // Pure White
}
export function palette32Color(value: number): [number, number, number] { export function palette32Color(value: number): [number, number, number] {
const palette = [ const palette = [
[0, 0, 0], // Black [0, 0, 0], // Black
[255, 255, 255], // White [RGB.MAX_VALUE, RGB.MAX_VALUE, RGB.MAX_VALUE], // White
[255, 0, 0], // Red [RGB.MAX_VALUE, 0, 0], // Red
[0, 255, 0], // Green [0, RGB.MAX_VALUE, 0], // Green
[0, 0, 255], // Blue [0, 0, RGB.MAX_VALUE], // Blue
[255, 255, 0], // Yellow [RGB.MAX_VALUE, RGB.MAX_VALUE, 0], // Yellow
[255, 0, 255], // Magenta [RGB.MAX_VALUE, 0, RGB.MAX_VALUE], // Magenta
[0, 255, 255], // Cyan [0, RGB.MAX_VALUE, RGB.MAX_VALUE], // Cyan
[255, 128, 0], // Orange [RGB.MAX_VALUE, 128, 0], // Orange
[128, 0, 255], // Purple [128, 0, RGB.MAX_VALUE], // Purple
[0, 255, 128], // Spring Green [0, RGB.MAX_VALUE, 128], // Spring Green
[255, 0, 128], // Pink [RGB.MAX_VALUE, 0, 128], // Pink
[128, 255, 0], // Lime [128, RGB.MAX_VALUE, 0], // Lime
[0, 128, 255], // Sky Blue [0, 128, RGB.MAX_VALUE], // Sky Blue
[128, 128, 128], // Gray [128, 128, 128], // Gray
[192, 192, 192], // Light Gray [192, 192, 192], // Light Gray
[64, 64, 64], // Dark Gray [64, 64, 64], // Dark Gray
@ -385,18 +509,18 @@ export function palette32Color(value: number): [number, number, number] {
[128, 0, 64], // Maroon [128, 0, 64], // Maroon
[64, 0, 128], // Indigo [64, 0, 128], // Indigo
[0, 128, 64], // Teal [0, 128, 64], // Teal
[255, 192, 128], // Peach [RGB.MAX_VALUE, 192, 128], // Peach
[128, 255, 192], // Mint [128, RGB.MAX_VALUE, 192], // Mint
[192, 128, 255], // Lavender [192, 128, RGB.MAX_VALUE], // Lavender
[255, 128, 192], // Rose [RGB.MAX_VALUE, 128, 192], // Rose
[128, 192, 255], // Light Blue [128, 192, RGB.MAX_VALUE], // Light Blue
[192, 255, 128], // Light Green [192, RGB.MAX_VALUE, 128], // Light Green
[64, 32, 16], // Dark Brown [64, 32, 16], // Dark Brown
[16, 64, 32], // Forest [16, 64, 32], // Forest
[32, 16, 64] // Deep Purple [32, 16, 64] // Deep Purple
]; ];
const index = Math.floor((value / 255.0) * (palette.length - 1)); const index = Math.floor((value / RGB.MAX_VALUE) * (palette.length - 1));
return palette[index] as [number, number, number]; return palette[index] as [number, number, number];
} }
@ -408,13 +532,12 @@ const COLOR_PALETTE_REGISTRY: Record<string, (value: number) => [number, number,
green: (value) => [0, value, 0], green: (value) => [0, value, 0],
blue: (value) => [0, 0, value], blue: (value) => [0, 0, value],
rgb: (value) => [value, (value * 2) % 256, (value * 3) % 256], // Same as classic for now rgb: (value) => [value, (value * 2) % 256, (value * 3) % 256], // Same as classic for now
hsv: (value) => rainbowColor(value), // Use rainbow for HSV
forest: forestColor, forest: forestColor,
copper: copperColor, copper: copperColor,
rainbow: rainbowColor, rainbow: rainbowColor,
thermal: thermalColor, thermal: thermalColor,
neon: neonColor, neon: neonColor,
cyberpunk: neonColor, // Use neon for cyberpunk theme cyberpunk: cyberpunkColor,
vaporwave: plasmaColor, // Use plasma for vaporwave theme vaporwave: plasmaColor, // Use plasma for vaporwave theme
sunset: sunsetColor, sunset: sunsetColor,
ocean: oceanColor, ocean: oceanColor,
@ -428,6 +551,12 @@ const COLOR_PALETTE_REGISTRY: Record<string, (value: number) => [number, number,
xray: xrayColor, xray: xrayColor,
spectrum: spectrumColor, spectrum: spectrumColor,
acid: acidColor, acid: acidColor,
quantum: quantumColor,
neonstrike: neonStrikeColor,
eightbit: eightBitColor,
silk: silkColor,
binary: binaryColor,
palette16: palette16Color,
palette32: palette32Color, palette32: palette32Color,
}; };
@ -437,7 +566,7 @@ export function calculateColorDirect(
hueShift: number = 0 hueShift: number = 0
): [number, number, number] { ): [number, number, number] {
const colorFunction = COLOR_PALETTE_REGISTRY[renderMode]; const colorFunction = COLOR_PALETTE_REGISTRY[renderMode];
const color = colorFunction ? colorFunction(absValue) : [absValue, absValue, absValue]; const color = colorFunction ? colorFunction(absValue) : [absValue, absValue, absValue] as [number, number, number];
return applyHueShift(color, hueShift); return applyHueShift(color, hueShift);
} }

View File

@ -19,6 +19,90 @@ export const PERFORMANCE = {
// Color Constants // Color Constants
export const COLOR_TABLE_SIZE = 256; export const COLOR_TABLE_SIZE = 256;
// Color Calculation Constants
export const RGB = {
MAX_VALUE: 255,
MIN_VALUE: 0,
} as const;
// Luminance calculation constants (ITU-R BT.709)
export const LUMINANCE_WEIGHTS = {
RED: 0.299,
GREEN: 0.587,
BLUE: 0.114,
} as const;
// HSV Color Constants
export const HSV = {
HUE_SECTORS: 6,
HUE_MAX_DEGREES: 360,
SECTOR_OFFSETS: {
GREEN: 2,
BLUE: 4,
},
SECTOR_BOUNDARIES: {
SIXTH: 1/6,
THIRD: 2/6,
HALF: 3/6,
TWO_THIRDS: 4/6,
FIVE_SIXTHS: 5/6,
},
} as const;
// Color Transition Thresholds
export const COLOR_TRANSITIONS = {
THERMAL: {
LOW: 0.25,
MID: 0.5,
HIGH: 0.75,
},
CYBERPUNK: {
LOW: 0.2,
MID: 0.4,
HIGH: 0.7,
PULSE_FREQUENCY: 8,
PULSE_AMPLITUDE: 0.3,
PULSE_OFFSET: 0.7,
},
SUNSET: {
LOW: 0.3,
HIGH: 0.6,
},
FIRE: {
LOW: 0.2,
MID: 0.5,
HIGH: 0.8,
},
} as const;
// Color Mode Specific Constants
export const COLOR_MODE_CONSTANTS = {
DITHER_LEVELS: 4,
DITHER_NOISE_AMPLITUDE: 32,
BINARY_THRESHOLD: 128,
RAINBOW_PHASE_MULTIPLIER: 6,
PLASMA: {
FREQUENCY_X: 2.4,
FREQUENCY_Y: 2.094,
FREQUENCY_Z: 4.188,
PHASE_OFFSET: 0.0,
},
INFRARED: {
INTENSITY_POWER: 0.6,
HEAT_FREQUENCY: 1.5,
},
XRAY: {
CONTRAST_POWER: 1.8,
},
SPECTRUM: {
HUE_DEGREES: 360,
SATURATION: 0.7,
LIGHTNESS_BASE: 0.6,
LIGHTNESS_FREQUENCY: 4,
LIGHTNESS_AMPLITUDE: 0.2,
},
} as const;
// Render Mode Constants - Keep in sync with color modes // Render Mode Constants - Keep in sync with color modes
export const RENDER_MODES = [ export const RENDER_MODES = [
'classic', 'classic',
@ -27,7 +111,6 @@ export const RENDER_MODES = [
'green', 'green',
'blue', 'blue',
'rgb', 'rgb',
'hsv',
'rainbow', 'rainbow',
'thermal', 'thermal',
'neon', 'neon',
@ -47,6 +130,12 @@ export const RENDER_MODES = [
'xray', 'xray',
'spectrum', 'spectrum',
'acid', 'acid',
'quantum',
'neonstrike',
'eightbit',
'silk',
'binary',
'palette16',
'palette32', 'palette32',
] as const; ] as const;
@ -91,10 +180,44 @@ export const VALUE_MODES = [
'glitch', 'glitch',
'diffusion', 'diffusion',
'cascade', 'cascade',
'echo',
'mosh',
'fold',
] as const; ] as const;
export type ValueMode = (typeof VALUE_MODES)[number]; export type ValueMode = (typeof VALUE_MODES)[number];
// Frame Rate and Timing Constants
export const TIMING = {
DEFAULT_FPS: 30,
MIN_FPS: 1,
MAX_FPS: 120,
MILLISECONDS_PER_SECOND: 1000,
DEFAULT_TIME_SPEED: 1.0,
DEFAULT_BPM: 120,
} as const;
// Worker and Threading Constants
export const WORKER = {
FALLBACK_CORE_COUNT: 4,
MAX_WORKERS: 32,
DEFAULT_PINCH_SCALE: 1,
} as const;
// Mathematical Constants
export const MATH = {
DEGREES_IN_CIRCLE: 360,
RADIANS_TO_DEGREES: 180 / Math.PI,
TWO_PI: 2 * Math.PI,
} as const;
// JSON and String Constants
export const FORMAT = {
JSON_INDENT: 2,
ID_RADIX: 36,
ID_SUBSTRING_START: 2,
} as const;
// Default Values // Default Values
export const DEFAULTS = { export const DEFAULTS = {
RESOLUTION: 8, RESOLUTION: 8,