diff --git a/src/core/config.ts b/src/core/config.ts index 995a67d..86879d9 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -106,6 +106,10 @@ export interface ConfigType { glow: { startOffsetMinutes: number; endOffsetMinutes: number; + glowPulseFrequency: number; + glowPulseAmplitude: number; + glowIntensityMax: number; + glowColorMap: Record; }; noteColorMap: { global: Record; @@ -154,7 +158,15 @@ export const CONFIG: ConfigType = { glow: { startOffsetMinutes: 30, // Start glowing 30 min before sunset endOffsetMinutes: 30, // Stop glowing 30 min after sunrise (or before? usually before dawn, but let's stick to plan) - // Plan says "stop before dawn". Let's say it fades out during pre-dawn. + glowPulseFrequency: 1.0, + glowPulseAmplitude: 0.5, + glowIntensityMax: 2.0, + glowColorMap: { + 'mushroom': 0xFFDDDD, + 'tree': 0xAAFFCC, + 'flower': 0xFFCCFF, + 'global': 0xFFFFFF + } }, // --- NOTE COLOR MAPPING --- diff --git a/src/foliage/mushroom-batcher.ts b/src/foliage/mushroom-batcher.ts index 0055e11..0fae1f7 100644 --- a/src/foliage/mushroom-batcher.ts +++ b/src/foliage/mushroom-batcher.ts @@ -23,6 +23,7 @@ import { uTwilight } from './sky.ts'; import { foliageGroup } from '../world/state.ts'; // Assuming state.ts exports foliageGroup import { spawnImpact } from './impacts.ts'; import { uChromaticIntensity } from './chromatic.ts'; +import { CONFIG } from '../core/config.ts'; const MAX_MUSHROOMS = 1000; // Reduced from 4000 for WebGPU uniform buffer limits @@ -562,8 +563,19 @@ export class MushroomBatcher { // Emissive Logic for Cap (Bioluminescence + Flash) const flashIntensity = smoothstep(0.2, 0.0, noteAge).mul(velocity).mul(2.0); - // 🎨 PALETTE: Let the cap breathe with the bass even when not directly triggered - const idlePulse = sin(uTime.mul(2.0)).mul(0.5).add(0.5).mul(uAudioLow.mul(0.5)); + + // 🎨 PALETTE: Twilight Glow System Support + // Apply phase offset based on instance index to prevent unison pulsing + const glowPhaseOffset = float(instanceIndex).mul(0.1); + const glowPulseFreq = float(CONFIG.glow.glowPulseFrequency); + const glowPulseAmp = float(CONFIG.glow.glowPulseAmplitude); + + // Use a base idle pulse that responds to audio and time with the phase offset + const idlePulse = sin(uTime.mul(glowPulseFreq).add(glowPhaseOffset)).mul(glowPulseAmp).add(1.0).mul(float(0.5)).mul(uAudioLow.mul(0.5)); + + // Get the specific twilight glow color from config and multiply by twilight window + const targetGlowColor = color(CONFIG.glow.glowColorMap['mushroom']); + const twilightGlowTint = targetGlowColor.mul(uTwilight).mul(float(CONFIG.glow.glowIntensityMax)); const baseGlow = uTwilight.mul(float(0.5).add(idlePulse)); // Combine Glow + Flash + Sparkle @@ -573,7 +585,9 @@ export class MushroomBatcher { // Yes, let's add a fraction of inner glow to emissive for night visibility. const totalGlow = baseGlow.add(flashIntensity).add(sugarSparkle).add(innerGlowFactor.mul(0.3)); - capMat.emissiveIntensityNode = totalGlow; + // Add twilight glow directly to emissive node output + capMat.emissiveNode = twilightGlowTint.mul(totalGlow); + capMat.emissiveIntensityNode = float(1.0); // Resetting multiplier since we multiply inside node // 2. Gills const gillMat = (foliageMaterials.mushroomGills as MeshStandardNodeMaterial).clone(); diff --git a/src/foliage/tree-batcher.ts b/src/foliage/tree-batcher.ts index 7b480eb..a54cd48 100644 --- a/src/foliage/tree-batcher.ts +++ b/src/foliage/tree-batcher.ts @@ -24,6 +24,8 @@ import { import { applyGlitch } from './glitch.ts'; import { getCylinderGeometry, getTorusKnotGeometry } from '../utils/geometry-dedup.ts'; import { createSugarSparkle } from './index.ts'; +import { uTwilight } from './sky.ts'; +import { CONFIG } from '../core/config.ts'; const _defaultColorWhite = new THREE.Color(0xFFFFFF); const _defaultColorOrange = new THREE.Color(0xFF4500); @@ -130,6 +132,19 @@ export class TreeBatcher { // Base Emissive logic based on High Freq Audio const sphereEmissive = sphereColor.mul(uAudioHigh.mul(1.5).add(0.2)); + // 🎨 PALETTE: Twilight Glow System Support + const instanceIndex = attribute('instanceIndex', 'uint'); + const glowPhaseOffset = float(instanceIndex).mul(0.1); + const glowPulseFreq = float(CONFIG.glow.glowPulseFrequency); + const glowPulseAmp = float(CONFIG.glow.glowPulseAmplitude); + + // Idle pulse responding to audio and time, with phase offset + const idlePulse = sin(uTime.mul(glowPulseFreq).add(glowPhaseOffset)).mul(glowPulseAmp).add(1.0).mul(float(0.5)).mul(uAudioLow.mul(0.5)); + + // Target glow color from config mapped to twilight + const targetGlowColor = color(CONFIG.glow.glowColorMap['tree']); + const twilightGlowTint = targetGlowColor.mul(uTwilight).mul(float(CONFIG.glow.glowIntensityMax)).mul(float(0.5).add(idlePulse)); + // Add Sugar Sparkle! (Palette Polish) // Scale 15.0 for fine grain, Density 0.3 for sparse twinkle, Intensity 2.0 const sugarSparkle = createSugarSparkle(normalWorld, float(15.0), float(0.3), float(2.0)); @@ -145,8 +160,8 @@ export class TreeBatcher { audioReactStrength: 0.5 // Inner glow pulse }); - // 🎨 PALETTE: Make tree leaves pop with sparkly glow and base audio emissive - sphereMat.emissiveNode = sphereEmissive.add(sugarSparkle); + // 🎨 PALETTE: Make tree leaves pop with sparkly glow, base audio emissive, and twilight glow + sphereMat.emissiveNode = sphereEmissive.add(sugarSparkle).add(twilightGlowTint); this.spheres = new THREE.InstancedMesh(sharedGeometries.unitSphere, sphereMat, this.sphereCapacity); this.spheres.instanceColor = new THREE.InstancedBufferAttribute(new Float32Array(this.sphereCapacity * 3), 3);