Browse Source

Phase 7: Implement parabolic orbit support with union

- Added union in OrbitalElements (semi_major_axis | semi_latus_rectum)
- Updated config_loader to parse and validate semi_latus_rectum for parabolic orbits
- Validation ensures correct parameter (semi_latus_rectum for e≈1, semi_major_axis for other)
- Updated orbital_mechanics.cpp parabolic case: r = p / (1 + cos(ν))
- Updated parabolic_comet.toml to use semi_latus_rectum = 1.496e11
- Tolerance for parabolic detection: |e - 1.0| < 0.005

Note: Parabolic test still fails velocity tolerance check due to floating-point precision
at r=0.5 AU, E ≈ -200 J/kg (not exactly 0)
main
cinnaboot 6 months ago
parent
commit
c4c06d4cd3
  1. 164
      src/config_loader.cpp
  2. 5
      src/orbital_mechanics.cpp
  3. 7
      src/orbital_mechanics.h
  4. 12
      tests/configs/parabolic_comet.toml

164
src/config_loader.cpp

@ -84,24 +84,45 @@ static bool parse_toml_body(toml_datum_t body_table, CelestialBody* body) {
body->orbit.argument_of_periapsis = 0.0;
body->orbit.true_anomaly = 0.0;
// Parse semi_major_axis (required) or altitude (convenience)
// Parse semi_major_axis (for elliptical/hyperbolic) or semi_latus_rectum (for parabolic) or altitude (convenience)
toml_datum_t semi_major = toml_get(orbit_table, "semi_major_axis");
toml_datum_t semi_latus = toml_get(orbit_table, "semi_latus_rectum");
toml_datum_t altitude = toml_get(orbit_table, "altitude");
if (semi_major.type == TOML_FP64) {
body->orbit.semi_major_axis = semi_major.u.fp64;
} else if (altitude.type == TOML_FP64) {
// altitude will be added to parent radius in initialization
body->orbit.semi_major_axis = altitude.u.fp64;
} else {
printf("Error: Body '%s' must have either 'semi_major_axis' or 'altitude' in orbit table\n", body->name);
// Parse eccentricity first to determine which parameter is required
toml_datum_t eccentricity = toml_get(orbit_table, "eccentricity");
if (eccentricity.type != TOML_FP64) {
printf("Error: Body '%s' missing required 'eccentricity' in orbit table\n", body->name);
return false;
}
body->orbit.eccentricity = eccentricity.u.fp64;
// Parse eccentricity (optional, default 0.0)
toml_datum_t eccentricity = toml_get(orbit_table, "eccentricity");
if (eccentricity.type == TOML_FP64) {
body->orbit.eccentricity = eccentricity.u.fp64;
bool is_parabolic = (fabs(body->orbit.eccentricity - 1.0) < 0.005);
if (is_parabolic) {
// Parabolic orbit - requires semi_latus_rectum
if (semi_latus.type != TOML_FP64) {
printf("Error: Parabolic orbit for body '%s' requires 'semi_latus_rectum' (not 'semi_major_axis' or 'altitude')\n", body->name);
return false;
}
body->orbit.semi_latus_rectum = semi_latus.u.fp64;
if (semi_major.type == TOML_FP64 || altitude.type == TOML_FP64) {
printf("Warning: Body '%s' has parabolic eccentricity, 'semi_latus_rectum' used instead of 'semi_major_axis' or 'altitude'\n", body->name);
}
} else {
// Elliptical or hyperbolic - requires semi_major_axis or altitude
if (semi_major.type == TOML_FP64) {
body->orbit.semi_major_axis = semi_major.u.fp64;
} else if (altitude.type == TOML_FP64) {
// altitude will be added to parent radius in initialization
body->orbit.semi_major_axis = altitude.u.fp64;
} else {
printf("Error: Body '%s' must have either 'semi_major_axis' or 'altitude' in orbit table (non-parabolic orbits)\n", body->name);
return false;
}
if (semi_latus.type == TOML_FP64) {
printf("Warning: Body '%s' has non-parabolic eccentricity, 'semi_latus_rectum' ignored\n", body->name);
}
}
// Parse true_anomaly (optional, default 0.0)
@ -200,12 +221,7 @@ bool load_system_config(SimulationState* sim, const char* filepath) {
continue;
}
if (body->orbit.semi_major_axis == 0.0) {
printf("Error: Body '%s' has invalid semi_major_axis: %.2e (must not be zero)\n",
body->name, body->orbit.semi_major_axis);
toml_free(result);
return false;
}
bool is_parabolic = (fabs(body->orbit.eccentricity - 1.0) < 0.005);
if (body->orbit.eccentricity < 0.0) {
printf("Error: Body '%s' has invalid eccentricity: %.2e (must be >= 0)\n",
@ -214,12 +230,30 @@ bool load_system_config(SimulationState* sim, const char* filepath) {
return false;
}
// For elliptical orbits (e < 1), semi_major_axis must be positive
if (body->orbit.eccentricity < 1.0 && body->orbit.semi_major_axis <= 0.0) {
printf("Error: Body '%s' has elliptical orbit but non-positive semi_major_axis: %.2e\n",
body->name, body->orbit.semi_major_axis);
toml_free(result);
return false;
if (is_parabolic) {
// For parabolic orbits, semi_latus_rectum must be positive
if (body->orbit.semi_latus_rectum <= 0.0) {
printf("Error: Body '%s' has parabolic orbit but non-positive semi_latus_rectum: %.2e\n",
body->name, body->orbit.semi_latus_rectum);
toml_free(result);
return false;
}
} else {
// For elliptical/hyperbolic orbits, semi_major_axis must be non-zero
if (body->orbit.semi_major_axis == 0.0) {
printf("Error: Body '%s' has invalid semi_major_axis: %.2e (must not be zero)\n",
body->name, body->orbit.semi_major_axis);
toml_free(result);
return false;
}
// For elliptical orbits (e < 1), semi_major_axis must be positive
if (body->orbit.eccentricity < 1.0 && body->orbit.semi_major_axis <= 0.0) {
printf("Error: Body '%s' has elliptical orbit but non-positive semi_major_axis: %.2e\n",
body->name, body->orbit.semi_major_axis);
toml_free(result);
return false;
}
}
}
@ -295,16 +329,34 @@ static bool load_spacecraft_from_toml(SimulationState* sim, toml_result_t result
for (int i = 0; i < sim->craft_count; i++) {
Spacecraft* craft = &sim->spacecraft[i];
if (craft->parent_index >= 0 && craft->parent_index < sim->body_count) {
bool is_parabolic = (fabs(craft->orbit.eccentricity - 1.0) < 0.005);
if (!is_parabolic && craft->parent_index >= 0 && craft->parent_index < sim->body_count) {
CelestialBody* parent = &sim->bodies[craft->parent_index];
craft->orbit.semi_major_axis += parent->radius;
}
// Validate semi_major_axis
if (fabs(craft->orbit.semi_major_axis) < 1e-10) {
printf("Error: Spacecraft '%s' has invalid semi_major_axis: %.2e (must not be zero)\n",
craft->name, craft->orbit.semi_major_axis);
return false;
if (is_parabolic) {
// For parabolic orbits, semi_latus_rectum must be positive
if (craft->orbit.semi_latus_rectum <= 0.0) {
printf("Error: Spacecraft '%s' has parabolic orbit but non-positive semi_latus_rectum: %.2e\n",
craft->name, craft->orbit.semi_latus_rectum);
return false;
}
} else {
// For elliptical/hyperbolic orbits, validate semi_major_axis
if (fabs(craft->orbit.semi_major_axis) < 1e-10) {
printf("Error: Spacecraft '%s' has invalid semi_major_axis: %.2e (must not be zero)\n",
craft->name, craft->orbit.semi_major_axis);
return false;
}
// For elliptical orbits (e < 1), semi_major_axis must be positive
if (craft->orbit.eccentricity < 1.0 && craft->orbit.semi_major_axis <= 0.0) {
printf("Error: Spacecraft '%s' has elliptical orbit but non-positive semi_major_axis: %.2e\n",
craft->name, craft->orbit.semi_major_axis);
return false;
}
}
// Validate eccentricity
@ -313,13 +365,6 @@ static bool load_spacecraft_from_toml(SimulationState* sim, toml_result_t result
craft->name, craft->orbit.eccentricity);
return false;
}
// For elliptical orbits (e < 1), semi_major_axis must be positive
if (craft->orbit.eccentricity < 1.0 && craft->orbit.semi_major_axis <= 0.0) {
printf("Error: Spacecraft '%s' has elliptical orbit but non-positive semi_major_axis: %.2e\n",
craft->name, craft->orbit.semi_major_axis);
return false;
}
}
printf("Loaded %d spacecraft from %s\n", craft_count, sim->config_name);
@ -359,24 +404,45 @@ static bool parse_toml_spacecraft(toml_datum_t craft_table, Spacecraft* craft) {
craft->orbit.argument_of_periapsis = 0.0;
craft->orbit.true_anomaly = 0.0;
// Parse semi_major_axis (required) or altitude (convenience)
// Parse semi_major_axis (for elliptical/hyperbolic) or semi_latus_rectum (for parabolic) or altitude (convenience)
toml_datum_t semi_major = toml_get(orbit_table, "semi_major_axis");
toml_datum_t semi_latus = toml_get(orbit_table, "semi_latus_rectum");
toml_datum_t altitude = toml_get(orbit_table, "altitude");
if (semi_major.type == TOML_FP64) {
craft->orbit.semi_major_axis = semi_major.u.fp64;
} else if (altitude.type == TOML_FP64) {
// altitude will be added to parent radius in initialization
craft->orbit.semi_major_axis = altitude.u.fp64;
} else {
printf("Error: Spacecraft '%s' must have either 'semi_major_axis' or 'altitude' in orbit table\n", craft->name);
// Parse eccentricity first to determine which parameter is required
toml_datum_t eccentricity = toml_get(orbit_table, "eccentricity");
if (eccentricity.type != TOML_FP64) {
printf("Error: Spacecraft '%s' missing required 'eccentricity' in orbit table\n", craft->name);
return false;
}
craft->orbit.eccentricity = eccentricity.u.fp64;
// Parse eccentricity (optional, default 0.0)
toml_datum_t eccentricity = toml_get(orbit_table, "eccentricity");
if (eccentricity.type == TOML_FP64) {
craft->orbit.eccentricity = eccentricity.u.fp64;
bool is_parabolic = (fabs(craft->orbit.eccentricity - 1.0) < 0.005);
if (is_parabolic) {
// Parabolic orbit - requires semi_latus_rectum
if (semi_latus.type != TOML_FP64) {
printf("Error: Parabolic orbit for spacecraft '%s' requires 'semi_latus_rectum' (not 'semi_major_axis' or 'altitude')\n", craft->name);
return false;
}
craft->orbit.semi_latus_rectum = semi_latus.u.fp64;
if (semi_major.type == TOML_FP64 || altitude.type == TOML_FP64) {
printf("Warning: Spacecraft '%s' has parabolic eccentricity, 'semi_latus_rectum' used instead of 'semi_major_axis' or 'altitude'\n", craft->name);
}
} else {
// Elliptical or hyperbolic - requires semi_major_axis or altitude
if (semi_major.type == TOML_FP64) {
craft->orbit.semi_major_axis = semi_major.u.fp64;
} else if (altitude.type == TOML_FP64) {
// altitude will be added to parent radius in initialization
craft->orbit.semi_major_axis = altitude.u.fp64;
} else {
printf("Error: Spacecraft '%s' must have either 'semi_major_axis' or 'altitude' in orbit table (non-parabolic orbits)\n", craft->name);
return false;
}
if (semi_latus.type == TOML_FP64) {
printf("Warning: Spacecraft '%s' has non-parabolic eccentricity, 'semi_latus_rectum' ignored\n", craft->name);
}
}
// Parse true_anomaly (optional, default 0.0)

5
src/orbital_mechanics.cpp

@ -18,8 +18,9 @@ void orbital_elements_to_cartesian(OrbitalElements elements, double parent_mass,
} else if (e < 1.0) {
r = a * (1.0 - e * e) / (1.0 + e * cos(nu));
v_mag = sqrt(mu * (2.0 / r - 1.0 / a));
} else if (fabs(e - 1.0) < 1e-10) {
r = 2.0 * a / (1.0 + cos(nu));
} else if (fabs(e - 1.0) < 0.005) {
double p = elements.semi_latus_rectum;
r = p / (1.0 + cos(nu));
v_mag = sqrt(2.0 * mu / r);
} else {
r = a * (1.0 - e * e) / (1.0 + e * cos(nu));

7
src/orbital_mechanics.h

@ -4,12 +4,15 @@
#include "physics.h"
struct OrbitalElements {
double semi_major_axis;
union {
double semi_major_axis; // for elliptical (e<1) and hyperbolic (e>1)
double semi_latus_rectum; // for parabolic (e≈1)
};
double eccentricity;
double true_anomaly;
double inclination;
double longitude_of_ascending_node;
double argument_of_periapsis;
double true_anomaly;
};
void orbital_elements_to_cartesian(OrbitalElements elements, double parent_mass,

12
tests/configs/parabolic_comet.toml

@ -7,10 +7,10 @@ name = "Sun"
mass = 1.989e30
radius = 6.96e8
parent_index = -1
color = { r = 1.0, g = 1.0, b = 0.0 }
color = {r = 1.0,g = 1.0,b = 0.0 }
orbit = {
semi_major_axis = 0.0
eccentricity = 0.0
semi_major_axis = 0.0,
eccentricity = 0.0,
true_anomaly = 0.0
}
@ -19,9 +19,9 @@ name = "ParabolicComet"
mass = 1.0e14
radius = 5.0e3
parent_index = 0
color = { r = 0.7, g = 0.8, b = 0.9 }
color = {r = 0.7,g = 0.8,b = 0.9 }
orbit = {
semi_major_axis = 1.0e30
eccentricity = 1.0
semi_latus_rectum = 1.496e11,
eccentricity = 1.0,
true_anomaly = 0.0
}

Loading…
Cancel
Save