17 constexpr double metersPerKm = 1000.0;
18 constexpr double metersPerAu = 149597870700.0;
19 constexpr double defaultEpochJd = 2461161.5;
20 constexpr double j2000Jd = 2451545.0;
21 constexpr double julianCenturyDays = 36525.0;
22 constexpr double pi = std::numbers::pi_v<double>;
23 constexpr double degToRad = pi / 180.0;
25 struct OrbitalElements {
26 double semiMajorAxisAu;
27 double semiMajorAxisAuPerCentury;
29 double eccentricityPerCentury;
30 double inclinationDeg;
31 double inclinationDegPerCentury;
32 double meanLongitudeDeg;
33 double meanLongitudeDegPerCentury;
34 double longitudePerihelionDeg;
35 double longitudePerihelionDegPerCentury;
36 double longitudeAscendingNodeDeg;
37 double longitudeAscendingNodeDegPerCentury;
50 float thermalMassFraction;
52 float linearExpansionCoeff;
54 float latentHeatFusion;
56 float latentHeatVaporization;
64 OrbitalElements orbit;
67 constexpr double sunMassKg = 1.9885e30;
68 constexpr double sunRadiusKm = 695700.0;
69 constexpr double sunTempK = 5772.0;
71 auto makeThermal = [](
const ThermalSpec& spec,
double internalHeatPower = 0.0) {
73 thermal.
tempK = spec.tempK;
77 thermal.
absorptivity = std::clamp(1.0f - spec.bondAlbedo, 0.0f, 1.0f);
91 auto emittedRadiationPower = [](
double radiusKm,
double tempK,
double emissivity) {
92 const double radiusM = radiusKm * metersPerKm;
93 const double surfaceArea = 4.0 * pi * radiusM * radiusM;
97 const double sunLuminosity = emittedRadiationPower(sunRadiusKm, sunTempK, 1.0);
99 auto absorbedSolarPower = [&](
double radiusKm,
double orbitDistanceAu,
double absorptivity) {
100 const double radiusM = radiusKm * metersPerKm;
101 const double projectedArea = pi * radiusM * radiusM;
102 const double orbitDistanceM = orbitDistanceAu * metersPerAu;
103 const double irradiance = sunLuminosity / (4.0 * pi * orbitDistanceM * orbitDistanceM);
104 return absorptivity * irradiance * projectedArea;
107 auto equilibriumInternalHeatPower = [&](
double radiusKm,
double orbitDistanceAu,
const ThermalSpec& spec) {
108 const double emitted = emittedRadiationPower(radiusKm, spec.tempK, 1.0);
109 const double absorbed = absorbedSolarPower(radiusKm, orbitDistanceAu, 1.0 -
static_cast<double>(spec.bondAlbedo));
110 return std::max(0.0, emitted - absorbed);
113 auto orbitalPeriodSeconds = [&](
const OrbitalElements& orbit,
double centralMassKg) {
114 const double semiMajorAxisM = orbit.semiMajorAxisAu * metersPerAu;
115 return 2.0 * pi * std::sqrt((semiMajorAxisM * semiMajorAxisM * semiMajorAxisM) / (
Constants::G * centralMassKg));
118 auto thermalSkinDepthM = [&](
const ThermalSpec& spec,
double forcingPeriodSeconds) {
119 const double density = std::max(
static_cast<double>(spec.density), 0.0);
120 const double specificHeat = std::max(
static_cast<double>(spec.specificHeat), 0.0);
121 const double conductivity = std::max(
static_cast<double>(spec.conductivity), 0.0);
122 if (density <= 0.0 || specificHeat <= 0.0 || conductivity <= 0.0 || forcingPeriodSeconds <= 0.0)
return 0.0;
123 return std::sqrt((conductivity / (density * specificHeat)) * forcingPeriodSeconds / pi);
126 auto activeShellFraction = [&](
double massKg,
double radiusKm,
const ThermalSpec& spec,
double forcingPeriodSeconds) {
127 const double radiusM = radiusKm * metersPerKm;
128 const double depthM = std::min(thermalSkinDepthM(spec, forcingPeriodSeconds), radiusM);
129 if (massKg <= 0.0 || radiusM <= 0.0 || depthM <= 0.0)
return 0.0f;
131 const double innerRadiusM = std::max(0.0, radiusM - depthM);
132 const double shellVolume = (4.0 / 3.0) * pi * (radiusM * radiusM * radiusM - innerRadiusM * innerRadiusM * innerRadiusM);
133 const double shellMass = shellVolume *
static_cast<double>(spec.density);
134 return static_cast<float>(std::clamp(shellMass / massKg, 1.0e-12, 1.0));
137 auto makeSolarThermal = [&](
double massKg,
double radiusKm,
double orbitDistanceAu,
double forcingPeriodSeconds,
const ThermalSpec& spec) {
138 ThermalSpec activeSpec = spec;
139 activeSpec.thermalMassFraction = activeShellFraction(massKg, radiusKm, spec, forcingPeriodSeconds);
140 return makeThermal(activeSpec, equilibriumInternalHeatPower(radiusKm, orbitDistanceAu, activeSpec));
143 auto makePlanetThermal = [&](
double massKg,
double radiusKm,
const OrbitalElements& orbit,
const ThermalSpec& spec) {
144 return makeSolarThermal(massKg, radiusKm, orbit.semiMajorAxisAu, orbitalPeriodSeconds(orbit, sunMassKg), spec);
147 auto makeRockySpec = [](
double tempK,
float density,
float bondAlbedo,
float specificHeat,
float conductivity) {
148 return ThermalSpec{tempK, density, bondAlbedo, specificHeat, 0.0f, conductivity, 3.0e-5f, 1700.0f, 4.0e5f, 3000.0f, 1.0e7f};
151 auto makeFluidSpec = [](
double tempK,
float density,
float bondAlbedo,
float specificHeat,
float conductivity) {
152 return ThermalSpec{tempK, density, bondAlbedo, specificHeat, 0.0f, conductivity, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
155 auto normalizeRadians = [](
double angle) {
156 constexpr double pi = std::numbers::pi_v<double>;
157 constexpr double twoPi = 2.0 * pi;
158 angle = std::fmod(angle, twoPi);
159 if (angle < -pi) angle += twoPi;
160 if (angle > pi) angle -= twoPi;
164 auto solveEccentricAnomaly = [&](
double meanAnomalyRad,
double eccentricity) {
165 double e = meanAnomalyRad + eccentricity * std::sin(meanAnomalyRad);
166 for (
int i = 0; i < 12; ++i) {
167 const double delta = (meanAnomalyRad - (e - eccentricity * std::sin(e))) / (1.0 - eccentricity * std::cos(e));
169 if (std::abs(delta) <= 1.0e-12)
break;
174 auto rotateOrbitToEcliptic = [](
const glm::dvec3& v,
double omega,
double node,
double inclination) {
175 const double cosOmega = std::cos(omega);
176 const double sinOmega = std::sin(omega);
177 const double cosNode = std::cos(node);
178 const double sinNode = std::sin(node);
179 const double cosI = std::cos(inclination);
180 const double sinI = std::sin(inclination);
183 (cosOmega * cosNode - sinOmega * sinNode * cosI) * v.x + (-sinOmega * cosNode - cosOmega * sinNode * cosI) * v.y,
184 (cosOmega * sinNode + sinOmega * cosNode * cosI) * v.x + (-sinOmega * sinNode + cosOmega * cosNode * cosI) * v.y,
185 (sinOmega * sinI) * v.x + (cosOmega * sinI) * v.y
189 auto eclipticToEngine = [](
const glm::dvec3& v) {
190 return glm::vec3(
static_cast<float>(v.x),
static_cast<float>(v.z),
static_cast<float>(v.y));
193 auto stateFromElements = [&](
const OrbitalElements& elements,
double centralMassKg,
double epochJd) {
194 const double centuries = (epochJd - j2000Jd) / julianCenturyDays;
195 const double a = (elements.semiMajorAxisAu + elements.semiMajorAxisAuPerCentury * centuries) * metersPerAu;
196 const double e = elements.eccentricity + elements.eccentricityPerCentury * centuries;
197 const double inclination = (elements.inclinationDeg + elements.inclinationDegPerCentury * centuries) * degToRad;
198 const double meanLongitude = (elements.meanLongitudeDeg + elements.meanLongitudeDegPerCentury * centuries) * degToRad;
199 const double longitudePerihelion = (elements.longitudePerihelionDeg + elements.longitudePerihelionDegPerCentury * centuries) * degToRad;
200 const double node = (elements.longitudeAscendingNodeDeg + elements.longitudeAscendingNodeDegPerCentury * centuries) * degToRad;
201 const double omega = longitudePerihelion - node;
202 const double meanAnomaly = normalizeRadians(meanLongitude - longitudePerihelion);
203 const double eccentricAnomaly = solveEccentricAnomaly(meanAnomaly, e);
204 const double cosE = std::cos(eccentricAnomaly);
205 const double sinE = std::sin(eccentricAnomaly);
206 const double oneMinusECosE = 1.0 - e * cosE;
207 const double sqrtOneMinusESq = std::sqrt(1.0 - e * e);
208 const double meanMotion = std::sqrt(
Constants::G * centralMassKg / (a * a * a));
209 const double eccentricAnomalyRate = meanMotion / oneMinusECosE;
211 const glm::dvec3 orbitalPosition(a * (cosE - e), a * sqrtOneMinusESq * sinE, 0.0);
212 const glm::dvec3 orbitalVelocity(-a * sinE * eccentricAnomalyRate, a * sqrtOneMinusESq * cosE * eccentricAnomalyRate, 0.0);
215 eclipticToEngine(rotateOrbitToEcliptic(orbitalPosition, omega, node, inclination)),
216 eclipticToEngine(rotateOrbitToEcliptic(orbitalVelocity, omega, node, inclination))
220 auto createSolarBody = [&](
const BodySpec& spec) {
221 const OrbitalState state = stateFromElements(spec.orbit, sunMassKg, defaultEpochJd);
224 options.
base.
scale = glm::vec3(
static_cast<float>(spec.radiusKm * 2.0 * metersPerKm));
225 options.
mass = spec.massKg;
241 sunOptions.
base.
scale = glm::vec3(
static_cast<float>(sunRadiusKm * 2.0 * metersPerKm));
242 sunOptions.
mass = sunMassKg;
247 const std::array<BodySpec, 8> planets{{
248 {
"Mercury", 0.33010e24, 2439.7, makeRockySpec(440.15, 5429.0f, 0.068f, 800.0f, 2.0f), {0.38709927, 0.00000037, 0.20563593, 0.00001906, 7.00497902, -0.00594749, 252.25032350, 149472.67411175, 77.45779628, 0.16047689, 48.33076593, -0.12534081}},
249 {
"Venus", 4.8673e24, 6051.8, makeRockySpec(737.15, 5243.0f, 0.770f, 850.0f, 2.0f), {0.72333566, 0.00000390, 0.00677672, -0.00004107, 3.39467605, -0.00078890, 181.97909950, 58517.81538729, 131.60246718, 0.00268329, 76.67984255, -0.27769418}},
250 {
"Earth", 5.9722e24, 6371.0, makeRockySpec(288.15, 5513.0f, 0.294f, 1000.0f, 2.5f), {1.00000261, 0.00000562, 0.01671123, -0.00004392, -0.00001531, -0.01294668, 100.46457166, 35999.37244981, 102.93768193, 0.32327364, 0.0, 0.0}},
251 {
"Mars", 0.64169e24, 3389.5, makeRockySpec(208.15, 3934.0f, 0.250f, 750.0f, 0.08f), {1.52371034, 0.00001847, 0.09339410, 0.00007882, 1.84969142, -0.00813131, -4.55343205, 19140.30268499, -23.94362959, 0.44441088, 49.55953891, -0.29257343}},
252 {
"Jupiter", 1898.13e24, 69911.0, makeFluidSpec(163.15, 1326.0f, 0.343f, 13000.0f, 0.18f), {5.20288700, -0.00011607, 0.04838624, -0.00013253, 1.30439695, -0.00183714, 34.39644051, 3034.74612775, 14.72847983, 0.21252668, 100.47390909, 0.20469106}},
253 {
"Saturn", 568.32e24, 58232.0, makeFluidSpec(133.15, 687.0f, 0.342f, 13000.0f, 0.18f), {9.53667594, -0.00125060, 0.05386179, -0.00050991, 2.48599187, 0.00193609, 49.95424423, 1222.49362201, 92.59887831, -0.41897216, 113.66242448, -0.28867794}},
254 {
"Uranus", 86.811e24, 25362.0, makeFluidSpec(78.15, 1270.0f, 0.300f, 9000.0f, 0.6f), {19.18916464, -0.00196176, 0.04725744, -0.00004397, 0.77263783, -0.00242939, 313.23810451, 428.48202785, 170.95427630, 0.40805281, 74.01692503, 0.04240589}},
255 {
"Neptune", 102.409e24, 24622.0, makeFluidSpec(73.15, 1638.0f, 0.290f, 9000.0f, 0.6f), {30.06992276, 0.00026291, 0.00859048, 0.00005105, 1.77004347, 0.00035372, -55.12002969, 218.45945325, 44.96476227, -0.32241464, 131.78422574, -0.00508664}},
259 OrbitalState earthState{};
260 for (
const BodySpec& planet : planets) {
262 if (std::string_view(planet.name) ==
"Earth") {
269 const OrbitalState moonRelativeState = stateFromElements(
270 {0.002569555, 0.0, 0.0549, 0.0, 5.1454, 0.0, 558.5516, 481267.88088047254, 443.1862, 4069.01334885, 125.1228, -1934.1378481575},
275 moonOptions.
base.
position = earthState.position + moonRelativeState.position;
276 moonOptions.base.scale = glm::vec3(1737.4f * 2.0f *
static_cast<float>(metersPerKm));
277 moonOptions.mass = 0.07346e24;
278 moonOptions.velocity = earthState.velocity + moonRelativeState.velocity;
281 const double earthYearSeconds = orbitalPeriodSeconds({1.00000261, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, sunMassKg);