This commit is contained in:
2025-07-14 21:58:04 +02:00
parent 431966d498
commit 3eeafc1277
4 changed files with 265 additions and 136 deletions

View File

@ -1,7 +1,7 @@
import { useStore } from '@nanostores/react';
import { useState } from 'react';
import { $appSettings, updateAppSettings } from '../stores/appSettings';
import { VALUE_MODES, ValueMode } from '../utils/constants';
import { VALUE_MODES, ValueMode, RENDER_MODES } from '../utils/constants';
import {
uiState,
toggleMobileMenu,
@ -16,29 +16,13 @@ import { useAudio } from '../hooks/useAudio';
import { LucideIcon } from '../hooks/useLucideIcon';
function getValueModeLabel(mode: string): string {
const labels: Record<string, string> = {
integer: 'Integer (0-255)',
float: 'Float (0.0-1.0)',
polar: 'Polar (angle-based)',
distance: 'Distance (radial)',
wave: 'Wave (ripple)',
fractal: 'Fractal (recursive)',
cellular: 'Cellular (automata)',
noise: 'Noise (perlin-like)',
warp: 'Warp (space deformation)',
flow: 'Flow (fluid dynamics)',
spiral: 'Spiral (logarithmic)',
turbulence: 'Turbulence (chaos)',
crystal: 'Crystal (lattice)',
marble: 'Marble (veining)',
quantum: 'Quantum (uncertainty)',
logarithmic: 'Logarithmic (scaling)',
mirror: 'Mirror (symmetrical)',
rings: 'Rings (interference)',
mesh: 'Mesh (grid rotation)',
glitch: 'Glitch (corruption)',
};
return labels[mode] || mode;
// Automatically generate human-readable labels from mode names
return mode.charAt(0).toUpperCase() + mode.slice(1).replace(/_/g, ' ');
}
function getRenderModeLabel(mode: string): string {
// Automatically generate human-readable labels from render mode names
return mode.charAt(0).toUpperCase() + mode.slice(1).replace(/_/g, ' ');
}
export function TopBar() {
@ -198,27 +182,11 @@ export function TopBar() {
marginRight: '10px',
}}
>
<option value="classic">Classic</option>
<option value="grayscale">Grayscale</option>
<option value="red">Red Channel</option>
<option value="green">Green Channel</option>
<option value="blue">Blue Channel</option>
<option value="rainbow">Rainbow</option>
<option value="thermal">Thermal</option>
<option value="neon">Neon</option>
<option value="sunset">Sunset</option>
<option value="ocean">Ocean</option>
<option value="forest">Forest</option>
<option value="copper">Copper</option>
<option value="dithered">Dithered</option>
<option value="palette">Palette</option>
<option value="vintage">Vintage</option>
<option value="infrared">Infrared</option>
<option value="fire">Fire</option>
<option value="ice">Ice</option>
<option value="plasma">Plasma</option>
<option value="xray">X-Ray</option>
<option value="spectrum">Spectrum</option>
{RENDER_MODES.map((mode) => (
<option key={mode} value={mode}>
{getRenderModeLabel(mode)}
</option>
))}
</select>
<input

View File

@ -628,6 +628,163 @@ export class PixelRenderer {
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;

View File

@ -342,102 +342,102 @@ export function spectrumColor(value: number): [number, number, number] {
];
}
export function acidColor(value: number): [number, number, number] {
const t = value / 255.0;
const phase = t * Math.PI * 2;
const r = Math.sin(phase) * 0.5 + 0.5;
const g = Math.sin(phase + Math.PI * 0.66) * 0.5 + 0.5;
const b = Math.sin(phase + Math.PI * 1.33) * 0.5 + 0.5;
const intensity = Math.pow(t, 0.8);
const glow = Math.sin(t * Math.PI * 6) * 0.2 + 0.8;
return [
Math.round(r * intensity * glow * 255),
Math.round(g * intensity * glow * 255),
Math.round(b * intensity * glow * 255)
];
}
export function palette32Color(value: number): [number, number, number] {
const palette = [
[0, 0, 0], // Black
[255, 255, 255], // White
[255, 0, 0], // Red
[0, 255, 0], // Green
[0, 0, 255], // Blue
[255, 255, 0], // Yellow
[255, 0, 255], // Magenta
[0, 255, 255], // Cyan
[255, 128, 0], // Orange
[128, 0, 255], // Purple
[0, 255, 128], // Spring Green
[255, 0, 128], // Pink
[128, 255, 0], // Lime
[0, 128, 255], // Sky Blue
[128, 128, 128], // Gray
[192, 192, 192], // Light Gray
[64, 64, 64], // Dark Gray
[128, 64, 0], // Brown
[64, 128, 0], // Olive
[0, 64, 128], // Navy
[128, 0, 64], // Maroon
[64, 0, 128], // Indigo
[0, 128, 64], // Teal
[255, 192, 128], // Peach
[128, 255, 192], // Mint
[192, 128, 255], // Lavender
[255, 128, 192], // Rose
[128, 192, 255], // Light Blue
[192, 255, 128], // Light Green
[64, 32, 16], // Dark Brown
[16, 64, 32], // Forest
[32, 16, 64] // Deep Purple
];
const index = Math.floor((value / 255.0) * (palette.length - 1));
return palette[index] as [number, number, number];
}
// Color palette registry - automatically maps render modes to color functions
const COLOR_PALETTE_REGISTRY: Record<string, (value: number) => [number, number, number]> = {
classic: (value) => [value, (value * 2) % 256, (value * 3) % 256],
grayscale: (value) => [value, value, value],
red: (value) => [value, 0, 0],
green: (value) => [0, value, 0],
blue: (value) => [0, 0, value],
rgb: (value) => [value, (value * 2) % 256, (value * 3) % 256], // Same as classic for now
hsv: (value) => rainbowColor(value), // Use rainbow for HSV
forest: forestColor,
copper: copperColor,
rainbow: rainbowColor,
thermal: thermalColor,
neon: neonColor,
cyberpunk: neonColor, // Use neon for cyberpunk theme
vaporwave: plasmaColor, // Use plasma for vaporwave theme
sunset: sunsetColor,
ocean: oceanColor,
dithered: ditheredColor,
palette: paletteColor,
vintage: vintageColor,
plasma: plasmaColor,
fire: fireColor,
ice: iceColor,
infrared: infraredColor,
xray: xrayColor,
spectrum: spectrumColor,
acid: acidColor,
palette32: palette32Color,
};
export function calculateColorDirect(
absValue: number,
renderMode: string,
hueShift: number = 0
): [number, number, number] {
let color: [number, number, number];
switch (renderMode) {
case 'classic':
color = [absValue, (absValue * 2) % 256, (absValue * 3) % 256];
break;
case 'grayscale':
color = [absValue, absValue, absValue];
break;
case 'red':
color = [absValue, 0, 0];
break;
case 'green':
color = [0, absValue, 0];
break;
case 'blue':
color = [0, 0, absValue];
break;
case 'forest':
color = forestColor(absValue);
break;
case 'copper':
color = copperColor(absValue);
break;
case 'rainbow':
color = rainbowColor(absValue);
break;
case 'thermal':
color = thermalColor(absValue);
break;
case 'neon':
color = neonColor(absValue);
break;
case 'sunset':
color = sunsetColor(absValue);
break;
case 'ocean':
color = oceanColor(absValue);
break;
case 'dithered':
color = ditheredColor(absValue);
break;
case 'palette':
color = paletteColor(absValue);
break;
case 'vintage':
color = vintageColor(absValue);
break;
case 'plasma':
color = plasmaColor(absValue);
break;
case 'fire':
color = fireColor(absValue);
break;
case 'ice':
color = iceColor(absValue);
break;
case 'infrared':
color = infraredColor(absValue);
break;
case 'xray':
color = xrayColor(absValue);
break;
case 'spectrum':
color = spectrumColor(absValue);
break;
default:
color = [absValue, absValue, absValue];
break;
}
const colorFunction = COLOR_PALETTE_REGISTRY[renderMode];
const color = colorFunction ? colorFunction(absValue) : [absValue, absValue, absValue];
return applyHueShift(color, hueShift);
}

View File

@ -46,6 +46,8 @@ export const RENDER_MODES = [
'plasma',
'xray',
'spectrum',
'acid',
'palette32',
] as const;
export type RenderMode = (typeof RENDER_MODES)[number];
@ -87,6 +89,8 @@ export const VALUE_MODES = [
'rings',
'mesh',
'glitch',
'diffusion',
'cascade',
] as const;
export type ValueMode = (typeof VALUE_MODES)[number];