163 lines
6.1 KiB
GDScript
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
|
|
}
|