- New js/daynight.js module: computes sun elevation from UTC time, drives a DirectionalLight sun arc (east → zenith → west), adjusts ambient color/intensity, fog color, and star opacity each frame. - Sunrise ~06:00 UTC, sunset ~18:00 UTC with smooth transitions. - Stars (existing starfield) fade during day and glow green at night. - Agent emissive glow and point-light intensity boost at night via nightFactor passed to updateAgents(). - WebGL context-loss recovery: daynight refs disposed in disposeWorld / disposeEffects and re-initialised on rebuild. Fixes #4 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
128 lines
4.3 KiB
JavaScript
128 lines
4.3 KiB
JavaScript
/**
|
||
* Day/night cycle based on real UTC time.
|
||
*
|
||
* Sunrise ~06:00 UTC, Sunset ~18:00 UTC.
|
||
* Drives: sun directional light, ambient light, fog color, star visibility.
|
||
* Returns a nightFactor (0=day, 1=night) for consumers (e.g. agent glow).
|
||
*/
|
||
import * as THREE from 'three';
|
||
|
||
let sunLight = null;
|
||
let ambientRef = null;
|
||
let sceneRef = null;
|
||
let starMatRef = null;
|
||
|
||
/** Call once during world init. */
|
||
export function initDayNight(scene, ambientLight) {
|
||
sceneRef = scene;
|
||
ambientRef = ambientLight;
|
||
|
||
sunLight = new THREE.DirectionalLight(0xffffff, 0);
|
||
sunLight.position.set(0, 80, 0);
|
||
scene.add(sunLight);
|
||
}
|
||
|
||
/** Called by effects.js after it creates the star PointsMaterial. */
|
||
export function setStarMaterial(mat) {
|
||
starMatRef = mat;
|
||
}
|
||
|
||
/** Returns seconds elapsed since UTC midnight. */
|
||
function utcSeconds() {
|
||
const n = new Date();
|
||
return n.getUTCHours() * 3600 + n.getUTCMinutes() * 60 + n.getUTCSeconds();
|
||
}
|
||
|
||
/**
|
||
* Sun elevation angle: 1 at UTC noon, -1 at UTC midnight.
|
||
* Zero-crossings at 06:00 (dawn) and 18:00 (dusk).
|
||
*/
|
||
function sunElevation() {
|
||
const frac = utcSeconds() / 86400; // 0–1
|
||
return Math.sin(2 * Math.PI * (frac - 0.25));
|
||
}
|
||
|
||
/**
|
||
* Smooth step from a to b.
|
||
* @param {number} edge0
|
||
* @param {number} edge1
|
||
* @param {number} x
|
||
*/
|
||
function smoothstep(edge0, edge1, x) {
|
||
const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
|
||
return t * t * (3 - 2 * t);
|
||
}
|
||
|
||
/**
|
||
* Update all day/night-driven scene properties.
|
||
* Called every animation frame.
|
||
* @returns {number} nightFactor — 0 (full day) to 1 (full night)
|
||
*/
|
||
export function updateDayNight() {
|
||
const elev = sunElevation();
|
||
|
||
// dayFactor: 1 at full day (elev > 0.15), 0 at full night (elev < -0.15)
|
||
const dayFactor = smoothstep(-0.15, 0.15, elev);
|
||
const nightFactor = 1 - dayFactor;
|
||
|
||
// ── Sun directional light ─────────────────────────────────────────────────
|
||
if (sunLight) {
|
||
// Arc: east (positive X) at dawn, overhead at noon, west at dusk
|
||
const frac = utcSeconds() / 86400;
|
||
const angle = 2 * Math.PI * (frac - 0.25);
|
||
const sx = -Math.cos(angle) * 60;
|
||
const sy = Math.sin(angle) * 80;
|
||
const sz = -30;
|
||
sunLight.position.set(sx, Math.max(sy, 0.1), sz);
|
||
|
||
// Color: warm orange near horizon, muted matrix-green at zenith
|
||
const horizonBlend = 1 - Math.min(1, Math.abs(elev) / 0.4);
|
||
const r = (0.3 + horizonBlend * 0.7) * dayFactor;
|
||
const g = (0.6 - horizonBlend * 0.2) * dayFactor;
|
||
const b = (0.3 - horizonBlend * 0.25) * dayFactor;
|
||
sunLight.color.setRGB(r, g, b);
|
||
sunLight.intensity = dayFactor * 1.2;
|
||
}
|
||
|
||
// ── Ambient light ─────────────────────────────────────────────────────────
|
||
if (ambientRef) {
|
||
// Night: very dark greenish. Day: slightly brighter, cyan tint.
|
||
ambientRef.intensity = 0.35 + dayFactor * 0.35;
|
||
const ar = dayFactor * 0.04;
|
||
const ag = 0.07 + dayFactor * 0.05;
|
||
const ab = dayFactor * 0.06;
|
||
ambientRef.color.setRGB(ar, ag, ab);
|
||
}
|
||
|
||
// ── Fog color ─────────────────────────────────────────────────────────────
|
||
if (sceneRef && sceneRef.fog) {
|
||
const fr = dayFactor * 0.008;
|
||
const fg = dayFactor * 0.016;
|
||
const fb = dayFactor * 0.010;
|
||
sceneRef.fog.color.setRGB(fr, fg, fb);
|
||
}
|
||
|
||
// ── Stars ─────────────────────────────────────────────────────────────────
|
||
if (starMatRef) {
|
||
starMatRef.opacity = 0.05 + nightFactor * 0.80;
|
||
// Night: bright green. Day: nearly invisible dark green.
|
||
const sg = 0.05 + nightFactor * 0.55;
|
||
starMatRef.color.setRGB(0, sg, 0);
|
||
starMatRef.needsUpdate = true;
|
||
}
|
||
|
||
return nightFactor;
|
||
}
|
||
|
||
/** Remove sun light from scene and clear module refs. */
|
||
export function disposeDayNight() {
|
||
if (sunLight && sceneRef) {
|
||
sceneRef.remove(sunLight);
|
||
sunLight.dispose();
|
||
}
|
||
sunLight = null;
|
||
ambientRef = null;
|
||
sceneRef = null;
|
||
starMatRef = null;
|
||
}
|