diff --git a/src/config_loader.cpp b/src/config_loader.cpp index 2c7b49c..267b5ba 100644 --- a/src/config_loader.cpp +++ b/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) diff --git a/src/orbital_mechanics.cpp b/src/orbital_mechanics.cpp index f44fac8..a0c01d6 100644 --- a/src/orbital_mechanics.cpp +++ b/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)); diff --git a/src/orbital_mechanics.h b/src/orbital_mechanics.h index 0e9ba0e..6b01db4 100644 --- a/src/orbital_mechanics.h +++ b/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, diff --git a/tests/configs/parabolic_comet.toml b/tests/configs/parabolic_comet.toml index eac1588..fb8881e 100644 --- a/tests/configs/parabolic_comet.toml +++ b/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 }