Files
the-nexus/modules/core/scene.js
Alexander Whitestone 4f207605ce
All checks were successful
CI / validate (pull_request) Successful in 6s
CI / auto-merge (pull_request) Successful in 0s
feat: extract core foundation modules — scene, ticker, theme, state (#420)
Phase 1 of app.js modularization (Refs #409, Fixes #420).

Adds four modules under modules/core/:

- theme.js: exports NEXUS with full NEXUS.theme (colors, fonts, lineWeights,
  glowParams, opacity). NEXUS.colors preserved as backwards-compat alias.

- state.js: exports zoneIntensity (shared mutable object), state (agentCount,
  blockHeight, starPulseIntensity, weather), and totalActivity().

- scene.js: creates and exports scene, camera, renderer, orbitControls,
  raycaster, lighting. Registers camera+renderer resize handler. Intentionally
  does not append renderer.domElement — app.js controls DOM insertion order.

- ticker.js: single requestAnimationFrame loop. Exports subscribe(), unsubscribe(),
  start(), stop(). app.js registers animate() via tickerSubscribe and calls
  tickerStart() instead of RAF directly.

app.js changes:
- Imports NEXUS from theme.js (removes inline NEXUS definition)
- Imports scene/camera/renderer/orbitControls/raycaster from scene.js
  (removes duplicate creation blocks)
- Imports zoneIntensity/state/totalActivity from state.js (removes local defs)
- animate() is now a ticker subscriber function, not a recursive RAF caller
- _activeAgentCount → state.agentCount
- _starPulseIntensity → state.starPulseIntensity
- lastKnownBlockHeight → state.blockHeight
- EffectComposer resize listener preserved separately in app.js

node --check app.js passes. No visual regressions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 14:24:00 -04:00

73 lines
2.7 KiB
JavaScript

/**
* modules/core/scene.js — Core 3D scene infrastructure.
*
* Creates and exports: THREE.Scene, camera, renderer, lighting, OrbitControls, raycaster.
* Registers the window resize handler for camera + renderer.
*
* IMPORTANT: renderer.domElement is NOT appended to document.body here.
* app.js controls DOM insertion order — the 2D matrix canvas must be inserted
* before the Three.js canvas so the Z-order (matrix behind Three.js) is correct.
*/
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// === SCENE ===
export const scene = new THREE.Scene();
// Background is null — the matrix rain canvas shows through the transparent renderer.
// === CAMERA ===
export const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
2000
);
camera.position.set(0, 6, 11);
// === RAYCASTER ===
export const raycaster = new THREE.Raycaster();
// === LIGHTING ===
// AmbientLight provides a dark-blue fill so unlit geometry stays visible.
const ambientLight = new THREE.AmbientLight(0x0a1428, 1.4);
scene.add(ambientLight);
// SpotLight replaces PointLight: shadows need only one depth map instead of six.
const overheadLight = new THREE.SpotLight(0x8899bb, 0.6, 80, Math.PI / 3.5, 0.5, 1.0);
overheadLight.position.set(0, 25, 0);
overheadLight.target.position.set(0, 0, 0);
overheadLight.castShadow = true;
overheadLight.shadow.mapSize.set(2048, 2048);
overheadLight.shadow.camera.near = 5;
overheadLight.shadow.camera.far = 60;
overheadLight.shadow.bias = -0.001;
scene.add(overheadLight);
scene.add(overheadLight.target);
// === RENDERER ===
// NOTE: renderer.domElement is intentionally NOT appended here — see module docblock.
export const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setClearColor(0x000000, 0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// PCFSoftShadowMap gives smooth penumbra edges matching the holographic aesthetic.
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// === ORBIT CONTROLS ===
// Free-camera controls used in photo mode only (enabled = false by default).
export const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.enableDamping = true;
orbitControls.dampingFactor = 0.05;
orbitControls.enabled = false;
// === RESIZE HANDLER ===
// Keeps camera aspect ratio and renderer size in sync with the browser window.
// The EffectComposer (owned by app.js) must register its own resize listener.
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});