wip
This commit is contained in:
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
Reference in New Issue
Block a user