From 3eeafc12771704065a0d2aacd9e43f1b4935704c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Mon, 14 Jul 2025 21:58:04 +0200 Subject: [PATCH] wip --- src/components/TopBar.tsx | 58 ++------ src/shader/rendering/PixelRenderer.ts | 157 ++++++++++++++++++++++ src/utils/colorModes.ts | 182 +++++++++++++------------- src/utils/constants.ts | 4 + 4 files changed, 265 insertions(+), 136 deletions(-) diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index f967a1f..195b97a 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -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 = { - 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', }} > - - - - - - - - - - - - - - - - - - - - - + {RENDER_MODES.map((mode) => ( + + ))} = 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; diff --git a/src/utils/colorModes.ts b/src/utils/colorModes.ts index c9a70e0..e42abd6 100644 --- a/src/utils/colorModes.ts +++ b/src/utils/colorModes.ts @@ -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 [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); } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b180d76..fddbcef 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -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];