diff --git a/docs/unified_orbital_elements_plan.md b/docs/unified_orbital_elements_plan.md index 5d4ede0..abea434 100644 --- a/docs/unified_orbital_elements_plan.md +++ b/docs/unified_orbital_elements_plan.md @@ -224,9 +224,9 @@ struct Spacecraft { - `config_loader.cpp`: Update parsing logic - `maneuver.cpp`: No changes needed -### Phase 3: Update Config Parser +### Phase 3: Update Config Parser ✅ COMPLETE -7. **Modify config_loader.cpp - body parsing** +7. ✅ **Modify config_loader.cpp - body parsing** - Remove old `position` field requirement from `parse_toml_body()` - Add `orbit` table parsing to `parse_toml_body()` - Parse all orbital element fields with defaults @@ -234,12 +234,12 @@ struct Spacecraft { - Parse into `body->orbit` struct - Remove old `eccentricity` and `semi_major_axis` top-level fields -8. **Modify config_loader.cpp - spacecraft parsing** +8. ✅ **Modify config_loader.cpp - spacecraft parsing** - Replace `position` + `velocity` fields with `orbit` table parsing - Use same orbital element parsing logic as bodies - Parse into `craft->orbit` struct -9. **Add validation for orbital elements** +9. ✅ **Add validation for orbital elements** - `semi_major_axis` must not be zero - `eccentricity` must be >= 0 - For elliptical orbits (e < 1): `semi_major_axis > 0` diff --git a/src/config_loader.cpp b/src/config_loader.cpp index 7d156df..3ca1508 100644 --- a/src/config_loader.cpp +++ b/src/config_loader.cpp @@ -59,36 +59,86 @@ static bool parse_toml_body(toml_datum_t body_table, CelestialBody* body) { toml_datum_t mass = toml_get(body_table, "mass"); toml_datum_t radius = toml_get(body_table, "radius"); toml_datum_t parent_idx = toml_get(body_table, "parent_index"); - toml_datum_t eccentricity = toml_get(body_table, "eccentricity"); - toml_datum_t semi_major = toml_get(body_table, "semi_major_axis"); if (mass.type != TOML_FP64 || radius.type != TOML_FP64 || - parent_idx.type != TOML_INT64 || - eccentricity.type != TOML_FP64 || - semi_major.type != TOML_FP64) { + parent_idx.type != TOML_INT64) { return false; } body->mass = mass.u.fp64; body->radius = radius.u.fp64; body->parent_index = (int)(parent_idx.type == TOML_INT64 ? parent_idx.u.int64 : (int)parent_idx.u.fp64); - body->orbit.eccentricity = eccentricity.u.fp64; - body->orbit.semi_major_axis = semi_major.u.fp64; - // Extract position vector - toml_datum_t position = toml_get(body_table, "position"); - if (position.type != TOML_TABLE || !extract_vec3_from_table(position, &body->global_position)) { + // Parse orbit table + toml_datum_t orbit_table = toml_get(body_table, "orbit"); + if (orbit_table.type != TOML_TABLE) { + printf("Error: Body '%s' missing required 'orbit' table\n", body->name); return false; } + // Initialize orbital elements with defaults + body->orbit.semi_major_axis = 0.0; + body->orbit.eccentricity = 0.0; + body->orbit.inclination = 0.0; + body->orbit.longitude_of_ascending_node = 0.0; + body->orbit.argument_of_periapsis = 0.0; + body->orbit.true_anomaly = 0.0; + + // Parse semi_major_axis (required) or altitude (convenience) + toml_datum_t semi_major = toml_get(orbit_table, "semi_major_axis"); + 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); + return false; + } + + // 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; + } + + // Parse true_anomaly (optional, default 0.0) + toml_datum_t true_anomaly = toml_get(orbit_table, "true_anomaly"); + if (true_anomaly.type == TOML_FP64) { + body->orbit.true_anomaly = true_anomaly.u.fp64; + } + + // Parse inclination (optional, default 0.0) + toml_datum_t inclination = toml_get(orbit_table, "inclination"); + if (inclination.type == TOML_FP64) { + body->orbit.inclination = inclination.u.fp64; + } + + // Parse longitude_of_ascending_node (optional, default 0.0) + toml_datum_t raan = toml_get(orbit_table, "longitude_of_ascending_node"); + if (raan.type == TOML_FP64) { + body->orbit.longitude_of_ascending_node = raan.u.fp64; + } + + // Parse argument_of_periapsis (optional, default 0.0) + toml_datum_t aop = toml_get(orbit_table, "argument_of_periapsis"); + if (aop.type == TOML_FP64) { + body->orbit.argument_of_periapsis = aop.u.fp64; + } + // Extract color toml_datum_t color = toml_get(body_table, "color"); if (color.type != TOML_TABLE || !extract_color_from_table(color, body->color)) { return false; } - // Initialize velocity (will be calculated later) + // Initialize velocity and position (will be calculated later from orbital elements) + body->global_position = {0.0, 0.0, 0.0}; body->global_velocity = {0.0, 0.0, 0.0}; + body->local_position = {0.0, 0.0, 0.0}; + body->local_velocity = {0.0, 0.0, 0.0}; body->soi_radius = 0.0; return true; @@ -161,6 +211,35 @@ bool load_system_config(SimulationState* sim, const char* filepath) { } } + // Validate orbital elements + for (int i = 0; i < body_count; i++) { + CelestialBody* body = &sim->bodies[i]; + + // Validate semi_major_axis + if (fabs(body->orbit.semi_major_axis) < 1e-10) { + 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; + } + + // Validate eccentricity + if (body->orbit.eccentricity < 0.0) { + printf("Error: Body '%s' has invalid eccentricity: %.3f (must be >= 0)\n", + body->name, body->orbit.eccentricity); + 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; + } + } + sim->body_count = body_count; strncpy(sim->config_name, filepath, sizeof(sim->config_name) - 1); @@ -217,10 +296,6 @@ static bool load_spacecraft_from_toml(SimulationState* sim, toml_result_t result return false; } - CelestialBody* parent = &sim->bodies[craft.parent_index]; - craft.global_position = vec3_add(parent->global_position, craft.local_position); - craft.global_velocity = vec3_add(parent->global_velocity, craft.local_velocity); - int idx = add_spacecraft(sim, &craft); if (idx < 0) { printf("Error: Failed to add spacecraft to simulation\n"); @@ -228,6 +303,32 @@ static bool load_spacecraft_from_toml(SimulationState* sim, toml_result_t result } } + // Validate spacecraft orbital elements + for (int i = 0; i < sim->craft_count; i++) { + Spacecraft* craft = &sim->spacecraft[i]; + + // 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; + } + + // Validate eccentricity + if (craft->orbit.eccentricity < 0.0) { + printf("Error: Spacecraft '%s' has invalid eccentricity: %.3f (must be >= 0)\n", + 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); return true; } @@ -250,21 +351,70 @@ static bool parse_toml_spacecraft(toml_datum_t craft_table, Spacecraft* craft) { craft->mass = mass.u.fp64; craft->parent_index = (int)(parent_idx.type == TOML_INT64 ? parent_idx.u.int64 : (int)parent_idx.u.fp64); - toml_datum_t position = toml_get(craft_table, "position"); - if (position.type != TOML_TABLE || !extract_vec3_from_table(position, &craft->local_position)) { + // Parse orbit table + toml_datum_t orbit_table = toml_get(craft_table, "orbit"); + if (orbit_table.type != TOML_TABLE) { + printf("Error: Spacecraft '%s' missing required 'orbit' table\n", craft->name); return false; } - craft->global_position = craft->local_position; - toml_datum_t velocity = toml_get(craft_table, "velocity"); - if (velocity.type == TOML_TABLE) { - if (!extract_vec3_from_table(velocity, &craft->local_velocity)) { - return false; - } + // Initialize orbital elements with defaults + craft->orbit.semi_major_axis = 0.0; + craft->orbit.eccentricity = 0.0; + craft->orbit.inclination = 0.0; + craft->orbit.longitude_of_ascending_node = 0.0; + craft->orbit.argument_of_periapsis = 0.0; + craft->orbit.true_anomaly = 0.0; + + // Parse semi_major_axis (required) or altitude (convenience) + toml_datum_t semi_major = toml_get(orbit_table, "semi_major_axis"); + 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 { - craft->local_velocity = {0.0, 0.0, 0.0}; + printf("Error: Spacecraft '%s' must have either 'semi_major_axis' or 'altitude' in orbit table\n", craft->name); + return false; + } + + // 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; + } + + // Parse true_anomaly (optional, default 0.0) + toml_datum_t true_anomaly = toml_get(orbit_table, "true_anomaly"); + if (true_anomaly.type == TOML_FP64) { + craft->orbit.true_anomaly = true_anomaly.u.fp64; } - craft->global_velocity = craft->local_velocity; + + // Parse inclination (optional, default 0.0) + toml_datum_t inclination = toml_get(orbit_table, "inclination"); + if (inclination.type == TOML_FP64) { + craft->orbit.inclination = inclination.u.fp64; + } + + // Parse longitude_of_ascending_node (optional, default 0.0) + toml_datum_t raan = toml_get(orbit_table, "longitude_of_ascending_node"); + if (raan.type == TOML_FP64) { + craft->orbit.longitude_of_ascending_node = raan.u.fp64; + } + + // Parse argument_of_periapsis (optional, default 0.0) + toml_datum_t aop = toml_get(orbit_table, "argument_of_periapsis"); + if (aop.type == TOML_FP64) { + craft->orbit.argument_of_periapsis = aop.u.fp64; + } + + // Initialize position and velocity (will be calculated later from orbital elements) + craft->global_position = {0.0, 0.0, 0.0}; + craft->global_velocity = {0.0, 0.0, 0.0}; + craft->local_position = {0.0, 0.0, 0.0}; + craft->local_velocity = {0.0, 0.0, 0.0}; return true; }