Files
millimeters-of-aluminum/scenes/ship/computer/shards/nav_hohman_planner.gd

163 lines
6.1 KiB
GDScript

# scenes/ship/computer/shards/maneuver_planner_databank.gd
extends Databank
class_name HohmanPlannerShard
## Emitted when a maneuver plan has been successfully calculated.
signal maneuver_calculated(plan: Array[DataTypes.ImpulsiveBurnPlan])
# --- References ---
var selection_shard: NavSelectionShard
var target_body: OrbitalBody3D = null
# --- Configurations ---
var boost_factor: float = 1.0
## Describes the functions this shard needs as input.
func get_input_sockets() -> Array[String]:
return ["target_updated", "calculate_hohmann_transfer"]
## Describes the signals this shard can output.
func get_output_sockets() -> Array[String]:
return ["maneuver_calculated"]
# INPUT SOCKET: Connected to the NavSelectionShard's "target_selected" signal.
func target_updated(new_target: OrbitalBody3D):
print("MANEUVER PLANNER: Target recieved %s." % new_target)
target_body = new_target
# In a UI, this would enable the "Calculate" button.
func set_boost_factor(value: float):
boost_factor = value
# UI ACTION: A panel button would call this function.
func calculate_hohmann_transfer():
if not is_instance_valid(root_module) or not is_instance_valid(target_body):
print("MANEUVER PLANNER: Cannot calculate without ship and target.")
return
var star = GameManager.current_star_system.get_star()
if not is_instance_valid(star): return
var mu = OrbitalMechanics.G * star.mass
var r1 = root_module.global_position.distance_to(star.global_position)
var r2 = target_body.global_position.distance_to(star.global_position)
var a_transfer = (r1 + r2) / 2.0 * boost_factor
var v_source_orbit = sqrt(mu / r1)
var v_target_orbit = sqrt(mu / r2)
var v_transfer_periapsis = sqrt(mu * ((2.0 / r1) - (1.0 / a_transfer)))
var v_transfer_apoapsis = sqrt(mu * ((2.0 / r2) - (1.0 / a_transfer)))
var delta_v1 = v_transfer_periapsis - v_source_orbit
var delta_v2 = v_target_orbit - v_transfer_apoapsis
var time_of_flight = PI * sqrt(pow(a_transfer, 3) / mu)
var ang_vel_target = sqrt(mu / pow(r2, 3))
var travel_angle = ang_vel_target * time_of_flight
var required_phase_angle = PI - travel_angle
var vec_to_ship = (root_module.global_position - star.global_position).normalized()
var vec_to_target = (target_body.global_position - star.global_position).normalized()
var current_phase_angle = vec_to_ship.angle_to(vec_to_target)
var ang_vel_ship = sqrt(mu / pow(r1, 3))
var relative_ang_vel = ang_vel_ship - ang_vel_target
var angle_to_wait = current_phase_angle - required_phase_angle
if relative_ang_vel == 0: return # Avoid division by zero
var wait_time = abs(angle_to_wait / relative_ang_vel)
# TODO: Need a way to get this from a shared calibration databank shard
var main_engine_thrust = 0.0
for thruster in root_module.get_components():
if thruster is Thruster and thruster.main_thruster:
main_engine_thrust += thruster.max_thrust
if main_engine_thrust == 0: return
var acceleration = main_engine_thrust / root_module.mass
# --- Use the absolute value of delta_v for burn duration ---
var burn_duration1 = abs(delta_v1) / acceleration
var burn_duration2 = abs(delta_v2) / acceleration
# --- NEW: Predict the ship's state at the time of the burn ---
var predicted_state = _predict_state_after_coast(root_module, star, wait_time)
var predicted_velocity_vec = predicted_state["velocity"]
var plan: Array[DataTypes.ImpulsiveBurnPlan] = []
var burn1 = DataTypes.ImpulsiveBurnPlan.new()
burn1.delta_v_magnitude = abs(delta_v1)
burn1.wait_time = wait_time
burn1.burn_duration = burn_duration1
# --- Determine rotation based on the sign of delta_v ---
# Prograde (speeding up) or retrograde (slowing down)
var prograde_vec = predicted_velocity_vec.normalized()
var up_vec = Vector3.UP
var target_basis = Basis.looking_at(prograde_vec, up_vec)
# For retrograde (slowing down), we look "backward"
if delta_v1 < 0:
target_basis = Basis.looking_at(-prograde_vec, up_vec)
burn1.desired_basis = target_basis # Renamed in data_types.gd
plan.append(burn1)
var burn2 = DataTypes.ImpulsiveBurnPlan.new()
burn2.delta_v_magnitude = delta_v2
burn2.wait_time = time_of_flight - burn_duration1
burn2.burn_duration = burn_duration2
# --- Determine rotation for the second burn ---
target_basis = Basis.looking_at(-prograde_vec, up_vec)
# For retrograde (slowing down), we look "backward"
if delta_v2 < 0:
target_basis = Basis.looking_at(prograde_vec, up_vec)
var target_prograde_direction = (target_body.global_position - star.global_position).orthogonal().angle()
burn2.desired_basis = target_prograde_direction if delta_v2 >= 0 else target_prograde_direction + PI
plan.append(burn2)
print("Hohmann Plan:")
print(" - Wait: %d s" % wait_time)
print(" - Burn 1: %.1f m/s (%.1f s)" % [delta_v1, burn_duration1])
print(" - Flight time: %d s" % time_of_flight)
print(" - Burn 2: %.1f m/s (%.1f s)" % [delta_v2, burn_duration2])
print("MANEUVER PLANNER: Hohmann transfer calculated. Emitting plan.")
maneuver_calculated.emit(plan)
# Simulates the ship's 2-body orbit around the star to predict its future state.
func _predict_state_after_coast(body_to_trace: OrbitalBody3D, primary: OrbitalBody3D, time: float) -> Dictionary:
# --- Simulation Parameters ---
var time_step = 1.0 # Simulate in 1-second increments
var num_steps = int(ceil(time / time_step))
# --- Initial State (relative to the primary) ---
var ghost_relative_pos = body_to_trace.global_position - primary.global_position
var ghost_relative_vel = body_to_trace.linear_velocity - primary.linear_velocity
var mu = OrbitalMechanics.G * primary.mass
for i in range(num_steps):
# --- Physics Calculation ---
var distance_sq = ghost_relative_pos.length_squared()
if distance_sq < 1.0: break
var direction = -ghost_relative_pos.normalized()
var force_magnitude = mu / distance_sq # Simplified F = mu*m/r^2 and a=F/m
var acceleration = direction * force_magnitude
# --- Integration (Euler method) ---
ghost_relative_vel += acceleration * time_step
ghost_relative_pos += ghost_relative_vel * time_step
# --- Return the final state, converted back to global space ---
return {
"position": ghost_relative_pos + primary.global_position,
"velocity": ghost_relative_vel + primary.linear_velocity
}