diff --git a/src/ShaderWorker.ts b/src/ShaderWorker.ts index 2deeb49..ecfe55f 100644 --- a/src/ShaderWorker.ts +++ b/src/ShaderWorker.ts @@ -53,8 +53,12 @@ type ShaderFunction = (...args: number[]) => number; class ShaderWorker { private compiledFunction: ShaderFunction | null = null; private lastCode: string = ''; - private imageDataCache: LRUCache = new LRUCache(PERFORMANCE.IMAGE_DATA_CACHE_SIZE); - private compilationCache: LRUCache = new LRUCache(PERFORMANCE.COMPILATION_CACHE_SIZE); + private imageDataCache: LRUCache = new LRUCache( + PERFORMANCE.IMAGE_DATA_CACHE_SIZE + ); + private compilationCache: LRUCache = new LRUCache( + PERFORMANCE.COMPILATION_CACHE_SIZE + ); private colorTables: Map = new Map(); private feedbackBuffer: Float32Array | null = null; @@ -66,7 +70,6 @@ class ShaderWorker { this.initializeColorTables(); } - private initializeColorTables(): void { const tableSize = COLOR_TABLE_SIZE; @@ -96,7 +99,6 @@ class ShaderWorker { } } - private handleMessage(message: WorkerMessage): void { try { switch (message.type) { @@ -305,7 +307,7 @@ class ShaderWorker { const data = imageData.data; const startTime = performance.now(); const maxRenderTime = PERFORMANCE.MAX_RENDER_TIME_MS; - + // Initialize feedback buffer if needed if (!this.feedbackBuffer || this.feedbackBuffer.length !== width * height) { this.feedbackBuffer = new Float32Array(width * height); @@ -412,15 +414,20 @@ class ShaderWorker { const v = actualY / fullHeight; const centerX = fullWidth / 2; const centerY = fullHeight / 2; - const radius = Math.sqrt((x - centerX) ** 2 + (actualY - centerY) ** 2); + const radius = Math.sqrt( + (x - centerX) ** 2 + (actualY - centerY) ** 2 + ); const angle = Math.atan2(actualY - centerY, x - centerX); const maxDistance = Math.sqrt(centerX ** 2 + centerY ** 2); const normalizedDistance = radius / maxDistance; const frameCount = Math.floor(time * 60); - const manhattanDistance = Math.abs(x - centerX) + Math.abs(actualY - centerY); + const manhattanDistance = + Math.abs(x - centerX) + Math.abs(actualY - centerY); const noise = (Math.sin(x * 0.1) * Math.cos(actualY * 0.1) + 1) * 0.5; - const feedbackValue = this.feedbackBuffer ? this.feedbackBuffer[pixelIndex] || 0 : 0; - + const feedbackValue = this.feedbackBuffer + ? this.feedbackBuffer[pixelIndex] || 0 + : 0; + const value = this.compiledFunction!( x, actualY, @@ -474,7 +481,7 @@ class ShaderWorker { data[i + 1] = g; data[i + 2] = b; data[i + 3] = 255; - + // Update feedback buffer with current processed value if (this.feedbackBuffer) { this.feedbackBuffer[pixelIndex] = safeValue; @@ -515,7 +522,6 @@ class ShaderWorker { if (!imageData) { imageData = new ImageData(width, height); this.imageDataCache.set(key, imageData); - } return imageData; @@ -620,16 +626,17 @@ class ShaderWorker { 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); + 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); + + processedValue = Math.floor((fractalValue + 1) * 0.5 * 255); break; } @@ -638,20 +645,24 @@ class ShaderWorker { 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)); - + 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 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; + + const cellState = cellHash % 256 > 128 ? 1 : 0; + const evolution = neighbors >= 3 && neighbors <= 5 ? 1 : cellState; processedValue = evolution * 255; break; } @@ -661,14 +672,14 @@ class ShaderWorker { 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); + processedValue = Math.floor((combinedNoise + 1) * 0.5 * 255); break; } @@ -676,27 +687,32 @@ class ShaderWorker { // 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; - + 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 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)); @@ -707,103 +723,109 @@ class ShaderWorker { // 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 + 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 + 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 - } + 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; + 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); - + 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; + 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)); - + 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; - + const enhanced = + Math.sin(flowValue * 0.05 + Math.abs(value) * 0.01) * 0.5 + 0.5; + processedValue = Math.floor(enhanced * 255); break; } @@ -825,7 +847,6 @@ class ShaderWorker { return calculateColorDirect(processedValue, renderMode); } - private sanitizeCode(code: string): string { // Auto-prefix Math functions const mathFunctions = [ diff --git a/src/Storage.ts b/src/Storage.ts index 2473fc1..873131d 100644 --- a/src/Storage.ts +++ b/src/Storage.ts @@ -1,5 +1,10 @@ import { AppSettings } from './stores/appSettings'; -import { STORAGE_KEYS, PERFORMANCE, DEFAULTS, ValueMode } from './utils/constants'; +import { + STORAGE_KEYS, + PERFORMANCE, + DEFAULTS, + ValueMode, +} from './utils/constants'; export interface SavedShader { id: string; diff --git a/src/components/HelpPopup.tsx b/src/components/HelpPopup.tsx index d96db3f..28bda42 100644 --- a/src/components/HelpPopup.tsx +++ b/src/components/HelpPopup.tsx @@ -80,7 +80,7 @@ export function HelpPopup() { n - Noise value (0.0 to 1.0)

- b - Previous frame's value (feedback) + b - Previous frame's value (feedback)

mouseX, mouseY - Mouse position (0.0 to 1.0) @@ -139,7 +139,7 @@ export function HelpPopup() {

trebleLevel - High frequencies (0.0-1.0)

-

Click "Enable Audio" to activate microphone

+

Click "Enable Audio" to activate microphone

@@ -295,10 +295,7 @@ export function HelpPopup() {

Website:{' '} - + raphaelforment.fr

@@ -307,6 +304,7 @@ export function HelpPopup() { git.raphaelforment.fr diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index e37a154..12d429b 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -112,7 +112,9 @@ export function MobileMenu() { updateAppSettings({ valueMode: e.target.value as ValueMode })} + onChange={(e) => + updateAppSettings({ valueMode: e.target.value as ValueMode }) + } style={{ background: 'rgba(255,255,255,0.1)', border: '1px solid #555', diff --git a/src/components/WelcomePopup.tsx b/src/components/WelcomePopup.tsx index ce3a8d7..ffdf6ff 100644 --- a/src/components/WelcomePopup.tsx +++ b/src/components/WelcomePopup.tsx @@ -32,11 +32,17 @@ export const WelcomePopup: React.FC = () => {

Welcome to BitFielder

-

BitFielder is an experimental lofi bitfield shader editor made by BuboBubo. Use it to create visual compositions through code. I use it for fun :)

+

+ BitFielder is an experimental lofi bitfield shader editor made by{' '} + BuboBubo. Use it to create + visual compositions through code. I use it for fun :){' '} +

Getting Started

    -
  • Edit the shader code and press Eval or Ctrl+Enter
  • +
  • + Edit the shader code and press Eval or Ctrl+Enter +
  • Use special variables to create reactive effects
  • Explore/store shaders in the library (left pane)
  • Export your creations as images or sharable links
  • @@ -44,13 +50,24 @@ export const WelcomePopup: React.FC = () => {

    Key Features

      -
    • Real-time editing: See your changes instantly
    • -
    • Motion and touch: Mouse, touchscreen support
    • -
    • Audio reactive: Synchronize with a sound signal
    • -
    • Export capabilities: Save and share your work
    • +
    • + Real-time editing: See your changes instantly +
    • +
    • + Motion and touch: Mouse, touchscreen support +
    • +
    • + Audio reactive: Synchronize with a sound signal +
    • +
    • + Export capabilities: Save and share your work +
    -

    Press ? anytime to view keyboard shortcuts and detailed help.

    +

    + Press ? anytime to view keyboard shortcuts and detailed + help. +

    Press any key to dismiss this message

diff --git a/src/hooks/useKeyboardShortcuts.ts b/src/hooks/useKeyboardShortcuts.ts index 9ffd403..8910e20 100644 --- a/src/hooks/useKeyboardShortcuts.ts +++ b/src/hooks/useKeyboardShortcuts.ts @@ -74,4 +74,4 @@ function shareURL() { .catch(() => { console.log('Copy failed'); }); -} \ No newline at end of file +} diff --git a/src/main.tsx b/src/main.tsx index 6c7551b..12b70f0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -13,11 +13,20 @@ $appSettings.set(savedSettings); function loadFromURL() { if (window.location.hash) { try { - const decoded = atob(window.location.hash.substring(1)); + const hash = window.location.hash.substring(1); + console.log('Loading from URL hash:', hash); + + const decoded = atob(hash); + console.log('Decoded data:', decoded); try { const shareData = JSON.parse(decoded); - setShaderCode(shareData.code); + console.log('Parsed share data:', shareData); + + if (shareData.code) { + setShaderCode(shareData.code); + } + $appSettings.set({ resolution: shareData.resolution || savedSettings.resolution, fps: shareData.fps || savedSettings.fps, @@ -28,7 +37,10 @@ function loadFromURL() { ? shareData.uiOpacity : savedSettings.uiOpacity, }); + + console.log('Settings updated from URL'); } catch (jsonError) { + console.log('JSON parse failed, falling back to old format'); // Fall back to old format (just code as string) setShaderCode(decoded); } diff --git a/src/stores/appSettings.ts b/src/stores/appSettings.ts index 4b8584e..44e51c7 100644 --- a/src/stores/appSettings.ts +++ b/src/stores/appSettings.ts @@ -31,9 +31,9 @@ export function cycleValueMode() { const currentIndex = VALUE_MODES.indexOf(currentMode); const nextIndex = (currentIndex + 1) % VALUE_MODES.length; const nextMode = VALUE_MODES[nextIndex]; - + updateAppSettings({ valueMode: nextMode }); - + // Return the new mode for UI feedback return nextMode; } diff --git a/src/stores/ui.ts b/src/stores/ui.ts index 317cc7f..afbc0de 100644 --- a/src/stores/ui.ts +++ b/src/stores/ui.ts @@ -21,7 +21,10 @@ export const defaultUIState: UIState = { export const uiState = atom(defaultUIState); export function toggleMobileMenu() { - uiState.set({ ...uiState.get(), mobileMenuOpen: !uiState.get().mobileMenuOpen }); + uiState.set({ + ...uiState.get(), + mobileMenuOpen: !uiState.get().mobileMenuOpen, + }); } export function closeMobileMenu() { @@ -37,7 +40,10 @@ export function hideHelp() { } export function toggleShaderLibrary() { - uiState.set({ ...uiState.get(), shaderLibraryOpen: !uiState.get().shaderLibraryOpen }); + uiState.set({ + ...uiState.get(), + shaderLibraryOpen: !uiState.get().shaderLibraryOpen, + }); } export function toggleUI() { diff --git a/src/styles/main.css b/src/styles/main.css index e594c71..e4d5814 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -462,7 +462,7 @@ button [data-lucide] { } .welcome-content li:before { - content: "▸"; + content: '▸'; position: absolute; left: 0; color: #999; diff --git a/src/utils/LRUCache.ts b/src/utils/LRUCache.ts index fd98f5d..3ae088b 100644 --- a/src/utils/LRUCache.ts +++ b/src/utils/LRUCache.ts @@ -69,4 +69,4 @@ export class LRUCache { this.cache.delete(leastUsed); } } -} \ No newline at end of file +} diff --git a/src/utils/colorModes.ts b/src/utils/colorModes.ts index 16c391f..9463ee7 100644 --- a/src/utils/colorModes.ts +++ b/src/utils/colorModes.ts @@ -1,4 +1,8 @@ -export function hsvToRgb(h: number, s: number, v: number): [number, number, number] { +export function hsvToRgb( + h: number, + s: number, + v: number +): [number, number, number] { const c = v * s; const x = c * (1 - Math.abs(((h * 6) % 2) - 1)); const m = v - c; @@ -97,11 +101,7 @@ export function cyberpunkColor(value: number): [number, number, number] { const t = value / 255.0; const phase = (t * 3) % 1; if (phase < 0.33) { - return [ - Math.round(255 * (1 - phase * 3)), - 0, - Math.round(255 * phase * 3), - ]; + return [Math.round(255 * (1 - phase * 3)), 0, Math.round(255 * phase * 3)]; } else if (phase < 0.67) { const p = (phase - 0.33) * 3; return [0, Math.round(255 * p), Math.round(255 * (1 - p))]; @@ -202,4 +202,4 @@ export function calculateColorDirect( default: return [absValue, absValue, absValue]; } -} \ No newline at end of file +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index f1c4773..18326c0 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -28,7 +28,7 @@ export const STORAGE_KEYS = { // Value Modes export const VALUE_MODES = [ 'integer', - 'float', + 'float', 'polar', 'distance', 'wave', @@ -36,10 +36,10 @@ export const VALUE_MODES = [ 'cellular', 'noise', 'warp', - 'flow' + 'flow', ] as const; -export type ValueMode = typeof VALUE_MODES[number]; +export type ValueMode = (typeof VALUE_MODES)[number]; // Default Values export const DEFAULTS = { @@ -49,4 +49,4 @@ export const DEFAULTS = { VALUE_MODE: 'integer' as ValueMode, UI_OPACITY: 0.3, SHADER_CODE: 'x^y', -} as const; \ No newline at end of file +} as const;