Stage 0. Zero-dependency serve.py (static + allowlisted horizons/sbdb/cad proxy, sha256 disk cache with past-span immutability). Three.js via pinned importmap, logarithmic depth, procedural starfield sky (+ Milky Way panorama swap-in), Sun with additive glow, OrbitControls, brass-and-void HUD, scrubbable time bar, central bodyWorld position pass, focus/camera system, hash state. Boots clean, no console errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
85 lines
3.2 KiB
JavaScript
85 lines
3.2 KiB
JavaScript
// SOLARGOD view transform — the one module that owns MEGA/TRUE compression and
|
|
// radius exaggeration. Truth (heliocentric AU) goes in, view-world units come
|
|
// out. Nothing else in the app maps AU→scene by hand (brief §4). "Truth before
|
|
// view": callers compute positions in AU, then pass through here.
|
|
|
|
import { AU_KM, eclToWorld, clamp, lerp, smoothstep } from './lib.js';
|
|
|
|
let K = 9.0;
|
|
let P = 0.4; // live exponent: MEGA=0.4, TRUE=1.0
|
|
let P_mega = 0.4, P_true = 1.0;
|
|
let planetE = 1200, sunE = 60;
|
|
let transitionMs = 2500;
|
|
|
|
let mode = 'mega';
|
|
let tween = null; // { from, to, elapsed } while animating
|
|
|
|
export function init(CONFIG) {
|
|
K = CONFIG.scale.K;
|
|
P_mega = CONFIG.scale.P_mega;
|
|
P_true = CONFIG.scale.P_true;
|
|
P = P_mega;
|
|
planetE = CONFIG.scale.planetE;
|
|
sunE = CONFIG.scale.sunE;
|
|
transitionMs = CONFIG.scale.transitionMs;
|
|
moonBase = CONFIG.scale.moonBase;
|
|
moonK = CONFIG.scale.moonK;
|
|
}
|
|
let moonBase = 1.8, moonK = 1.2;
|
|
|
|
// Radial compression: AU → view units. Direction is never touched, so MEGA↔TRUE
|
|
// preserves every body's heliocentric bearing (verify gate: unit dot ≈ 1).
|
|
export function radial(rAU) { return K * Math.pow(rAU, P); }
|
|
|
|
// Heliocentric ecliptic AU → view-world Vector3 (radial compression + frame map).
|
|
export function viewFromEcl(ecl, out) {
|
|
const r = Math.hypot(ecl.x, ecl.y, ecl.z);
|
|
if (r === 0) { out.x = out.y = out.z = 0; return out; }
|
|
const s = radial(r) / r; // scale factor along the true direction
|
|
eclToWorld({ x: ecl.x * s, y: ecl.y * s, z: ecl.z * s }, out);
|
|
return out;
|
|
}
|
|
|
|
// Body draw radius in view units. E defaults to the planet exaggeration; the Sun
|
|
// and close-focus lerp pass their own.
|
|
export function drawRadius(radiusKm, E = planetE) {
|
|
return (radiusKm / AU_KM) * K * E;
|
|
}
|
|
|
|
// Local moon distance from parent centre, view units. MEGA lifts every moon clear
|
|
// of the exaggerated parent surface and preserves ordering; TRUE is plain linear
|
|
// true distance. Blends with the same P animation.
|
|
export function moonDistance(dKm, parentRkm, parentDrawView) {
|
|
const megaD = parentDrawView * (moonBase + moonK * Math.log10(1 + dKm / parentRkm));
|
|
const trueD = (dKm / AU_KM) * K;
|
|
const blend = clamp((P - P_mega) / (P_true - P_mega), 0, 1);
|
|
return lerp(megaD, trueD, blend);
|
|
}
|
|
|
|
// ---- mode / transition ----
|
|
export function getMode() { return mode; }
|
|
export function getP() { return P; }
|
|
export function isTransitioning() { return tween !== null; }
|
|
|
|
export function setMode(next) {
|
|
if (next === mode && !tween) return;
|
|
mode = next;
|
|
tween = { from: P, to: next === 'true' ? P_true : P_mega, elapsed: 0 };
|
|
}
|
|
export function toggle() { setMode(mode === 'mega' ? 'true' : 'mega'); }
|
|
|
|
// Advance the MEGA↔TRUE tween. Call once per frame with wall dt (ms).
|
|
export function update(dtMs) {
|
|
if (!tween) return;
|
|
tween.elapsed += dtMs;
|
|
const t = clamp(tween.elapsed / transitionMs, 0, 1);
|
|
P = lerp(tween.from, tween.to, smoothstep(t));
|
|
if (t >= 1) { P = tween.to; tween = null; }
|
|
}
|
|
|
|
// ---- exaggeration (HUD slider) ----
|
|
export function setPlanetE(e) { planetE = e; }
|
|
export function getPlanetE() { return planetE; }
|
|
export function getSunE() { return sunE; }
|
|
export function getK() { return K; }
|