Files
the-matrix/js/effects.js
Alexander Whitestone 87486d2c7f feat: add day/night cycle driven by real UTC time
- 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>
2026-03-23 14:06:37 -04:00

124 lines
3.6 KiB
JavaScript

import * as THREE from 'three';
import { getQualityTier } from './quality.js';
import { setStarMaterial } from './daynight.js';
let rainParticles;
let rainPositions;
let rainVelocities;
let rainCount = 0;
let skipFrames = 0; // 0 = update every frame, 1 = every 2nd frame
let frameCounter = 0;
export function initEffects(scene) {
const tier = getQualityTier();
skipFrames = tier === 'low' ? 1 : 0; // Low tier: update rain every 2nd frame
initMatrixRain(scene, tier);
initStarfield(scene, tier);
}
function initMatrixRain(scene, tier) {
// Scale particle count by quality tier
rainCount = tier === 'low' ? 500 : tier === 'medium' ? 1200 : 2000;
const geo = new THREE.BufferGeometry();
const positions = new Float32Array(rainCount * 3);
const velocities = new Float32Array(rainCount);
const colors = new Float32Array(rainCount * 3);
for (let i = 0; i < rainCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 100;
positions[i * 3 + 1] = Math.random() * 50 + 5;
positions[i * 3 + 2] = (Math.random() - 0.5) * 100;
velocities[i] = 0.05 + Math.random() * 0.15;
const brightness = 0.3 + Math.random() * 0.7;
colors[i * 3] = 0;
colors[i * 3 + 1] = brightness;
colors[i * 3 + 2] = 0;
}
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
rainPositions = positions;
rainVelocities = velocities;
const mat = new THREE.PointsMaterial({
size: tier === 'low' ? 0.16 : 0.12,
vertexColors: true,
transparent: true,
opacity: 0.7,
sizeAttenuation: true,
});
rainParticles = new THREE.Points(geo, mat);
scene.add(rainParticles);
}
function initStarfield(scene, tier) {
const count = tier === 'low' ? 150 : tier === 'medium' ? 350 : 500;
const geo = new THREE.BufferGeometry();
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
positions[i * 3] = (Math.random() - 0.5) * 300;
positions[i * 3 + 1] = Math.random() * 80 + 10;
positions[i * 3 + 2] = (Math.random() - 0.5) * 300;
}
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const mat = new THREE.PointsMaterial({
color: 0x003300,
size: 0.08,
transparent: true,
opacity: 0.05, // daynight.js will drive this each frame
});
const stars = new THREE.Points(geo, mat);
scene.add(stars);
// Register with day/night system so it can control star visibility
setStarMaterial(mat);
}
export function updateEffects(_time) {
if (!rainParticles) return;
// On low tier, skip every other frame to halve iteration cost
if (skipFrames > 0) {
frameCounter++;
if (frameCounter % (skipFrames + 1) !== 0) return;
}
// When skipping frames, multiply velocity to maintain visual speed
const velocityMul = skipFrames > 0 ? (skipFrames + 1) : 1;
for (let i = 0; i < rainCount; i++) {
rainPositions[i * 3 + 1] -= rainVelocities[i] * velocityMul;
if (rainPositions[i * 3 + 1] < -1) {
rainPositions[i * 3 + 1] = 40 + Math.random() * 20;
rainPositions[i * 3] = (Math.random() - 0.5) * 100;
rainPositions[i * 3 + 2] = (Math.random() - 0.5) * 100;
}
}
rainParticles.geometry.attributes.position.needsUpdate = true;
}
/**
* Dispose all effect resources (used on world teardown).
*/
export function disposeEffects() {
if (rainParticles) {
rainParticles.geometry.dispose();
rainParticles.material.dispose();
rainParticles = null;
}
rainPositions = null;
rainVelocities = null;
rainCount = 0;
frameCounter = 0;
// Clear star material ref in daynight (daynight.js handles its own cleanup)
setStarMaterial(null);
}