import * as THREE from 'three'; import { G } from './globals.js'; import { DAY_NIGHT } from './config.js'; const c = DAY_NIGHT.colors; const color = (hex) => new THREE.Color(hex); const SKY_N = color(c.ambientSkyNight); const SKY_D = color(c.ambientSkyDay); const GRD_N = color(c.ambientGroundNight); const GRD_D = color(c.ambientGroundDay); const DIR_N = color(c.dirNight); const DIR_D = color(c.dirDay); const SUN_R = color(c.sunSunrise || 0xffa04a); const SUN_N = color(c.sunNoon || 0xfff1cc); const FOG_N = color(c.fogNight); const FOG_D = color(c.fogDay); const BG_N = color(c.bgNight); const BG_D = color(c.bgDay); // Reusable colors to avoid allocs each frame const tmpA = new THREE.Color(); const tmpB = new THREE.Color(); function clamp01(x){ return Math.max(0, Math.min(1, x)); } // Returns [dayFactor, nightFactor] function dayNightFactors(t) { // Cosine-based curve: 1 at noon, 0 at midnight const day = 0.5 - 0.5 * Math.cos(2 * Math.PI * t); const night = 1 - day; return [day, night]; } export function updateDayNight(delta) { if (!DAY_NIGHT.enabled) return; // Advance time const len = Math.max(10, DAY_NIGHT.lengthSec); G.timeOfDay = (G.timeOfDay + delta / len) % 1; const [dayF, nightF] = dayNightFactors(G.timeOfDay); // Intensities const ambI = DAY_NIGHT.nightAmbient * nightF + DAY_NIGHT.dayAmbient * dayF; const sunI = DAY_NIGHT.dayKey * dayF; const moonI = DAY_NIGHT.nightKey * nightF; const fogD = DAY_NIGHT.nightFogDensity * nightF + DAY_NIGHT.dayFogDensity * dayF; // Colors tmpA.copy(SKY_N).lerp(SKY_D, dayF); if (G.ambientLight) { G.ambientLight.intensity = ambI; G.ambientLight.color.copy(tmpA); tmpB.copy(GRD_N).lerp(GRD_D, dayF); // HemisphereLight has groundColor G.ambientLight.groundColor.copy(tmpB); } // Compute sun/moon directions along an arced path // Noon at t=0.5, midnight at t=0.0, use phi in [-pi, pi] const phi = (G.timeOfDay - 0.25) * Math.PI * 2; // -pi/2 at t=0, +pi/2 at t=0.5 const sunDir = new THREE.Vector3(0, Math.sin(phi), Math.cos(phi)); // YZ plane // Apply yaw tilt const tilt = THREE.MathUtils.degToRad(DAY_NIGHT.sunTiltDeg || 0); sunDir.applyAxisAngle(new THREE.Vector3(0, 1, 0), tilt).normalize(); const moonDir = sunDir.clone().multiplyScalar(-1); // Update sun light if (G.sunLight) { G.sunLight.intensity = sunI; // Sun tint warms near horizon and whites near zenith const elev = Math.max(0, sunDir.y); const warmT = Math.pow(elev, 0.75); tmpA.copy(SUN_R).lerp(SUN_N, warmT); G.sunLight.color.copy(tmpA); const dist = DAY_NIGHT.sunDistance || 200; G.sunLight.position.copy(sunDir).multiplyScalar(dist); if (G.sunLight.target) G.sunLight.target.position.set(0, 0, 0); G.sunLight.visible = sunI > 0.01; } // Update moon light if (G.moonLight) { G.moonLight.intensity = moonI; G.moonLight.color.copy(DIR_N); const distM = DAY_NIGHT.moonDistance || 200; G.moonLight.position.copy(moonDir).multiplyScalar(distM); if (G.moonLight.target) G.moonLight.target.position.set(0, 0, 0); G.moonLight.visible = moonI > 0.01; } // Sun/moon sprites if (G.sunSprite) { const d = DAY_NIGHT.sunDistance || 200; G.sunSprite.position.copy(sunDir).multiplyScalar(d); // Match sprite tint to sun light color and boost opacity with day const elev = Math.max(0, sunDir.y); const warmT = Math.pow(elev, 0.75); tmpA.copy(SUN_R).lerp(SUN_N, warmT); G.sunSprite.material.color.copy(tmpA); G.sunSprite.material.opacity = 0.65 + 0.35 * dayF; G.sunSprite.visible = sunDir.y > 0.02; // only above horizon } if (G.moonSprite) { const d = DAY_NIGHT.moonDistance || 200; G.moonSprite.position.copy(moonDir).multiplyScalar(d); G.moonSprite.material.opacity = 0.5 + 0.3 * nightF; G.moonSprite.visible = moonDir.y > 0.02; // only above horizon } tmpA.copy(FOG_N).lerp(FOG_D, dayF); if (G.scene && G.scene.fog) { G.scene.fog.color.copy(tmpA); G.scene.fog.density = fogD; } tmpA.copy(BG_N).lerp(BG_D, dayF); if (G.scene && G.scene.background) { G.scene.background.copy(tmpA); } }