wip
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { $appSettings, updateAppSettings } from '../stores/appSettings';
|
import { $appSettings, updateAppSettings } from '../stores/appSettings';
|
||||||
import { VALUE_MODES, ValueMode } from '../utils/constants';
|
import { VALUE_MODES, ValueMode, RENDER_MODES } from '../utils/constants';
|
||||||
import {
|
import {
|
||||||
uiState,
|
uiState,
|
||||||
toggleMobileMenu,
|
toggleMobileMenu,
|
||||||
@ -16,29 +16,13 @@ import { useAudio } from '../hooks/useAudio';
|
|||||||
import { LucideIcon } from '../hooks/useLucideIcon';
|
import { LucideIcon } from '../hooks/useLucideIcon';
|
||||||
|
|
||||||
function getValueModeLabel(mode: string): string {
|
function getValueModeLabel(mode: string): string {
|
||||||
const labels: Record<string, string> = {
|
// Automatically generate human-readable labels from mode names
|
||||||
integer: 'Integer (0-255)',
|
return mode.charAt(0).toUpperCase() + mode.slice(1).replace(/_/g, ' ');
|
||||||
float: 'Float (0.0-1.0)',
|
}
|
||||||
polar: 'Polar (angle-based)',
|
|
||||||
distance: 'Distance (radial)',
|
function getRenderModeLabel(mode: string): string {
|
||||||
wave: 'Wave (ripple)',
|
// Automatically generate human-readable labels from render mode names
|
||||||
fractal: 'Fractal (recursive)',
|
return mode.charAt(0).toUpperCase() + mode.slice(1).replace(/_/g, ' ');
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopBar() {
|
export function TopBar() {
|
||||||
@ -198,27 +182,11 @@ export function TopBar() {
|
|||||||
marginRight: '10px',
|
marginRight: '10px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="classic">Classic</option>
|
{RENDER_MODES.map((mode) => (
|
||||||
<option value="grayscale">Grayscale</option>
|
<option key={mode} value={mode}>
|
||||||
<option value="red">Red Channel</option>
|
{getRenderModeLabel(mode)}
|
||||||
<option value="green">Green Channel</option>
|
</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>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -628,6 +628,163 @@ export class PixelRenderer {
|
|||||||
break;
|
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:
|
default:
|
||||||
// Integer mode: treat value as 0-255 (original behavior)
|
// Integer mode: treat value as 0-255 (original behavior)
|
||||||
processedValue = Math.abs(value) % 256;
|
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(
|
export function calculateColorDirect(
|
||||||
absValue: number,
|
absValue: number,
|
||||||
renderMode: string,
|
renderMode: string,
|
||||||
hueShift: number = 0
|
hueShift: number = 0
|
||||||
): [number, number, number] {
|
): [number, number, number] {
|
||||||
let color: [number, number, number];
|
const colorFunction = COLOR_PALETTE_REGISTRY[renderMode];
|
||||||
|
const color = colorFunction ? colorFunction(absValue) : [absValue, absValue, absValue];
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return applyHueShift(color, hueShift);
|
return applyHueShift(color, hueShift);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,8 @@ export const RENDER_MODES = [
|
|||||||
'plasma',
|
'plasma',
|
||||||
'xray',
|
'xray',
|
||||||
'spectrum',
|
'spectrum',
|
||||||
|
'acid',
|
||||||
|
'palette32',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type RenderMode = (typeof RENDER_MODES)[number];
|
export type RenderMode = (typeof RENDER_MODES)[number];
|
||||||
@ -87,6 +89,8 @@ export const VALUE_MODES = [
|
|||||||
'rings',
|
'rings',
|
||||||
'mesh',
|
'mesh',
|
||||||
'glitch',
|
'glitch',
|
||||||
|
'diffusion',
|
||||||
|
'cascade',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ValueMode = (typeof VALUE_MODES)[number];
|
export type ValueMode = (typeof VALUE_MODES)[number];
|
||||||
|
|||||||
Reference in New Issue
Block a user