PageSourceSearch

https://spaceethics.vercel.app/src/js/modules/renderLoop.js

js spaceethics.vercel.app collected 2026-10-03 07:48:13 UTC 4,700 bytes, 136 lines download raw bytes

1// renderLoop.js — Own requestAnimationFrame render loop
2//
3// Replaces Spacekit's internal animation loop.
4
5import { THREE, renderer, scene, camera, controls } from './sim.js';
6import { _followTarget } from './sim.js';
7import { getJdPerSecond, setDateMs, getDateMs, isPaused } from './simClock.js';
8import { tick as _perfTick } from './perfBudget.js';
9import { isSurfaceLocked, getSurfaceBodyId, getFocusScenePos } from './focusTracker.js';
10import { BODY_PARAMS } from './bodyRotation.js';
11
12let _running = false;
13let _onTick = null;
14let _lastTime = 0;
15let _errorLogged = false;
16let _frameCount = 0;
17const MS_PER_DAY = 86400000;
18const _DEG = Math.PI / 180;
19const _OBLIQUITY = 23.43929111 * _DEG;
20const _cosE = Math.cos(_OBLIQUITY), _sinE = Math.sin(_OBLIQUITY);
21let _prevDateMs = 0;
22
23// Compute ecliptic pole vector for surface-lock co-rotation (cached per bodyId)
24const _poleCache = new Map();
25function _getPoleEcl(bodyId) {
26  if (_poleCache.has(bodyId)) return _poleCache.get(bodyId);
27  const p = BODY_PARAMS[bodyId];
28  if (!p) return null;
29  const ra = p.poleRA * _DEG, dec = p.poleDec * _DEG;
30  const px = Math.cos(dec) * Math.cos(ra);
31  const py = Math.cos(dec) * Math.sin(ra);
32  const pz = Math.sin(dec);
33  const v = new THREE.Vector3(px, py * _cosE + pz * _sinE, -py * _sinE + pz * _cosE).normalize();
34  _poleCache.set(bodyId, v);
35  return v;
36}
37
38// Reusable quaternion for co-rotation
39const _coRotQ = new THREE.Quaternion();
40const _coRotV = new THREE.Vector3();
41
42export function setOnTick(fn) { _onTick = fn; }
43
44export function startLoop() {
45  if (_running) return;
46  _running = true;
47  _lastTime = performance.now();
48  _frame(performance.now());
49}
50
51export function stopLoop() {
52  _running = false;
53}
54
55function _frame(time) {
56  if (!_running) return;
57  requestAnimationFrame(_frame);
58
59  // Performance budget — measure frame time, update quality multiplier
60  _perfTick(time);
61
62  // Advance simulation time
63  const dt = (time - _lastTime) / 1000;
64  _lastTime = time;
65  if (dt > 0 && dt < 0.5 && !isPaused()) {
66    const jdPerSec = getJdPerSecond();
67    const advanceMs = jdPerSec * dt * MS_PER_DAY;
68    setDateMs(getDateMs() + advanceMs);
69  }
70
71  // User frame logic (wrapped to prevent single-frame errors from killing loop)
72  if (_onTick) {
73    try { _onTick(); }
74    catch (e) { if (!_errorLogged) { console.error('[renderLoop] onTick error:', e); _errorLogged = true; } }
75  }
76
77  // Track followed object (camera follows as planet moves)
78  if (_followTarget) {
79    const pos = _followTarget.getPosition?.();
80    if (pos) {
81      const newTarget = new THREE.Vector3(pos.x ?? 0, pos.y ?? 0, pos.z ?? 0);
82      if (controls?.target) {
83        const delta = newTarget.clone().sub(controls.target);
84        controls.target.copy(newTarget);
85        camera.position.add(delta);
86      }
87    }
88  }
89
90  // OrbitControls damping
91  if (controls && controls.update) controls.update();
92
93  // ── Surface-lock co-rotation ──────────────────────────────────────────────
94  // When surface-locked to a body, rotate camera position around the body's
95  // ecliptic pole axis by pmRate * actualDeltaDays so the surface appears
96  // stationary. Uses actual dateMs delta to match body mesh rotation exactly.
97  if (isSurfaceLocked()) {
98    const bodyId = getSurfaceBodyId();
99    const params = bodyId ? BODY_PARAMS[bodyId] : null;
100    const pole   = bodyId ? _getPoleEcl(bodyId) : null;
101    if (params && pole) {
102      const curDateMs = getDateMs();
103      const deltaDays = (curDateMs - _prevDateMs) / MS_PER_DAY;
104      if (Math.abs(deltaDays) > 0 && Math.abs(deltaDays) < 100) {
105        const deltaAngle = params.pmRate * deltaDays * _DEG;
106        const focusPos = getFocusScenePos();
107        // Rotate camera position around focus point along pole axis
108        _coRotV.copy(camera.position).sub(focusPos);
109        _coRotQ.setFromAxisAngle(pole, deltaAngle);
110        _coRotV.applyQuaternion(_coRotQ);
111        camera.position.copy(focusPos).add(_coRotV);
112        // Also rotate OrbitControls target to stay centred
113        if (controls?.target) {
114          controls.target.copy(focusPos);
115        }
116      }
117    }
118  }
119  _prevDateMs = getDateMs();
120
121  // Note: camera.near/far is set in _onTickBody via getFocusDistAU
122  // (more sophisticated than simple distance — considers focused object's scale)
123
124  // Render
125  renderer.render(scene, camera);
126
127  // Dismiss loading overlay after first few frames
128  _frameCount++;
129  if (_frameCount === 3) {
130    const overlay = document.getElementById('scene-loading');
131    if (overlay) {
132      overlay.style.opacity = '0';
133      setTimeout(() => overlay.remove(), 800);
134    }
135  }
136}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.