From 646564fc734c6da59d43b09be2cec609e5b4b4cd Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Fri, 22 May 2026 08:03:13 +0100 Subject: [PATCH] Fixes --- src/config/vibe-presets.test.ts | 7 +-- src/config/vibe-presets.ts | 44 +++++++------- src/pipelines/render/render.wgsl | 100 +++++++++++++++++++++++++------ src/vibe-uri.test.ts | 2 +- 4 files changed, 108 insertions(+), 45 deletions(-) diff --git a/src/config/vibe-presets.test.ts b/src/config/vibe-presets.test.ts index 4052166..54c7c9e 100644 --- a/src/config/vibe-presets.test.ts +++ b/src/config/vibe-presets.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'; import { vibePresets } from './vibe-presets'; const FINAL_VIBE_NAMES = [ - 'Aurora Mycelium', + 'Aurora Mycelium Copy', 'Velvet Observatory', 'Lichen Signal', 'Tidepool Lantern', @@ -31,10 +31,7 @@ describe('vibePresets', () => { ) .map((preset) => preset.name); - expect(blendedNames).toEqual([ - 'Aurora Mycelium', - 'Tidepool Lantern', - ]); + expect(blendedNames).toEqual(['Tidepool Lantern']); expect(softParticleNames).toEqual(['Chrome Pollen']); }); diff --git a/src/config/vibe-presets.ts b/src/config/vibe-presets.ts index e25911a..3b9a2b5 100644 --- a/src/config/vibe-presets.ts +++ b/src/config/vibe-presets.ts @@ -20,13 +20,13 @@ type ColorReactionSettings = Pick< const colorReactions = { auroraMycelium: { color1ToColor1: 1, - color1ToColor2: 1, + color1ToColor2: 0, color1ToColor3: 0, - color2ToColor1: 0, + color2ToColor1: -1, color2ToColor2: 1, - color2ToColor3: 1, - color3ToColor1: 1, - color3ToColor2: 0, + color2ToColor3: 0, + color3ToColor1: -1, + color3ToColor2: -1, color3ToColor3: 1, }, velvetObservatory: { @@ -137,38 +137,38 @@ export const defaultVibeId = VibeId.AuroraMycelium; export const vibePresets: Array = [ { id: VibeId.AuroraMycelium, - name: 'Aurora Mycelium', + name: 'Aurora Mycelium Copy', colors: [ - [78, 255, 176], + [221, 255, 78], [154, 99, 255], - [169, 238, 255], + [255, 31, 199], ], backgroundColor: [6, 13, 22], settings: { ...colorReactions.auroraMycelium, - backgroundGrainStrength: 0.014, - brushSize: 21, - clarity: 0.52, - decayRateTrails: 988, - forwardRotationScale: 0.28, - individualTrailWeight: 0.082, - moveSpeed: 54, + backgroundGrainStrength: 0.003, + brushSize: 8.75, + clarity: 0.379, + decayRateTrails: 940, + forwardRotationScale: 0, + individualTrailWeight: 0.121, + moveSpeed: 270, sensorOffsetAngle: 36, - sensorOffsetDistance: 76, - spawnPerPixel: 0.14, - strokeAngleJitterRadians: 1.45, - turnSpeed: 34, - turnWhenLost: 0.75, + sensorOffsetDistance: 51, + spawnPerPixel: 0.13999999999999999, + strokeAngleJitterRadians: 0.44999999999999996, + turnSpeed: 22, + turnWhenLost: 6.071532165918825e-17, }, audio: { ...defaultGardenAudioVibeSettings, idleIntensity: 0.12, bpm: 60, - rampUpIntensity: 0.7, + rampUpIntensity: 0.7000000000000001, rampUpTime: 0.14, noteLength: 0.86, notePitchOffset: -2, - brightness: 0.84, + brightness: 0.8400000000000001, scale: musicScales.lydian, progression: musicProgressions.aurora, }, diff --git a/src/pipelines/render/render.wgsl b/src/pipelines/render/render.wgsl index 65e9229..d5613bd 100644 --- a/src/pipelines/render/render.wgsl +++ b/src/pipelines/render/render.wgsl @@ -12,6 +12,14 @@ struct Settings { brushColorStrengthMultiplier: f32, }; +const COMMON_CHANNEL_REDUCTION: f32 = 0.75; +const OVERLAP_SATURATION_BOOST: f32 = 1.35; +const LOW_SATURATION_RESCUE_AMOUNT: f32 = 0.65; +const LOW_SATURATION_RESCUE_MIN: f32 = 0.08; +const LOW_SATURATION_RESCUE_MAX: f32 = 0.22; +const COLOR_WEIGHT_EPSILON: f32 = 0.0001; +const LUMA_WEIGHTS: vec3 = vec3(0.2126, 0.7152, 0.0722); + @group(1) @binding(0) var settings: Settings; @group(1) @binding(2) var trailMap: texture_2d; @group(1) @binding(3) var sourceMap: texture_2d; @@ -41,25 +49,13 @@ fn renderColor(traces: vec4, sources: vec4, background: vec3) -> } if brushStrength <= 0.0 { - let traceColor = - traceStrengths.r * settings.colorA - + traceStrengths.g * settings.colorB - + traceStrengths.b * settings.colorC; - let normalizedTraceColor = normalizeColorIntensity(traceColor); - return vec4(mix(background, clamp(normalizedTraceColor, vec3(0), vec3(1)), traceStrength), 1); + let traceColor = colorFromChannelStrengths(traceStrengths); + return vec4(mix(background, clamp(traceColor, vec3(0), vec3(1)), traceStrength), 1); } let strengths = max(traceStrengths, sourceStrengths); - let traceColor = - strengths.r * settings.colorA - + strengths.g * settings.colorB - + strengths.b * settings.colorC; - let normalizedTraceColor = normalizeColorIntensity(traceColor); - let brushColor = - sourceStrengths.r * settings.colorA - + sourceStrengths.g * settings.colorB - + sourceStrengths.b * settings.colorC; - let normalizedBrushColor = normalizeColorIntensity(brushColor); + let traceColor = colorFromChannelStrengths(strengths); + let brushColor = colorFromChannelStrengths(sourceStrengths); let brushVisibility = clamp( brushStrength * ( settings.brushColorBase + @@ -68,7 +64,7 @@ fn renderColor(traces: vec4, sources: vec4, background: vec3) -> 0, 1 ); - let color = max(normalizedTraceColor, normalizedBrushColor); + let color = mix(traceColor, brushColor, brushVisibility); let strength = max(maxComponent(strengths), brushVisibility); return vec4(mix(background, clamp(color, vec3(0), vec3(1)), strength), 1); @@ -78,10 +74,80 @@ fn maxComponent(v: vec3) -> f32 { return max(max(v.r, v.g), v.b); } +fn minComponent(v: vec3) -> f32 { + return min(min(v.r, v.g), v.b); +} + +fn componentSum(v: vec3) -> f32 { + return v.r + v.g + v.b; +} + fn clarity(strength: vec3) -> vec3 { return pow(clamp(strength, vec3(0), vec3(1)), vec3(settings.clarity)); } +fn colorFromChannelStrengths(strengths: vec3) -> vec3 { + if maxComponent(strengths) <= 0.0 { + return vec3(0.0); + } + + let weights = colorWeights(strengths); + let color = + weights.r * settings.colorA + + weights.g * settings.colorB + + weights.b * settings.colorC; + return preserveOverlapVibrancy(normalizeColorIntensity(color), strengths); +} + +fn colorWeights(strengths: vec3) -> vec3 { + let commonStrength = minComponent(strengths); + var weightBase = max( + strengths - vec3(commonStrength * COMMON_CHANNEL_REDUCTION), + vec3(0.0) + ); + if componentSum(weightBase) <= COLOR_WEIGHT_EPSILON { + weightBase = strengths; + } + + let sharpenedWeights = weightBase * weightBase; + return sharpenedWeights / max(COLOR_WEIGHT_EPSILON, componentSum(sharpenedWeights)); +} + +fn preserveOverlapVibrancy(color: vec3, strengths: vec3) -> vec3 { + let strongest = maxComponent(strengths); + let overlapAmount = clamp( + (componentSum(strengths) - strongest) / max(COLOR_WEIGHT_EPSILON, strongest), + 0.0, + 1.0 + ); + + let luminance = dot(color, LUMA_WEIGHTS); + var vibrantColor = clamp( + vec3(luminance) + + (color - vec3(luminance)) * + mix(1.0, OVERLAP_SATURATION_BOOST, overlapAmount), + vec3(0.0), + vec3(1.0) + ); + + let saturation = maxComponent(vibrantColor) - minComponent(vibrantColor); + let rescueAmount = + overlapAmount * + (1.0 - smoothstep(LOW_SATURATION_RESCUE_MIN, LOW_SATURATION_RESCUE_MAX, saturation)) * + LOW_SATURATION_RESCUE_AMOUNT; + return mix(vibrantColor, dominantColor(strengths), rescueAmount); +} + +fn dominantColor(strengths: vec3) -> vec3 { + if strengths.r >= strengths.g && strengths.r >= strengths.b { + return normalizeColorIntensity(settings.colorA); + } + if strengths.g >= strengths.b { + return normalizeColorIntensity(settings.colorB); + } + return normalizeColorIntensity(settings.colorC); +} + fn normalizeColorIntensity(color: vec3) -> vec3 { let brightestChannel = maxComponent(color); return color / max(settings.traceNormalizationFloor, brightestChannel); diff --git a/src/vibe-uri.test.ts b/src/vibe-uri.test.ts index 6cc602a..79f0109 100644 --- a/src/vibe-uri.test.ts +++ b/src/vibe-uri.test.ts @@ -8,7 +8,7 @@ describe('vibe URI handling', () => { expect(getVibeIdFromUri('https://example.test/?vibe=aurora-mycelium')).toBe( VibeId.AuroraMycelium ); - expect(getVibeIdFromUri('https://example.test/?vibe=Aurora%20Mycelium')).toBe( + expect(getVibeIdFromUri('https://example.test/?vibe=Aurora%20Mycelium%20Copy')).toBe( VibeId.AuroraMycelium ); expect(getVibeIdFromUri('https://example.test/?vibe=velvet%20observatory')).toBe(