3D Simulation Technical Guide
This guide explains how the Interactive 3D Solar System Simulation works under the hood. It covers the mathematical foundations, the calculation order, and how each orbital parameter is configured.
Intended audience: Developers, researchers, and anyone curious about the technical implementation. Basic familiarity with JavaScript and trigonometry is helpful but not required.
Developer Quick Start
The simulation lives in a single file: script.js (~32,900 lines). Here are the five functions youβll touch most:
| Function | Line | What it does |
|---|---|---|
moveModel(pos) | ~25729 | Positions every object: ΞΈ = speed Γ pos β startPos Γ (Ο/180) |
updatePositions() | ~25457 | Reads 3D scene β computes RA/Dec and distances |
createPlanet(pd) | ~28401 | Builds the Three.js containers for one object |
updatePredictions() | ~27724 | Analytical: obliquity, year lengths, precession rates |
render() | ~12838 | Animation loop: ties everything together |
Where to look first: Object definitions (lines ~1835β3708) define every celestial bodyβs parameters. The hierarchy wiring (lines ~4525β4624) shows which object is nested inside which. These two sections plus moveModel cover 80% of what the simulation does.
Core Principles
The simulation is built in Three.jsΒ and uses two fundamental units:
| Concept | Simulation Value | Real-World Equivalent |
|---|---|---|
| One solar year | 2Ο radians | 365.242189 days |
| One AU | 100 units | 149,597,870.7 km |
Since the circumference of a circle is 2Οr, all orbital calculations derive from these base values:
- Time: All periods are expressed relative to one solar year = 2Ο
- Distance: All distances are expressed relative to 1 AU = 100
The Geocentric Frame
The simulation places Earth at the center of the scene β a geocentric reference frame. The Sun orbits Earth (not the other way around), and all planets revolve around the Sun, which itself revolves around Earth. This is a natural choice because the model describes precession cycles as seen from Earthβs reference frame.
How it works in the 3D scene:
The Sun has speed: 2Ο β exactly one revolution per year around barycenterEarthAndSun. Since barycenterEarthAndSun is nested inside Earthβs precession hierarchy, the Sun inherits all of Earthβs precession rotations while orbiting at 1 AU.
For other planets, a yearly revolution sandwich converts heliocentric orbits into the geocentric frame:
PerihelionFromEarth (speed: +2Ο/yr, perihelion offset position)
βββ ...
βββ RealPerihelionAtSun (speed: β2Ο/yr, orbit size, inclination tilt)
βββ Planet (own orbital speed)The +2Ο layer makes the planetβs orbit center revolve at the same rate as the Sun β keeping the planet centered on the Sun as seen from Earth. The β2Ο layer cancels this revolution for the orbit inside, so the planet orbits at its own heliocentric rate. The net effect: the planet orbits the Sun, which orbits Earth, producing the correct apparent geocentric motion (annual retrograde loops, elongations, and zodiac transit).
Start Date Alignment
The simulationβs start date is aligned to the June Solstice of 21 June 2000, 00:00 UTC.
At this moment, the Sun was at its highest declination (RA ~6h / ~18h), corresponding to maximum obliquity effect.
The June Solstice doesnβt occur at exactly the same time each year. The calendar uses 365.2425 days (accounting for leap years), which doesnβt perfectly match the actual solar year length. The simulation accounts for this offset.
Source Code & Data
- Simulation code: Available on GitHubΒ (see the simulation source files)
- Interactive mind map: Explore the simulationβs structure at mindmap.holisticuniverse.comΒ
- Excel spreadsheet: The full spreadsheet with all settings and calculations is available on request
- Configuration: All values in the simulation match the Formulas and the theory described in this documentation
Code Architecture
The simulation lives in a single file (script.js, ~32,900 lines) built on Three.jsΒ with Tweakpane v4Β for controls. The code is organized into five major blocks:
1. Constants & Derived Values (top ~1,800 lines)
All model parameters are declared upfront as constants β nothing is hard-coded deeper in the file. From these inputs, the code derives all secondary values: precession cycle lengths (H/3, H/5, H/8, H/13, H/16), the Balanced Year, gravitational parameters, planetary masses, and AU distances.
An OrbitalFormulas object provides reusable helper functions: Keplerβs equation solver, mean/true anomaly conversion, orbital velocity, semi-axis calculations, and heliocentric distance.
2. Scene Construction (~lines 1,800β6,000)
Each celestial body is defined as a JavaScript object with orbital parameters (speed, tilt, eccentricity, start position, precession period). Three.js builds a nested hierarchy of 3D containers:
scene
βββ startingPoint
βββ earthWobbleCenter (fixed at origin, visualization only)
βββ earth (axial precession, βH/13)
β βββ earthInclinationPrecession (+H/3)
β β βββ midEccentricityOrbit (visualization helper, sibling)
β β βββ earthEclipticPrecession (+H/5, tilt β0.634Β°)
β β βββ earthObliquityPrecession (βH/8, tilt +0.634Β°)
β β βββ earthPerihelionPrecession1 (+H/16)
β β βββ earthPerihelionPrecession2 (βH/16, eccentricity offset)
β β βββ barycenterEarthAndSun
β β βββ sun (1 AU, speed 2Ο/yr)
β β βββ earthPerihelionFromEarth (copy of barycenter position)
β β βββ moon (6 precession layers under earth.pivotObj)
β β βββ mercury β¦ neptune (5-layer hierarchy each)
β βββ rotationAxis (axial tilt, celestial sphere)
βββ invariable plane, stars, constellations, Milky WayEach precession cycle is a separate container rotating at its own rate. Because the containers are nested, each rotation is applied on top of the previous one β the physical principle of superimposed precessions maps directly to Three.js parent-child transforms.
Earth is unique in this hierarchy: it has six nested precession layers (the two counter-rotating reference points from the model). earthWobbleCenter is a separate visualization object fixed at the origin β it is not a parent container of earth. All other planets use a 5-layer hierarchy (perihelion precession sandwich + yearly geocentric revolution + orbit frame + planet), with inclination oscillation computed analytically rather than geometrically.
3. GUI & Controls (~lines 12,299β13,800)
The Tweakpane v4 panel provides:
- About β the Six Laws, Free Parameters, Calibration Inputs, Model Parameters
- Date/time input β type any date or Julian Day; the simulation jumps to that epoch
- Simulation controls β run/pause, speed selection (1 second = 1 real second up to 1,000 years)
- Camera β focus target, FOV, distance
- Visualization β planet size boost, orbits, labels, starfield, constellations, zodiac
- Tracing / Show-Hide β chip-grid buttons to toggle orbit traces and object visibility per planet
- Reports β export planet positions, solstice/equinox data, year length analysis
- Tools β Planet Inspector, Invariable Plane Inspector, Console Tests (F12)
A separate Planet Info sidebar shows live per-planet data (RA/Dec, distances, orbital parameters, predictions, inclination charts) when clicking a planet name.
4. Animation Loop (~line 12,838)
The render() function runs every frame and advances the universal time counter:
o.pos += speedFactor Γ speed Γ deltaTo balance accuracy with performance, updates are split into tiers:
- Every frame: orbital positions (
moveModel), orbit traces, camera - 20 Hz: date/time display
- 10 Hz: heavy astronomy (inclinations, invariable plane balance, predictions, anomalies)
- 30 Hz: visual effects (lighting, lens flare, glow)
5. Position Calculations (~lines 25,000+)
moveModel(pos) is the core function. For each object it computes:
ΞΈ = obj.speed Γ pos β obj.startPos Γ (Ο / 180)Then it positions the object by rotating its orbitObj:
orbitObj.rotation.y = ΞΈAll orbits in the simulation are circular β every object has a === b (semi-major equals semi-minor axis). This is a core design principle: the model builds complex orbital motion entirely from uniform circular rotations layered through the nested container hierarchy, not from ellipse equations. The elliptical paths observed in nature emerge from the superposition of multiple circular motions at different rates.
The code contains infrastructure for elliptical orbits (a β b branch in moveModel), but no object currently uses it. If activated, it would position both pivotObj and rotationAxis at (cos(ΞΈ) Γ a, 0, sin(ΞΈ) Γ b) instead of using a Y-rotation.
Then updatePositions() transforms world-space coordinates into Earthβs equatorial frame to compute RA/Dec for every planet β entirely in the browser, no external APIs.
Input Parameter Reference
The simulation is driven by a set of input constants. All other values (day lengths, year lengths, precession rates, orbital positions) are derived from these inputs. For the complete list of values, see Configuration: Simulation Input Constants.
Core Cycle Parameters
| Parameter | Description |
|---|---|
holisticyearLength | The master cycle length (333,888 years). All precession periods are derived by dividing this value by Fibonacci-related numbers (3, 5, 8, 13, 16). |
perihelionalignmentYear | The year when the longitude of perihelion was exactly 90Β° (aligned with December solstice). According to J. Meeusβs formula, this was 1246 AD. This anchor date determines where we are in all precession cycles. |
perihelionalignmentJD | The same alignment date expressed as a Julian Day number (2,176,142), used for precise date calculations. |
temperatureGraphMostLikely | A value from 0 to 16 (in 0.5 steps) that positions us within the obliquity cycle. Value 14.5 means we are 14.5/16 of the way through the current Holistic-Year since the Balanced Year. This determines the Balanced Year calculation: 1246 - (14.5 Γ 20,868) = -301,340 BC. |
Year & Day Length Parameters
| Parameter | Description |
|---|---|
inputmeanlengthsolaryearindays | The reference solar year length (365.2421897 days). This is the primary input from which mean values are calculated. The actual mean is rounded to fit exactly within the Holistic-Year cycle. |
meansiderealyearlengthinSeconds | The sidereal year in seconds (31,558,149.724). This is the fixed anchor of the model - it never changes because it measures Earthβs orbit relative to fixed stars. All day/year calculations derive from this constant. |
meansiderealyearAmplitudeinSecondsaDay | Controls how much the sidereal year (in days) varies over the perihelion precession cycle. The sidereal year in seconds is fixed, but the number of days varies because day length changes with eccentricity. |
meansolaryearAmplitudeinSecondsaDay | Controls how much the solar year varies over the obliquity cycle. As obliquity changes, the timing of equinoxes shifts slightly. |
meanAnomalisticYearAmplitudeinSecondsaDay | Controls how much the anomalistic year (in days) varies over the perihelion precession cycle. |
Model Start Position Parameters
| Parameter | Description |
|---|---|
startmodelJD | The modelβs reference date as a Julian Day (2,451,716.5 = June 21, 2000 at 00:00 UTC). All orbital positions are calculated relative to this point. |
startmodelYear | The same date as a decimal year (2000.5 = mid-2000). Used for year-based calculations. |
whichSolsticeOrEquinox | Determines which astronomical event the model aligns to at start: 0 = March Equinox, 1 = June Solstice, 2 = September Equinox, 3 = December Solstice. Default is 1 (June Solstice). |
startAngleModel | Earthβs orbital angle at the start date (89.919Β°). The June Solstice occurs at exactly 90Β°, but the model starts at midnight UTC which is slightly before the actual solstice time (01:47 UTC). |
correctionDays | Fine-tuning offset (-0.2316 days) to ensure the model aligns precisely with observed solstice times and produces clean Julian Day numbers at the Balanced Year. |
correctionSun | Angular correction (0.2774Β°) because the model starts at 00:00 UTC but the June Solstice 2000 occurred at 01:47 UTC. This shifts the Sunβs starting position to match reality. |
Obliquity Parameters
| Parameter | Description |
|---|---|
earthtiltMean | The mean obliquity (23.41398Β°). This is Earthβs average axial tilt over a full obliquity cycle. The value is optimized to match IAU 2006 precession rates. |
earthInvPlaneInclinationAmplitude | The amplitude of obliquity oscillation (0.633849Β°). Obliquity varies from (mean - amplitude) to (mean + amplitude) as the Axial Tilt and Inclination Tilt effects add or cancel. Total range: 22.21Β° to 24.71Β°. |
earthInvPlaneInclinationMean | Mean inclination of Earthβs orbit to the invariable plane (1.481592Β°). This determines the center point of the inclination oscillation. |
earthRAAngle | Right Ascension correction angle (1.258454Β°). This is the most complex parameter - it depends on temperatureGraphMostLikely, earthtiltMean, and earthInvPlaneInclinationAmplitude. It ensures the PERIHELION-OF-EARTH is correctly positioned relative to the start date. |
Eccentricity Parameters
| Parameter | Description |
|---|---|
eccentricityBase | Base orbital eccentricity (0.015321). This is the arithmetic midpoint of the eccentricity cycle (time-averaged mean = 0.015387). |
eccentricityAmplitude | Amplitude of eccentricity oscillation (0.0014226). Eccentricity varies from ~0.0139 (minimum) to ~0.0167 (maximum) over the 20,868-year cycle. |
Longitude of Perihelion Parameters
| Parameter | Description |
|---|---|
helionpointAmplitude | Primary amplitude (5.05Β°) for the longitude of perihelion calculation. This represents the main oscillation in perihelion position within each precession cycle. |
mideccentricitypointAmplitude | Secondary amplitude (2.4587Β°) that adds a harmonic to the perihelion longitude. Combined with the primary amplitude, this produces the observed wobble pattern in perihelion position. |
Physical Constants
| Parameter | Description |
|---|---|
currentAUDistance | The length of 1 Astronomical Unit in kilometers (149,597,870.698828 km). Used for converting simulation units to real distances. |
speedOfLight | Speed of light (299,792.458 km/s). Used for light-time calculations when computing apparent vs. true positions. |
deltaTStart | The Delta-T value at the model start date (63.63 seconds). Delta-T is the difference between Terrestrial Time and UTC, which changes over time due to Earthβs variable rotation. |
Calculation Order
The precession movements must be applied in a specific order. In Three.js, each movement is nested inside the previous one:
earth.pivotObj.add(earthInclinationPrecession.containerObj);
earthInclinationPrecession.pivotObj.add(earthEclipticPrecession.containerObj);
earthEclipticPrecession.pivotObj.add(earthObliquityPrecession.containerObj);
earthObliquityPrecession.pivotObj.add(earthPerihelionPrecession1.containerObj);
earthPerihelionPrecession1.pivotObj.add(earthPerihelionPrecession2.containerObj);
earthPerihelionPrecession2.pivotObj.add(barycenterSun.containerObj);
barycenterSun.pivotObj.add(earthHelionPoint.containerObj);The calculation chain:
- Earth β Sets Inclination Precession (333,888 Γ· 3 years)
- Inclination Precession β Sets Ecliptic Precession (333,888 Γ· 5 years)
- Ecliptic Precession β Sets Obliquity cycle (333,888 Γ· 8 years)
- Obliquity cycle β Sets Perihelion Precession (first pass)
- Perihelion Precession 1 β Sets Perihelion Precession (second pass)
- Perihelion Precession 2 β Sets Sun Barycenter location
- Barycenter Sun β Sets PERIHELION-OF-EARTH location
The cumulative effect produces the Axial Precession duration of 333,888 Γ· 13 years around the EARTH-WOBBLE-CENTER.
Configuration Settings
Mean Solar Year Length
The solar year length is calculated from the Holistic-Year:
meanSolarYearLengthInDays = Math.round(365.2421897 * (333888/16)) / (333888/16);The experienced solar year length varies due to Earthβs motion around the EARTH-WOBBLE-CENTER and the counter-motion of the PERIHELION-OF-EARTH around the Sun. If this value is set incorrectly, historic solstice dates wonβt match observations.
Earth Variables
The Earth object contains all settings for Earthβs orbit around the EARTH-WOBBLE-CENTER:
{
"name": "Earth",
"size": (12756.27 / 149597870.698828) * 100, // Diameter relative to AU
"speed": -Math.PI * 2 / (333888/13), // Axial precession speed
"rotationSpeed": Math.PI * 2 * (365.242188997508 + 1), // Daily rotation
"tilt": -23.41398, // Mean axial tilt
"orbitRadius": -0.0014226 * 100 // Eccentricity amplitude
}| Parameter | Formula | Purpose |
|---|---|---|
size | (Earth diameter / 1 AU) Γ 100 | Visual scale |
speed | 2Ο / (333,888 Γ· 13) | Axial precession rate (negative = clockwise) |
rotationSpeed | 2Ο Γ (solar days + 1) | Earthβs daily rotation |
tilt | -23.41398Β° | Mean axial tilt |
orbitRadius | -0.0014226 Γ 100 | Distance to EARTH-WOBBLE-CENTER |
Inclination Precession
{
"name": "Earth Inclination Precession",
"startPos": ((balancedYear - startYear) / (333888/3) * 360),
"speed": Math.PI * 2 / (333888/3)
}The startPos is calculated from the Balanced Year (-301,340 BC). This is when the solstice aligned with the PERIHELION-OF-EARTH while the Inclination Tilt and Axial Tilt were exactly opposite - a perfectly balanced state.
Counter-rotating motions: Earthβs speed (Axial Precession) is negative (clockwise), while Inclination Precession speed is positive (counter-clockwise). These two opposing motions are the foundation of the Holistic Universe Model.
Ecliptic Precession
{
"name": "Earth Ecliptic Precession",
"startPos": ((balancedYear - startYear) / (333888/5) * 360),
"speed": Math.PI * 2 / (333888/5),
"orbitTiltb": -0.634 // Tilt amplitude (negative)
}The orbitTiltb value of -0.634Β° is opposite to the Obliquity cycleβs value of +0.634Β°. These opposing tilts combine to produce the total obliquity variation.
Obliquity Cycle
{
"name": "Earth Obliquity Precession",
"startPos": -((balancedYear - startYear) / (333888/8) * 360),
"speed": -Math.PI * 2 / (333888/8),
"orbitTiltb": 0.634 // Tilt amplitude (positive)
}Note the negative startPos and speed - this cycle runs opposite to the Ecliptic Precession, creating the combined obliquity effect.
How the Two Tilts Work in the 3D Scene
Each object created by createPlanet builds four nested Three.js containers:
containerObj β rotation.x = orbitTilta, rotation.z = orbitTiltb (FIXED tilt)
position = (orbitCentera, orbitCenterc, orbitCenterb) (FIXED offset)
βββ orbitObj β rotation.y = ΞΈ(t) (ANIMATED by moveModel each frame)
βββ pivotObj (children attach here via hierarchy wiring)
βββ rotationAxis (axial tilt + spin)
βββ planetObj (visible mesh)Note: pivotObj and rotationAxis are siblings under orbitObj, not parent-child. The hierarchy wiring (.add() calls) attaches child objects to pivotObj. The visual mesh lives under rotationAxis.
The containerObj properties are permanent β tilts and position offset are set once at creation and never change. The orbitTiltb tilt physically tips the orbital plane. Because children attach to the pivotObj (inside the orbitObj), the parentβs animated Y-rotation carries the childβs tilt direction around as it sweeps. This is the core mechanism: a fixed tilt whose direction is swept by the parentβs orbital rotation.
What each tilt does in the Earth frame:
The Ecliptic Precession tilt (-A) sits inside the Inclination Precession orbitObj, which rotates at H/3. So this tilt direction is swept around at the H/3 rate (one full revolution per 111,296 years).
The Obliquity Precession tilt (+A) sits one level deeper β inside both the Inclination Precession (H/3) and the Ecliptic Precession (H/5) orbitObjs. Both parent rotations carry its tilt direction, so the net sweep rate is 3 + 5 = 8 β one revolution per H/8 = 41,736 years.
| Tilt | Carried by parents | Net sweep rate in Earth frame |
|---|---|---|
| Ecliptic Precession (-A) | Inclination Precession (H/3) | H/3 |
| Obliquity Precession (+A) | Inclination (H/3) + Ecliptic (H/5) | 3 + 5 = H/8 |
How this creates the obliquity cycle:
Earthβs spin axis is defined by rotationAxis.rotation.z = -23.41Β° at the earth level β outside the precession hierarchy. It is fixed in the Earth frame.
The two tilts create two rotating perturbations to the orbital plane normal. As each tilt direction sweeps past the spin axis, it alternately increases and decreases the angle between the spin axis and the orbital plane β the obliquity.
- The ecliptic tilt (-A) sweeps at H/3 β produces a slow obliquity oscillation with period H/3
- The obliquity tilt (+A) sweeps at H/8 β produces the ~41,000-year Milankovitch obliquity signal
The combined effect on the obliquity is the sum of these two projections onto the spin axis direction: mean β AΒ·cos(H/3) + AΒ·cos(H/8).
When both components reinforce (both cosines at -1), the obliquity reaches its maximum of ~24.68Β°. When both oppose (both cosines at +1), it reaches its minimum of ~22.15Β°. The total range is Β±2A = Β±1.27Β°.
How this creates the inclination oscillation:
The inclination to the invariable plane depends on the combined magnitude and direction of the two tilts. The parent Inclination Precession container (H/3) precesses the ascending node direction at H/3. The analytical formula captures this as: mean β AΒ·cos(H/3).
The Fibonacci identity at work: the two tilt rates (H/5 and H/8) and the inclination precession rate (H/3) satisfy 3 + 5 = 8. This identity is why the obliquity tiltβs net sweep rate (3 + 5 = 8) lands exactly on the Fibonacci number 8, producing the H/8 obliquity cycle from components at H/3 and H/5.
Perihelion Precession (Two Parts)
The Perihelion Precession is applied twice with opposite values:
Part 1:
{
"name": "Earth Perihelion Precession1",
"startPos": ((balancedYear - startYear) / (333888/16) * 360),
"speed": Math.PI * 2 / (333888/16),
"orbitTilta": -1.26 // RA correction angle
}Part 2:
{
"name": "Earth Perihelion Precession2",
"startPos": -((balancedYear - startYear) / (333888/16) * 360),
"speed": -Math.PI * 2 / (333888/16),
"orbitCentera": -0.015321 * 100 // Base eccentricity
}How the counter-rotation works in the 3D scene:
This uses the same βsandwichβ principle as the two tilts, but for position instead of tilt:
earthPerihelionPrecession1.containerObj (orbitTilta = -earthRAAngle)
βββ orbitObj rotates at +H/16 (prograde)
βββ earthPerihelionPrecession2.containerObj (orbitCentera = -eccentricityBase Γ 100)
βββ orbitObj rotates at -H/16 (retrograde, cancels the prograde)
βββ barycenterEarthAndSun, PERIHELION-OF-EARTH, SunThe base eccentricity offset (orbitCentera = -0.015321 Γ 100) is a fixed position on PerihelionPrecession2βs containerObj. It sits inside PerihelionPrecession1βs orbitObj, which rotates at +H/16. So the offset is swept around at the perihelion precession rate β this IS the perihelion direction precessing.
PerihelionPrecession2βs orbitObj then counter-rotates at -H/16, canceling the precession for everything inside. The Sun, Barycenter, and PERIHELION-OF-EARTH orbit in the correct non-precessing frame, but they are positioned at the eccentricity offset that sweeps with the perihelion.
The Barycenter Sun adds the eccentricity variation (amplitude = 0.0014226 Γ 100) on top of the base eccentricity:
{
"name": "Barycenter Sun",
"orbitRadius": 0.0014226 * 100 // Eccentricity amplitude
}Together, the base offset (sweeping with perihelion) plus the amplitude create the full eccentricity cycle: the distance from Earth to the EARTH-WOBBLE-CENTER varies between (base - amplitude) and (base + amplitude) as the perihelion precesses.
The PERIHELION-OF-EARTH is a placeholder object at the Barycenter Sun position to prevent complex Barycenter calculations from interfering with planet position calculations.
The Balanced Year:
const balancedYear = 1246 - (14.5 * (333888/16)); // = -301,340 BCThis represents 14.5 Perihelion Precession cycles before 1246 AD, aligned to explain the obliquity-driven temperature cycles.
Sun
{
"name": "Sun",
"startPos": 0.28, // Correction for exact solstice time
"speed": Math.PI * 2, // One solar year
"rotationSpeed": Math.PI * 2 / (365.2422 / 27.32),// Visual rotation (~27.32 days, simplified)
"tilt": -7.155, // Solar axial tilt
"orbitRadius": 100 // 1 AU
}| Parameter | Value | Notes |
|---|---|---|
startPos | 0.28Β° | The June Solstice 2000 occurred at ~01:47 UTC, not exactly midnight |
speed | 2Ο | Exactly one solar year |
orbitRadius | 100 | Exactly 1 AU from PERIHELION-OF-EARTH |
tilt | -7.155Β° | Solar axial tiltΒ |
Phase Alignment: How startPos Works
Every precession object needs to start at the correct position in its cycle at the model start date. The startPos parameter (in degrees) achieves this with a single formula:
startPos = (balancedYear - startYear) / cycleLength Γ 360Since the Balanced Year (-301,340 BC) is the reference epoch where all precession cycles are at their zero phase, dividing the elapsed time by the cycle length gives the fractional cycle completed, and multiplying by 360 converts to degrees.
Examples from the configuration:
| Object | Cycle | startPos formula |
|---|---|---|
| Inclination Precession | H/3 | (balancedYear β startYear) / (H/3) Γ 360 |
| Ecliptic Precession | H/5 | (balancedYear β startYear) / (H/5) Γ 360 |
| Perihelion Precession 1 | H/16 | (balancedYear β startYear) / (H/16) Γ 360 |
Retrograde objects negate their startPos:
| Object | startPos |
|---|---|
| Obliquity Precession | β(balancedYear β startYear) / (H/8) Γ 360 |
| Perihelion Precession 2 | β(balancedYear β startYear) / (H/16) Γ 360 |
The negation matches the negative speed of these retrograde layers, ensuring the phase offset is in the correct direction.
How moveModel uses it:
In moveModel(), the startPos is subtracted from the time-evolved angle:
ΞΈ = speed Γ pos β startPos Γ (Ο / 180)At pos = 0 (the model start date), this gives ΞΈ = βstartPos Γ (Ο/180), placing the object at its correct initial phase. As time advances, speed Γ pos evolves the phase forward (or backward for retrograde objects).
Right Ascension & Declination Calculations
All celestial position values in the simulation are calculated entirely in the browser - no external API calls. The calculations are deterministic based on the current Julian Date.
Step 1: Get World Positions
Each frame, the simulation retrieves the 3D positions of celestial bodies:
earth.rotationAxis.getWorldPosition(EARTH_POS); // Earth center
sun.planetObj.getWorldPosition(SUN_POS); // Sun center
obj.planetObj.getWorldPosition(PLANET_POS); // Current planetStep 2: Transform to Earth-Equatorial Frame
The planetβs position is converted to Earthβs local coordinate system, which includes the axial tilt and the 90Β° rotation that starts the simulation at the June Solstice:
LOCAL.copy(PLANET_POS);
earth.rotationAxis.worldToLocal(LOCAL); // World β Earth frameThe line earth.containerObj.rotation.y = (Math.PI/2) * whichSolsticeOrEquinox applies the 90Β° rotation so that 0h RA still points at the March Equinox even though the simulation begins at the June Solstice.
Step 3: Convert to Spherical Coordinates
SPHERICAL.setFromVector3(LOCAL);
obj.ra = SPHERICAL.theta; // ΞΈ β Right Ascension (radians)
obj.dec = SPHERICAL.phi; // Ο β Declination (radians)Since LOCAL is already in the tilted equatorial frame, ΞΈ directly gives the RA and Ο gives the Declination - no additional trigonometry needed.
Step 4: Format for Display
The raw radians are converted to sexagesimal notation:
obj.raDisplay = radiansToRa(obj.ra); // "12h34m56s"
obj.decDisplay = radiansToDec(obj.dec); // "+12Β°34β²56β³"Why Values Stay Consistent
The updatePositions() function runs after every orbit/precession update. Since it re-projects the current world-space vector into the live-tilted equator of earth.rotationAxis, any changes to Earthβs tilt, precession rate, or date/time are automatically reflected.
How Other Planets Work
Every planet (Mercury through Neptune, plus Pluto, Halleyβs comet, and Eros) uses the same counter-rotation sandwich as Earthβs perihelion precession. The hierarchy, using Jupiter as an example:
barycenterEarthAndSun
βββ jupiterPerihelionDurationEcliptic1 (prograde, H/5 perihelion precession)
βββ PERIHELION JUPITER (speed: +2Ο/yr, perihelion offset)
βββ jupiterPerihelionDurationEcliptic2 (retrograde, -H/5 cancels #1)
βββ Jupiter Real Perihelion At Sun (orbit size, inclination tilt, -2Ο/yr)
β βββ Jupiter (orbital speed, axial tilt, mesh)
βββ Fixed Perihelion At Sun (fixed perihelion reference, sibling)The same sandwich principle applies: Ecliptic1 carries the perihelion marker at the precession rate; Ecliptic2 cancels the precession for the orbit inside. The planet orbits the Sun in the correct heliocentric frame, while its perihelion direction precesses independently.
The +2Ο/yr and β2Ο/yr layers are the geocentric frame conversion β they make the orbit center revolve with the Sun while letting the planet orbit at its own rate inside.
Orbital inclination is encoded as a static tilt on the RealPerihelionAtSun container, decomposed into two components based on the J2000 ascending node (Ξ©) and inclination (i):
orbitTilta = i Γ sin(Ξ©) // rotation.x component
orbitTiltb = i Γ cos(Ξ©) // rotation.z componentThis orients the orbital plane at the correct angle to the ecliptic. The tilt is fixed at J2000 values β it does not precess in the 3D scene.
Dynamic inclination is computed analytically for all planets:
i(t) = mean + amplitude Γ cos(Ξ©(t) - phaseOffset)where Ξ©(t) is the ascending node on the invariable plane, precessing at the planetβs perihelion precession rate. As the ascending node sweeps around, the inclination oscillates between (mean - amplitude) and (mean + amplitude). These computed values are displayed in the GUI predictions panel but do not change the 3D orbital tilt.
Key Differences from Earth
| Aspect | Earth | Other Planets |
|---|---|---|
| Precession layers | 6 nested (inclination, ecliptic, obliquity, 2Γ perihelion, barycenter) | 5 layers (2Γ perihelion precession sandwich, yearly revolution, orbit frame, planet) |
| Orbital tilt | Two dynamic tilts creating obliquity + inclination cycles | Static J2000 tilt on containerObj |
| Inclination | Geometric (from two-tilt mechanism) + analytical formula | Analytical formula only |
| Perihelion precession rate | H/16 = 20,868 years | Planet-specific (e.g., Jupiter: H/5, Saturn: -H/8) |
Moon
The Moon uses Earth as its center (not the Sun). It has six nested precession layers, following the same containerObj/orbitObj/pivotObj pattern:
earth.pivotObj
βββ moonApsidalPrecession β apsidal precession (~8.85 yr), carries eccentricity offset
βββ moonApsidalNodalPrecession1 β apsidal-nodal beat (~206 days, retrograde)
βββ moonApsidalNodalPrecession2 β cancels beat (prograde, same period)
βββ moonLunarLevelingCyclePrecession β lunar leveling cycle (retrograde)
βββ moonNodalPrecession β nodal precession (~18.6 yr, retrograde), carries ecliptic inclination tilt
βββ moon β tropical month orbital speedThe apsidal precession layer carries the orbital eccentricity offset (orbitRadius). The nodal precession layer carries the ecliptic inclination tilt (orbitTilta/orbitTiltb). All intermediate layers are pure rotation layers with zero radius and zero tilt.
The detailed settings for each body are available in the Excel spreadsheet (on request).
3D Scene vs Analytical Formulas
The simulation computes values through two independent channels. Understanding which channel produces which output is important when reading the code.
3D-derived values β computed by reading positions from the scene hierarchy:
The moveModel() function positions all objects each frame. Then updatePositions() reads their world-space coordinates and transforms them into Earthβs equatorial frame to produce:
- RA and Declination for every planet, the Sun, and the camera
- Distances (Earthβplanet, Sunβplanet, perihelionβplanet)
These values emerge from the full 3D geometry β all precession layers, orbital tilts, and eccentricities are encoded in the scene hierarchy and reflected automatically in the output.
Analytical formulas β computed independently from the scene:
Several values are computed by dedicated functions that use the same input constants but do not read the 3D scene:
| Function | What it computes | Cycles used |
|---|---|---|
computeObliquityEarth() | Earthβs axial tilt | H/3 and H/8 cosines |
computeInclinationEarth() | Earthβs orbital inclination to invariable plane | H/3 cosine |
computePlanetInvPlaneInclinationDynamic() | Planet inclinations to invariable plane | Planet-specific periods |
These functions run on a 10 Hz throttle (not every frame) and feed the GUI predictions panel, year length calculations, and path visualizations. They produce the same numerical results as the 3D geometry β the formulas are the analytical representation of what the nested containers create geometrically.
Why both? The 3D scene is the primary computation β it produces RA/Dec and distances that can only be derived from the full hierarchical transform. The analytical formulas provide values that could be read from the scene geometry (like obliquity) but are faster to compute directly. They also allow displaying predictions without waiting for the full scene update.
Update sequence each frame:
| Step | Function | What it does | Frequency |
|---|---|---|---|
| 1 | pos += speedFactor Γ speed Γ delta | Advance time | Every frame |
| 2 | trace(pos) | Orbit trace sampling | Every frame |
| 3 | moveModel(pos) | Position all objects in the scene | Every frame |
| 4 | updatePositions() | Read 3D scene β RA/Dec, distances | Every frame |
| 5 | updatePositionDisplayStrings() | Format RA/Dec for GUI | 20 Hz |
| 6 | updatePredictions() | Analytical: obliquity, year lengths, precession | 10 Hz |
| 6 | updateDynamicInclinations() | Planet inclination formulas | 10 Hz |
| 6 | updateAscendingNodes() | Ascending node positions | 10 Hz |
| 6 | updatePlanetAnomalies() | True/mean anomalies | 10 Hz |
| 6 | updateInvariablePlaneBalance() | Invariable plane checks | 10 Hz |
| 7 | updateElongations(), updatePerihelion() | Elongation angles, perihelion tracking | 10 Hz |
| 8 | updateDomLabel() | DOM label updates | 5 Hz |
| 9 | updateLightingForFocus(), updateFlares() | Lighting, lens flares | 30 Hz |
| 10 | updateFocusRing(), animateGlow() | Focus ring, glow animation | 10 Hz |
| 11 | renderer.render(scene, camera) | Draw frame | Every frame |
Summary
| Component | Key Value | Purpose |
|---|---|---|
| Base time unit | 2Ο = 1 solar year | All periods relative to this |
| Base distance unit | 100 = 1 AU | All distances relative to this |
| Start date | June Solstice 2000 | Reference point for all calculations |
| Holistic-Year | 333,888 years | Master cycle length |
| Balanced Year | -301,340 BC | System equilibrium point |
The simulation demonstrates that all precession phenomena emerge from two simple counter-rotating motions:
- Earth around EARTH-WOBBLE-CENTER (clockwise, 333,888 Γ· 13 years)
- PERIHELION-OF-EARTH around Sun (counter-clockwise, 333,888 Γ· 3 years)
Key Takeaways
- Two base units - 2Ο for time (1 year), 100 for distance (1 AU)
- Nested calculations - Each precession builds on the previous in a specific order
- Counter-rotating motions - Negative speeds (clockwise) vs positive speeds (counter-clockwise)
- Browser-based calculations - All RA/Dec values computed locally, no external APIs
- Geocentric frame β The Sun orbits Earth at 2Ο/year; planets use +2Ο/β2Ο layers for the heliocentric-to-geocentric conversion
- Two computation channels β RA/Dec from 3D scene geometry; obliquity and inclination from analytical formulas
- Excel parity - All simulation values match the Excel spreadsheet calculations
Return to the Interactive 3D Simulation or explore the Formulas for the complete analytical formulas.