WIP 3d refactor COMPILING
This commit is contained in:
@ -1,30 +1,24 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://b1kpyek60vyof"]
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dhp1kc684qklv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_1abiy"]
|
||||
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_risxe"]
|
||||
|
||||
[node name="Module" type="RigidBody2D"]
|
||||
position = Vector2(-50, 50)
|
||||
mass = null
|
||||
mass = 1.0
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector2(-50, 0)
|
||||
inertia = null
|
||||
linear_velocity = null
|
||||
angular_velocity = null
|
||||
inertia = 0.0
|
||||
linear_velocity = Vector2(0, 0)
|
||||
angular_velocity = 0.0
|
||||
script = ExtResource("1_1abiy")
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_risxe")]
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="@StaticBody2D@23989" parent="StructuralContainer" instance=ExtResource("2_risxe")]
|
||||
position = Vector2(-100, 0)
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
|
||||
@ -1,30 +1,24 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://baeikwxkh26fh"]
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cvqs2vivnrepf"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_1rae4"]
|
||||
[ext_resource type="PackedScene" uid="uid://bho8x10x4oab7" path="res://scenes/ship/builder/pieces/hullplate.tscn" id="2_fbnt1"]
|
||||
|
||||
[node name="Module" type="RigidBody2D"]
|
||||
position = Vector2(-50, 50)
|
||||
mass = null
|
||||
mass = 1.0
|
||||
center_of_mass_mode = 1
|
||||
center_of_mass = Vector2(-50, 0)
|
||||
inertia = null
|
||||
linear_velocity = null
|
||||
angular_velocity = null
|
||||
inertia = 0.0
|
||||
linear_velocity = Vector2(0, 0)
|
||||
angular_velocity = 0.0
|
||||
script = ExtResource("1_1rae4")
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="Hullplate" parent="StructuralContainer" instance=ExtResource("2_fbnt1")]
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="@StaticBody2D@23989" parent="StructuralContainer" instance=ExtResource("2_fbnt1")]
|
||||
position = Vector2(-100, 0)
|
||||
base_mass = null
|
||||
inertia = null
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ signal follow_requested(body: Node2D)
|
||||
|
||||
@onready var name_label: Label = $NameLabel
|
||||
|
||||
var body_reference: OrbitalBody2D
|
||||
var body_reference: OrbitalBody3D
|
||||
var dot_color: Color = Color.WHITE
|
||||
|
||||
var hover_tween: Tween
|
||||
@ -27,7 +27,7 @@ func _ready() -> void:
|
||||
mouse_entered.connect(_on_mouse_entered)
|
||||
mouse_exited.connect(_on_mouse_exited)
|
||||
|
||||
func initialize(body: OrbitalBody2D):
|
||||
func initialize(body: OrbitalBody3D):
|
||||
body_reference = body
|
||||
name_label.text = body.name
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class_name Asteroid
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
|
||||
# The orbital radius for this asteroid.
|
||||
var orbital_radius: float
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class_name Moon
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
|
||||
# The orbital radius for this moon.
|
||||
var orbital_radius: float
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class_name Planet
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
|
||||
# The orbital radius for this planet.
|
||||
var orbital_radius: float
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class_name Star
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
|
||||
func get_class_name() -> String:
|
||||
return "Star"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class_name Station
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
|
||||
# The orbital radius for this station.
|
||||
var orbital_radius: float
|
||||
|
||||
@ -63,7 +63,7 @@ func _ready():
|
||||
|
||||
camera.make_current()
|
||||
|
||||
func on_body_entered(body: Node2D):
|
||||
func on_body_entered(body: OrbitalBody3D):
|
||||
# Detect Modules (which all inherit OrbitalBody2D via StructuralPiece)
|
||||
if body is StructuralPiece:
|
||||
overlapping_modules += 1
|
||||
@ -72,7 +72,7 @@ func on_body_entered(body: Node2D):
|
||||
if body is Ladder:
|
||||
ladder_area = body.find_child("ClimbArea") # Assuming the Ladder has a specific Area2D for climbing
|
||||
|
||||
func on_body_exited(body: Node2D):
|
||||
func on_body_exited(body: OrbitalBody3D):
|
||||
if body is StructuralPiece:
|
||||
overlapping_modules -= 1
|
||||
|
||||
@ -126,13 +126,13 @@ func process_interaction():
|
||||
|
||||
# Priority 1: Disengage from a station if we are in one.
|
||||
if current_station:
|
||||
current_station.disengage(self)
|
||||
# current_station.disengage(self)
|
||||
current_station = null
|
||||
return
|
||||
# Priority 2: Occupy a nearby station if we are not in one.
|
||||
elif is_instance_valid(nearby_station):
|
||||
current_station = nearby_station
|
||||
current_station.occupy(self)
|
||||
# current_station.occupy(self)
|
||||
return
|
||||
|
||||
# Priority 3: Handle ladder launch logic.
|
||||
|
||||
@ -60,11 +60,11 @@ func _unhandled_input(event: InputEvent):
|
||||
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
camera.zoom /= 1.2
|
||||
|
||||
func _on_marker_selected(body: Node2D):
|
||||
func _on_marker_selected(body):
|
||||
# Update the info panel with the selected body's data.
|
||||
var text = "[b]%s[/b]\n" % body.name
|
||||
|
||||
if body is OrbitalBody2D:
|
||||
if body is OrbitalBody3D:
|
||||
text += "Mass: %.2f\n" % body.mass
|
||||
text += "Velocity: (%.2f, %.2f)\n" % [body.linear_velocity.x, body.linear_velocity.y]
|
||||
text += "Position: (%.0f, %.0f)\n" % [body.global_position.x, body.global_position.y]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
@tool
|
||||
class_name Module
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
|
||||
@export var ship_name: String = "Unnamed Ship" # Only relevant for the root module
|
||||
@export var hull_integrity: float = 100.0 # This could also be a calculated property later
|
||||
@ -43,14 +43,14 @@ func get_attachment_points() -> Array:
|
||||
|
||||
# --- Bulkheads (Interior and Exterior Edge Attachments) ---
|
||||
elif piece is Bulkhead:
|
||||
var interior_point = piece_center + piece.transform.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
var interior_point = piece_center + piece.transform.origin.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
points.append({
|
||||
"position": interior_point,
|
||||
"type": Component.AttachmentType.INTERIOR_WALL,
|
||||
"piece": piece
|
||||
})
|
||||
|
||||
var exterior_point = piece_center - piece.transform.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
var exterior_point = piece_center - piece.transform.origin.y * (COMPONENT_GRID_SIZE / 2.0)
|
||||
points.append({
|
||||
"position": exterior_point,
|
||||
"type": Component.AttachmentType.EXTERIOR_HULL,
|
||||
@ -60,7 +60,7 @@ func get_attachment_points() -> Array:
|
||||
return points
|
||||
|
||||
# --- This function remains largely the same ---
|
||||
func attach_component(component: Component, global_pos: Vector2, parent_piece: StructuralPiece):
|
||||
func attach_component(component: Component, global_pos: Vector3, parent_piece: StructuralPiece):
|
||||
component.position = global_pos - global_position
|
||||
component.attached_piece = parent_piece
|
||||
add_child(component)
|
||||
@ -82,7 +82,7 @@ func _recalculate_collision_shape():
|
||||
# combined_polygons.append(piece_collision_shape.shape.points)
|
||||
pass
|
||||
|
||||
# NOTE: The OrbitalBody2D's _update_mass_and_inertia() takes care of mass!
|
||||
# NOTE: The OrbitalBody3D's _update_mass_and_inertia() takes care of mass!
|
||||
pass
|
||||
|
||||
# --- UPDATED: Clear module now iterates over all relevant children ---
|
||||
|
||||
@ -2,12 +2,6 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://6co67nfy8ngb" path="res://scenes/ship/builder/module.gd" id="1_b1h2b"]
|
||||
|
||||
[node name="Module" type="Node2D"]
|
||||
[node name="Module" type="Node3D"]
|
||||
script = ExtResource("1_b1h2b")
|
||||
metadata/_custom_type_script = "uid://0isnsk356que"
|
||||
|
||||
[node name="StructuralContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="HullVolumeContainer" type="Node2D" parent="."]
|
||||
|
||||
[node name="AtmosphereVisualizer" type="Node2D" parent="."]
|
||||
mass = 1.0
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
@tool
|
||||
class_name StructuralPiece
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
|
||||
# Does this piece block atmosphere? (e.g., a hull plate would, a girder would not).
|
||||
@export var is_pressurized: bool = true
|
||||
@ -12,9 +12,9 @@ extends OrbitalBody2D
|
||||
var is_preview: bool = false:
|
||||
set(value):
|
||||
is_preview = value
|
||||
if is_preview:
|
||||
# Make the piece translucent if it's a preview.
|
||||
modulate = Color(1, 1, 1, 0.5)
|
||||
else:
|
||||
# Make it opaque if it's a permanent piece.
|
||||
modulate = Color(1, 1, 1, 1)
|
||||
# if is_preview:
|
||||
# # Make the piece translucent if it's a preview.
|
||||
# modulate = Color(1, 1, 1, 0.5)
|
||||
# else:
|
||||
# # Make it opaque if it's a permanent piece.
|
||||
# modulate = Color(1, 1, 1, 1)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
class_name Component
|
||||
|
||||
# Defines the size of the component in terms of the grid (e.g., 1x1, 1x2, 2x2)
|
||||
|
||||
@ -16,7 +16,7 @@ var UiWindowScene = preload("res://scenes/UI/ui_window.tscn")
|
||||
var wiring_schematic: WiringSchematic
|
||||
|
||||
# --- State ---
|
||||
var occupants: Array[PilotBall] = []
|
||||
var occupants: Array[CharacterPawn3D] = []
|
||||
var active_shard_instances: Array[Databank] = []
|
||||
var persistent_panel_instances: Array[BasePanel] = []
|
||||
var occupant_panel_map: Dictionary = {}
|
||||
@ -72,7 +72,7 @@ func _process(_delta):
|
||||
func is_occupied() -> bool:
|
||||
return not occupants.is_empty()
|
||||
|
||||
func occupy(character: PilotBall):
|
||||
func occupy(character: CharacterPawn3D):
|
||||
if character in occupants: return
|
||||
|
||||
occupants.append(character)
|
||||
@ -84,7 +84,7 @@ func occupy(character: PilotBall):
|
||||
|
||||
occupancy_changed.emit(true)
|
||||
|
||||
func disengage(character: PilotBall):
|
||||
func disengage(character: CharacterPawn3D):
|
||||
if not character in occupants: return
|
||||
|
||||
# --- FIX: Close UI for THIS character only ---
|
||||
@ -98,7 +98,7 @@ func disengage(character: PilotBall):
|
||||
|
||||
# --- UI MANAGEMENT ---
|
||||
|
||||
func close_interfaces_for_occupant(character: PilotBall):
|
||||
func close_interfaces_for_occupant(character: CharacterPawn3D):
|
||||
if occupant_panel_map.has(character):
|
||||
occupant_panel_map[character].queue_free()
|
||||
occupant_panel_map.erase(character)
|
||||
@ -109,7 +109,7 @@ func close_interface(c: Control):
|
||||
occupant_panel_map[occupant].queue_free()
|
||||
occupant_panel_map.erase(occupant)
|
||||
|
||||
func launch_interfaces_for_occupant(character: PilotBall):
|
||||
func launch_interfaces_for_occupant(character: CharacterPawn3D):
|
||||
var ui_container = character.get_ui_container()
|
||||
if not ui_container: return
|
||||
|
||||
|
||||
@ -74,41 +74,38 @@ func _physics_process(delta: float):
|
||||
# If the thruster is active, apply a constant central force in its local "up" direction.
|
||||
if is_firing:
|
||||
apply_thrust_force()
|
||||
|
||||
# Also, ensure the visual effect is running
|
||||
queue_redraw()
|
||||
|
||||
# Function called by the ThrusterController system to fire the thruster
|
||||
func apply_thrust_force():
|
||||
if is_firing:
|
||||
# 1. Calculate the local force vector (magnitude and direction)
|
||||
var local_force = Vector2.UP * max_thrust
|
||||
var local_force = Vector3.UP * max_thrust
|
||||
|
||||
# 2. FIX: Convert the force to global space using ONLY the rotation (basis).
|
||||
# This ensures the force vector's magnitude is not corrupted by the thruster's global position.
|
||||
# NOTE: This replaces the problematic global_transform * vector or global_transform.xform(vector)
|
||||
var force_vector = global_transform.basis_xform(local_force)
|
||||
var force_vector = global_transform.basis * local_force
|
||||
|
||||
# 3. Apply the force to itself.
|
||||
apply_force(force_vector, global_position)
|
||||
|
||||
func _draw():
|
||||
# This function is only called if the thruster is firing (due to queue_redraw)
|
||||
if not is_firing:
|
||||
return
|
||||
# func _draw():
|
||||
# # This function is only called if the thruster is firing (due to queue_redraw)
|
||||
# if not is_firing:
|
||||
# return
|
||||
|
||||
# --- Draw a fiery, flickering cone ---
|
||||
# The plume goes in the OPPOSITE direction of the thrust
|
||||
var plume_direction = Vector2.DOWN
|
||||
var plume_length = randf_range(20.0, 30.0) # Random length for a flickering effect
|
||||
# # --- Draw a fiery, flickering cone ---
|
||||
# # The plume goes in the OPPOSITE direction of the thrust
|
||||
# var plume_direction = Vector2.DOWN
|
||||
# var plume_length = randf_range(20.0, 30.0) # Random length for a flickering effect
|
||||
|
||||
# Define the 3 points of a triangle for the cone
|
||||
var tip = plume_direction * plume_length
|
||||
var base_offset = plume_direction.orthogonal() * 8.0
|
||||
var base1 = base_offset
|
||||
var base2 = -base_offset
|
||||
# # Define the 3 points of a triangle for the cone
|
||||
# var tip = plume_direction * plume_length
|
||||
# var base_offset = plume_direction.orthogonal() * 8.0
|
||||
# var base1 = base_offset
|
||||
# var base2 = -base_offset
|
||||
|
||||
var points = PackedVector2Array([base1, tip, base2])
|
||||
# var points = PackedVector2Array([base1, tip, base2])
|
||||
|
||||
# Draw the cone with a fiery color
|
||||
draw_polygon(points, PackedColorArray([Color.ORANGE_RED, Color.GOLD, Color.ORANGE_RED]))
|
||||
# # Draw the cone with a fiery color
|
||||
# draw_polygon(points, PackedColorArray([Color.ORANGE_RED, Color.GOLD, Color.ORANGE_RED]))
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
class_name SensorPanel
|
||||
extends BasePanel
|
||||
|
||||
signal body_selected_for_planning(body: OrbitalBody2D)
|
||||
signal body_selected_for_planning(body: OrbitalBody3D)
|
||||
|
||||
@export var map_icon_scene: PackedScene
|
||||
|
||||
@ -13,11 +13,11 @@ const ICON_CULLING_PIXEL_THRESHOLD = 40.0
|
||||
|
||||
var map_scale: float = 0.001
|
||||
var map_offset: Vector2 = Vector2.ZERO
|
||||
var focal_body: OrbitalBody2D
|
||||
var focal_body: OrbitalBody3D
|
||||
|
||||
var icon_map: Dictionary = {}
|
||||
|
||||
var followed_body: OrbitalBody2D = null
|
||||
var followed_body: OrbitalBody3D = null
|
||||
var map_tween: Tween
|
||||
|
||||
# The starting point for our lerp animation.
|
||||
@ -37,7 +37,7 @@ func get_input_sockets():
|
||||
return ["update_sensor_feed"]
|
||||
|
||||
# This is now the primary input for the map. It receives the "sensor feed".
|
||||
func update_sensor_feed(all_bodies: Array[OrbitalBody2D]):
|
||||
func update_sensor_feed(all_bodies: Array[OrbitalBody3D]):
|
||||
# This function replaces the old _populate_map logic.
|
||||
# We'll check which bodies are new and which have been removed.
|
||||
var bodies_in_feed = all_bodies.duplicate()
|
||||
@ -69,7 +69,7 @@ func _draw() -> void:
|
||||
# TODO: The calculation of the projections should be moved into a databank
|
||||
# as this panel should only ever display control nodes and possibly projection paths that are fed to it
|
||||
var star_system = GameManager.current_star_system
|
||||
var star_orbiters: Array[OrbitalBody2D] = []
|
||||
var star_orbiters: Array[OrbitalBody3D] = []
|
||||
star_orbiters.append(star_system.get_star())
|
||||
star_orbiters.append_array(star_system.get_planetary_systems())
|
||||
star_orbiters.append_array(star_system.get_orbital_bodies())
|
||||
@ -86,7 +86,7 @@ func _draw() -> void:
|
||||
draw_projected_orbits(planet_system.get_internal_attractors())
|
||||
else: continue
|
||||
|
||||
func draw_projected_orbits(bodies_to_project: Array[OrbitalBody2D]):
|
||||
func draw_projected_orbits(bodies_to_project: Array[OrbitalBody3D]):
|
||||
var map_center = get_rect().size / 2.0
|
||||
var focal_body = bodies_to_project[0]
|
||||
|
||||
@ -192,10 +192,10 @@ func _gui_input(event: InputEvent) -> void:
|
||||
|
||||
map_offset += event.relative
|
||||
|
||||
func _on_map_icon_selected(body: OrbitalBody2D):
|
||||
func _on_map_icon_selected(body: OrbitalBody3D):
|
||||
body_selected_for_planning.emit(body)
|
||||
|
||||
func _on_follow_requested(body: OrbitalBody2D):
|
||||
func _on_follow_requested(body: OrbitalBody3D):
|
||||
print("Map view locking on to: ", body.name)
|
||||
follow_progress = 0.0
|
||||
followed_body = body
|
||||
|
||||
@ -23,21 +23,21 @@ class ImpulsiveBurnPlan:
|
||||
var delta_v_magnitude: float
|
||||
var wait_time: float = 0.0
|
||||
var burn_duration: float
|
||||
var desired_rotation_rad: float
|
||||
var desired_rotation_rad: Basis
|
||||
|
||||
class PathProjection:
|
||||
var body_ref: OrbitalBody2D
|
||||
var body_ref: OrbitalBody3D
|
||||
var points: Array[PathPoint]
|
||||
|
||||
func _init(b: OrbitalBody2D):
|
||||
func _init(b: OrbitalBody3D):
|
||||
body_ref = b
|
||||
|
||||
class PathPoint:
|
||||
var time: float # Time in seconds from the start of the projection
|
||||
var position: Vector2
|
||||
var velocity: Vector2
|
||||
var position: Vector3
|
||||
var velocity: Vector3
|
||||
|
||||
func _init(t: float, p: Vector2, v: Vector2):
|
||||
func _init(t: float, p: Vector3, v: Vector3):
|
||||
time = t
|
||||
position = p
|
||||
velocity = v
|
||||
|
||||
@ -54,7 +54,7 @@ func execute_plan():
|
||||
print("AUTOPILOT: Executing plan. Waiting for first burn window.")
|
||||
|
||||
for step in current_plan:
|
||||
status = "Performing Rotation: T- %f" % rad_to_deg(step.desired_rotation_rad)
|
||||
# status = "Performing Rotation: T- %f" % rad_to_deg(step.desired_rotation_rad)
|
||||
var time_elapsed: float = await _execute_autopilot_rotation(step)
|
||||
|
||||
current_timer = get_tree().create_timer(step.wait_time - time_elapsed)
|
||||
@ -95,32 +95,59 @@ func _execute_next_burn(step: DataTypes.ImpulsiveBurnPlan):
|
||||
# --- AUTOPILOT "BANG-COAST-BANG" LOGIC (REFACTORED) ---
|
||||
func _execute_autopilot_rotation(step: DataTypes.ImpulsiveBurnPlan) -> float:
|
||||
var time_window = minf(step.wait_time, max_rot_time)
|
||||
var angle_to_turn = shortest_angle_between(root_module.rotation, step.desired_rotation_rad)
|
||||
|
||||
# --- 3D REFACTOR ---
|
||||
# 1. We assume 'step.desired_rotation_rad' is now 'step.desired_basis'
|
||||
# You MUST update your planners (Hohman, Brachistochrone) to
|
||||
# calculate a target Basis (e.g., Basis.looking_at(prograde_vec, Vector3.UP))
|
||||
# and store it in the ImpulsiveBurnPlan.
|
||||
var step_target_basis: Basis = step.desired_rotation_rad # DANGER: This line assumes you updated DataTypes.
|
||||
# For this to compile, you MUST change ImpulsiveBurnPlan in data_types.gd:
|
||||
# var desired_rotation_rad: float -> var desired_basis: Basis
|
||||
var error_quaternion: Quaternion = shortest_rotation_between(root_module.global_transform.basis, step_target_basis)
|
||||
var angle_to_turn: float = error_quaternion.get_angle()
|
||||
var axis_to_turn: Vector3 = error_quaternion.get_axis()
|
||||
|
||||
var init_time = Time.get_ticks_msec()
|
||||
|
||||
if abs(angle_to_turn) < 0.01:
|
||||
request_rotation.emit(step.desired_rotation_rad)
|
||||
if angle_to_turn < 0.01: # Already aligned
|
||||
request_rotation.emit(step_target_basis) # Send the Basis to the helm
|
||||
request_attitude_hold.emit(true)
|
||||
|
||||
return 0.0
|
||||
return 0.0
|
||||
|
||||
# --- Get the specific torque values for each phase ---
|
||||
var accel_torque = RCS_calibration.max_pos_torque if angle_to_turn > 0 else RCS_calibration.max_neg_torque
|
||||
var decel_torque = RCS_calibration.max_neg_torque if angle_to_turn > 0 else RCS_calibration.max_pos_torque
|
||||
# --- 3D Torque Calculation ---
|
||||
# This logic is now much more complex. We need to find the torque
|
||||
# vector to apply.
|
||||
# For a simple "bang-bang" controller, we just apply max torque
|
||||
# along the calculated axis.
|
||||
|
||||
if accel_torque == 0 or decel_torque == 0:
|
||||
print("AUTOPILOT ERROR: Missing thrusters for a full rotation.")
|
||||
return 0.0
|
||||
|
||||
print(" - Performing rotation.")
|
||||
# TODO: This assumes your calibration data is now 3D
|
||||
# (e.g., max_pos_torque is now max_torque_vector).
|
||||
# This needs a calibration refactor, which is complex.
|
||||
|
||||
# --- SIMPLIFIED 3D LOGIC (for now) ---
|
||||
# We'll re-use the PD controller logic from your Helm Shard
|
||||
# to get a torque vector.
|
||||
var error_torque = axis_to_turn * angle_to_turn * RCS_calibration.max_pos_torque # (This is a P-controller)
|
||||
var damping_torque = -root_module.angular_velocity * (RCS_calibration.max_pos_torque * 0.5) # (This is a D-controller)
|
||||
var desired_torque_vector = error_torque + damping_torque
|
||||
|
||||
# We are no longer calculating burn times, we are just applying
|
||||
# torque until we reach the target.
|
||||
# This is a full change from bang-bang to a PD controller.
|
||||
|
||||
# --- REFACTORING THE "BANG-BANG" LOGIC FOR 3D ---
|
||||
# Let's stick closer to your original design.
|
||||
|
||||
# 1. Get calibrated torque values (this now needs to be per-axis)
|
||||
# Let's assume a simplified calibration for now.
|
||||
var accel_torque_magnitude = RCS_calibration.max_pos_torque # Needs refactor
|
||||
var decel_torque_magnitude = RCS_calibration.max_neg_torque # Needs refactor
|
||||
|
||||
var accel_angular_accel = accel_torque_magnitude / root_module.inertia
|
||||
var decel_angular_accel = decel_torque_magnitude / root_module.inertia
|
||||
|
||||
# --- Asymmetrical Burn Calculation ---
|
||||
# This is a more complex kinematic problem. We solve for the peak velocity and individual times.
|
||||
var accel_angular_accel = accel_torque / root_module.inertia
|
||||
var decel_angular_accel = decel_torque / root_module.inertia
|
||||
|
||||
# Solve for peak angular velocity (ω_peak) and times (t1, t2)
|
||||
var peak_angular_velocity = (2 * angle_to_turn * accel_angular_accel * decel_angular_accel) / (accel_angular_accel + decel_angular_accel)
|
||||
peak_angular_velocity = sqrt(abs(peak_angular_velocity)) * sign(angle_to_turn)
|
||||
|
||||
@ -130,31 +157,27 @@ func _execute_autopilot_rotation(step: DataTypes.ImpulsiveBurnPlan) -> float:
|
||||
var total_maneuver_time = accel_burn_time + decel_burn_time
|
||||
|
||||
if total_maneuver_time > time_window:
|
||||
print("AUTOPILOT WARNING: Maneuver is impossible in the given time window. Performing max-power turn.")
|
||||
# Fallback to a simple 50/50 burn if time is too short.
|
||||
accel_burn_time = time_window / 2.0
|
||||
decel_burn_time = time_window / 2.0
|
||||
|
||||
# No coast time in this simplified model, but it could be added back with more complex math.
|
||||
|
||||
print(" - Asymmetrical Rotation Plan: Accel Burn %.2fs, Decel Burn %.2fs" % [accel_burn_time, decel_burn_time])
|
||||
# --- Execute Maneuver (3D) ---
|
||||
|
||||
# --- Execute Maneuver ---
|
||||
|
||||
# ACCELERATION BURN
|
||||
request_rotation_thrust.emit(sign(angle_to_turn))
|
||||
# ACCELERATION BURN: Apply torque along the axis
|
||||
request_rotation_thrust.emit(axis_to_turn * accel_torque_magnitude)
|
||||
await get_tree().create_timer(accel_burn_time).timeout
|
||||
|
||||
# DECELERATION BURN
|
||||
print(" - Rotation acceleration complete, executing deceleration burn.")
|
||||
request_rotation_thrust.emit(sign(-angle_to_turn))
|
||||
# DECELERATION BURN: Apply torque against the axis
|
||||
request_rotation_thrust.emit(-axis_to_turn * decel_torque_magnitude)
|
||||
await get_tree().create_timer(decel_burn_time).timeout
|
||||
|
||||
print(" - Rotation de-acceleration complete, executing deceleration burn.")
|
||||
request_rotation.emit(step.desired_rotation_rad)
|
||||
# Stop all torque
|
||||
request_rotation_thrust.emit(Vector3.ZERO)
|
||||
|
||||
# Set final hold
|
||||
request_rotation.emit(step_target_basis)
|
||||
request_attitude_hold.emit(true)
|
||||
|
||||
print("AUTOPILOT: Rotation maneuver complete.")
|
||||
print("AUTOPILOT: 3D Rotation maneuver complete.")
|
||||
|
||||
return init_time - Time.get_ticks_msec()
|
||||
|
||||
@ -168,3 +191,29 @@ func shortest_angle_between(from_angle: float, to_angle: float) -> float:
|
||||
return difference - TAU
|
||||
else:
|
||||
return difference
|
||||
|
||||
# A simple class to hold the result of a Basis comparison.
|
||||
class BasisComparisonResult:
|
||||
var axis: Vector3
|
||||
var angle: float
|
||||
|
||||
func _init(axis: Vector3, angle: float):
|
||||
self.axis = axis
|
||||
self.angle = angle
|
||||
|
||||
# Finds the shortest rotation (as an axis and angle) between two Basis objects.
|
||||
# Returns a Dictionary: {"axis": Vector3, "angle": float}
|
||||
func shortest_rotation_between(from_basis: Basis, to_basis: Basis) -> Quaternion:
|
||||
var current_quat = from_basis.get_rotation_quaternion().normalized()
|
||||
var target_quat = to_basis.get_rotation_quaternion().normalized()
|
||||
|
||||
# Calculate the difference quaternion (rotation from 'current' to 'target')
|
||||
var diff_quat = target_quat * current_quat.inverse()
|
||||
|
||||
# Ensure we're taking the shortest path.
|
||||
# A quaternion and its negative represent the same orientation,
|
||||
# but one is the "long way around".
|
||||
if diff_quat.w < 0:
|
||||
diff_quat = -diff_quat
|
||||
|
||||
return diff_quat
|
||||
|
||||
@ -8,7 +8,7 @@ class_name HelmLogicShard
|
||||
@export var HOLD_KP: float = 8000.0 # Proportional gain
|
||||
@export var HOLD_KD: float = 1200.0 # Derivative gain
|
||||
|
||||
@onready var target_rotation_rad: float = 0.0
|
||||
@onready var target_rotation_rad: Basis
|
||||
var attitude_hold_enabled: bool = false
|
||||
|
||||
var thruster_calibration_data: DataTypes.ThrusterCalibration
|
||||
@ -32,14 +32,14 @@ func initialize(ship_root: Module):
|
||||
# You can add logic here to listen for parts being added/removed to re-scan.
|
||||
|
||||
# Default to holding the initial attitude.
|
||||
target_rotation_rad = root_module.rotation
|
||||
target_rotation_rad = root_module.basis
|
||||
|
||||
func _physics_process(_delta):
|
||||
if not is_instance_valid(root_module): return
|
||||
|
||||
# If attitude hold is on, run the PD controller.
|
||||
if attitude_hold_enabled:
|
||||
_perform_manual_hold()
|
||||
# # If attitude hold is on, run the PD controller.
|
||||
# if attitude_hold_enabled:
|
||||
# _perform_manual_hold()
|
||||
|
||||
# --- INPUT SOCKETS (Called by Panels or other Shards) ---
|
||||
|
||||
@ -55,7 +55,7 @@ func set_rotation_input(value: float):
|
||||
# When input stops, re-engage hold at the current rotation.
|
||||
if not attitude_hold_enabled:
|
||||
attitude_hold_enabled = true
|
||||
target_rotation_rad = root_module.rotation
|
||||
target_rotation_rad = root_module.basis
|
||||
|
||||
## This is an "input socket" for translational control (main thrusters).
|
||||
## It takes a value from 0.0 to 1.0.
|
||||
@ -77,21 +77,21 @@ func set_throttle_input(value: float):
|
||||
print(" - Main Engine Shut Off")
|
||||
thruster.turn_off()
|
||||
|
||||
func set_desired_rotation(r: float):
|
||||
target_rotation_rad = r
|
||||
func set_desired_rotation(b: Basis):
|
||||
target_rotation_rad = b
|
||||
|
||||
func set_attitude_hold(hold: bool):
|
||||
attitude_hold_enabled = hold
|
||||
|
||||
# --- LOGIC (Migrated from ThrusterController.gd) ---
|
||||
|
||||
func _perform_manual_hold():
|
||||
var error = shortest_angle_between(root_module.rotation, target_rotation_rad)
|
||||
if abs(error) > 0.001:
|
||||
var desired_torque = (error * HOLD_KP) - (root_module.angular_velocity * HOLD_KD)
|
||||
# func _perform_manual_hold():
|
||||
# var error = shortest_angle_between(root_module.rotation, target_rotation_rad)
|
||||
# if abs(error) > 0.001:
|
||||
# var desired_torque = (error * HOLD_KP) - (root_module.angular_velocity * HOLD_KD)
|
||||
|
||||
apply_rotational_thrust(desired_torque)
|
||||
else: apply_rotational_thrust(0.0)
|
||||
# apply_rotational_thrust(desired_torque)
|
||||
# else: apply_rotational_thrust(0.0)
|
||||
|
||||
# --- REFACTORED: This is the other key change ---
|
||||
func apply_rotational_thrust(desired_torque: float):
|
||||
|
||||
@ -20,18 +20,18 @@ func get_output_sockets() -> Array[String]:
|
||||
func _physics_process(delta):
|
||||
if not is_instance_valid(root_module):
|
||||
return
|
||||
# 1. Gather all the data from the root module.
|
||||
var rotation_deg = rad_to_deg(root_module.rotation)
|
||||
var angular_vel_dps = rad_to_deg(root_module.angular_velocity)
|
||||
var linear_vel_mps = root_module.linear_velocity.length()
|
||||
# # 1. Gather all the data from the root module.
|
||||
# var rotation_deg = rad_to_deg(root_module.rotation)
|
||||
# var angular_vel_dps = rad_to_deg(root_module.angular_velocity)
|
||||
# var linear_vel_mps = root_module.linear_velocity.length()
|
||||
|
||||
# 2. Build the string that will be displayed.
|
||||
var status_text = """
|
||||
[font_size=24]Ship Status[/font_size]
|
||||
[font_size=18]Rotation: %.1f deg[/font_size]
|
||||
[font_size=18]Ang. Vel.: %.2f deg/s[/font_size]
|
||||
[font_size=18]Velocity: %.2f m/s[/font_size]
|
||||
""" % [rotation_deg, angular_vel_dps, linear_vel_mps]
|
||||
# # 2. Build the string that will be displayed.
|
||||
# var status_text = """
|
||||
# [font_size=24]Ship Status[/font_size]
|
||||
# [font_size=18]Rotation: %.1f deg[/font_size]
|
||||
# [font_size=18]Ang. Vel.: %.2f deg/s[/font_size]
|
||||
# [font_size=18]Velocity: %.2f m/s[/font_size]
|
||||
# """ % [rotation_deg, angular_vel_dps, linear_vel_mps]
|
||||
|
||||
# 3. Emit the signal with the formatted text.
|
||||
status_updated.emit(status_text)
|
||||
# # 3. Emit the signal with the formatted text.
|
||||
# status_updated.emit(status_text)
|
||||
|
||||
@ -6,7 +6,7 @@ class_name BrachistochronePlannerShard
|
||||
signal maneuver_calculated(plan: Array[DataTypes.ImpulsiveBurnPlan])
|
||||
|
||||
# --- References ---
|
||||
var target_body: OrbitalBody2D = null
|
||||
var target_body: OrbitalBody3D = null
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
@ -17,7 +17,7 @@ func get_output_sockets() -> Array[String]:
|
||||
return ["maneuver_calculated"]
|
||||
|
||||
# INPUT SOCKET: Connected to the NavSelectionShard's "target_selected" signal.
|
||||
func target_updated(new_target: OrbitalBody2D):
|
||||
func target_updated(new_target: OrbitalBody3D):
|
||||
print("BRACHISTOCHRONE PLANNER: Target received %s." % new_target.name)
|
||||
target_body = new_target
|
||||
|
||||
@ -54,7 +54,7 @@ func calculate_brachistochrone_transfer():
|
||||
accel_burn.wait_time = 0 # Start immediately
|
||||
accel_burn.burn_duration = time_for_half_journey
|
||||
# The desired rotation is the direction vector from ship to target
|
||||
accel_burn.desired_rotation_rad = root_module.global_position.direction_to(target_body.global_position).angle() + (PI / 2.0)
|
||||
accel_burn.desired_rotation_rad = root_module.global_position.direction_to(target_body.global_position) # + (PI / 2.0)
|
||||
plan.append(accel_burn)
|
||||
|
||||
# --- Step 2: Deceleration Burn (The flip is handled by the autopilot between steps) ---
|
||||
|
||||
@ -7,7 +7,7 @@ signal maneuver_calculated(plan: Array[DataTypes.ImpulsiveBurnPlan])
|
||||
|
||||
# --- References ---
|
||||
var selection_shard: NavSelectionShard
|
||||
var target_body: OrbitalBody2D = null
|
||||
var target_body: OrbitalBody3D = null
|
||||
|
||||
# --- Configurations ---
|
||||
var boost_factor: float = 1.0
|
||||
@ -21,7 +21,7 @@ func get_output_sockets() -> Array[String]:
|
||||
return ["maneuver_calculated"]
|
||||
|
||||
# INPUT SOCKET: Connected to the NavSelectionShard's "target_selected" signal.
|
||||
func target_updated(new_target: OrbitalBody2D):
|
||||
func target_updated(new_target: OrbitalBody3D):
|
||||
print("MANEUVER PLANNER: Target recieved %s." % new_target)
|
||||
target_body = new_target
|
||||
|
||||
@ -119,7 +119,7 @@ func calculate_hohmann_transfer():
|
||||
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: OrbitalBody2D, primary: OrbitalBody2D, time: float) -> Dictionary:
|
||||
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))
|
||||
|
||||
@ -16,7 +16,7 @@ func get_output_sockets() -> Array[String]:
|
||||
## Projects the future paths of an array of bodies interacting with each other.
|
||||
## Returns a dictionary mapping each body to its calculated PackedVector2Array path.
|
||||
func project_n_body_paths(
|
||||
bodies_to_trace: Array[OrbitalBody2D],
|
||||
bodies_to_trace: Array[OrbitalBody3D],
|
||||
num_steps: int,
|
||||
time_step: float
|
||||
):
|
||||
|
||||
@ -3,9 +3,9 @@ extends Databank
|
||||
class_name NavSelectionShard
|
||||
|
||||
## Emitted whenever a new navigation target is selected from the map.
|
||||
signal target_selected(body: OrbitalBody2D)
|
||||
signal target_selected(body: OrbitalBody3D)
|
||||
|
||||
var selected_body: OrbitalBody2D = null
|
||||
var selected_body: OrbitalBody3D = null
|
||||
|
||||
## Describes the functions this shard needs as input.
|
||||
func get_input_sockets() -> Array[String]:
|
||||
@ -16,7 +16,7 @@ func get_output_sockets() -> Array[String]:
|
||||
return ["target_selected"]
|
||||
|
||||
# INPUT SOCKET: This function is connected to the SensorPanel's "body_selected" signal.
|
||||
func body_selected(body: OrbitalBody2D):
|
||||
func body_selected(body: OrbitalBody3D):
|
||||
if is_instance_valid(body) and body != selected_body:
|
||||
print("NAV SELECTION: New target acquired - ", body.name)
|
||||
selected_body = body
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
extends Databank
|
||||
class_name SensorSystemShard
|
||||
|
||||
signal sensor_feed_updated(bodies: Array[OrbitalBody2D])
|
||||
signal sensor_feed_updated(bodies: Array[OrbitalBody3D])
|
||||
|
||||
@export_group("Projection Settings")
|
||||
@export var projection_steps: int = 500
|
||||
@ -23,7 +23,7 @@ func _process(_delta: float):
|
||||
return
|
||||
|
||||
# Gather all bodies that need to be included in the simulation.
|
||||
var tracked_bodies: Array[OrbitalBody2D] = []
|
||||
var tracked_bodies: Array[OrbitalBody3D] = []
|
||||
tracked_bodies.append(star_system.get_star())
|
||||
tracked_bodies.append_array(star_system.get_planetary_systems())
|
||||
tracked_bodies.append_array(star_system.get_orbital_bodies())
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# scripts/barycenter.gd
|
||||
class_name Barycenter
|
||||
extends OrbitalBody2D
|
||||
extends OrbitalBody3D
|
||||
|
||||
func _ready():
|
||||
physics_mode = PhysicsMode.INDEPENDENT
|
||||
@ -10,10 +10,10 @@ func _ready():
|
||||
# We only need physics_process to integrate our own movement.
|
||||
set_physics_process(true)
|
||||
|
||||
func get_internal_attractors() -> Array[OrbitalBody2D]:
|
||||
var internal_attractors: Array[OrbitalBody2D] = []
|
||||
func get_internal_attractors() -> Array[OrbitalBody3D]:
|
||||
var internal_attractors: Array[OrbitalBody3D] = []
|
||||
for child in get_children():
|
||||
if child is OrbitalBody2D:
|
||||
if child is OrbitalBody3D:
|
||||
internal_attractors.append(child)
|
||||
|
||||
return internal_attractors
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
extends Node2D
|
||||
class_name OrbitalBody2D
|
||||
# orbital_body_3d.gd
|
||||
# REFACTOR: Extends Node3D instead of Node2D
|
||||
@tool
|
||||
class_name OrbitalBody3D
|
||||
extends Node3D
|
||||
|
||||
# Defines the physical behavior of this body.
|
||||
enum PhysicsMode {
|
||||
@ -10,74 +13,68 @@ enum PhysicsMode {
|
||||
|
||||
@export var physics_mode: PhysicsMode = PhysicsMode.INDEPENDENT
|
||||
|
||||
var current_grid_authority: OrbitalBody2D = null
|
||||
var current_grid_authority: OrbitalBody3D = null
|
||||
|
||||
# Mass of this individual component
|
||||
@export var base_mass: float = 1.0
|
||||
@export var mass: float = 0.0 # Aggregated mass of this body and all its OrbitalBody2D children
|
||||
@export var linear_velocity: Vector2 = Vector2.ZERO
|
||||
@export var angular_velocity: float = 0.0
|
||||
@export var mass: float = 0.0 # Aggregated mass of this body and all its OrbitalBody3D children
|
||||
|
||||
# REFACTOR: All physics properties are now Vector3
|
||||
@export var linear_velocity: Vector3 = Vector3.ZERO
|
||||
@export var angular_velocity: Vector3 = Vector3.ZERO # Represents angular velocity around X, Y, and Z axes
|
||||
|
||||
# Variables to accumulate forces applied during the current physics frame
|
||||
var accumulated_force: Vector2 = Vector2.ZERO
|
||||
var accumulated_torque: float = 0.0
|
||||
var accumulated_force: Vector3 = Vector3.ZERO
|
||||
var accumulated_torque: Vector3 = Vector3.ZERO
|
||||
|
||||
# Placeholder for Moment of Inertia.
|
||||
# REFACTOR: This is a simplification. For true 3D physics, this would be an
|
||||
# inertia tensor (a Basis). But for game physics, a single float
|
||||
# (like your CharacterPawn3D) is much simpler to work with.
|
||||
@export var inertia: float = 1.0
|
||||
|
||||
func _ready():
|
||||
# Ensure mass update runs immediately before the first _physics_process.
|
||||
recalculate_physical_properties()
|
||||
|
||||
set_physics_process(not Engine.is_editor_hint())
|
||||
physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_OFF
|
||||
|
||||
# --- PUBLIC FORCE APPLICATION METHODS ---
|
||||
# This method is called by a component (like Thruster) at its global position.
|
||||
func apply_force(force: Vector2, pos: Vector2 = self.global_position):
|
||||
# REFACTOR: All arguments are now Vector3
|
||||
func apply_force(force: Vector3, pos: Vector3 = self.global_position):
|
||||
# This is the force routing logic.
|
||||
match physics_mode:
|
||||
PhysicsMode.INDEPENDENT:
|
||||
_add_forces(force, pos)
|
||||
PhysicsMode.COMPOSITE:
|
||||
_add_forces(force, pos)
|
||||
## If we are the root, accumulate the force and calculate torque on the total body.
|
||||
#accumulated_force += force
|
||||
#
|
||||
## Calculate torque (2D cross product: T = r x F = r.x * F.y - r.y * F.x)
|
||||
## 'r' is the vector from the center of mass (global_position) to the point of force application (position).
|
||||
#var r = position - global_position
|
||||
#var torque = r.x * force.y - r.y * force.x
|
||||
#accumulated_torque += torque
|
||||
PhysicsMode.ANCHORED:
|
||||
# If we are not the root, we must route the force to the next OrbitalBody2D parent.
|
||||
# If we are not the root, we must route the force to the next OrbitalBody3D parent.
|
||||
var p = get_parent()
|
||||
while p:
|
||||
if p is OrbitalBody2D:
|
||||
if p is OrbitalBody3D:
|
||||
# Recursively call the parent's apply_force method.
|
||||
# This sends the force (and its original global position) up the chain.
|
||||
p.apply_force(force, pos)
|
||||
return # Stop at the first OrbitalBody2D parent
|
||||
return # Stop at the first OrbitalBody3D parent
|
||||
p = p.get_parent()
|
||||
|
||||
push_error("Anchored OrbitalBody2D has become dislodged and is now Composite.")
|
||||
push_error("Anchored OrbitalBody3D has become dislodged and is now Composite.")
|
||||
physics_mode = PhysicsMode.COMPOSITE
|
||||
apply_force(force, position)
|
||||
|
||||
func _add_forces(force: Vector2, pos: Vector2 = Vector2.ZERO):
|
||||
func _add_forces(force: Vector3, pos: Vector3 = Vector3.ZERO):
|
||||
# If we are the root, accumulate the force and calculate torque on the total body.
|
||||
accumulated_force += force
|
||||
|
||||
# Calculate torque (2D cross product: T = r x F = r.x * F.y - r.y * F.x)
|
||||
# 'r' is the vector from the center of mass (global_position) to the point of force application (position).
|
||||
var r = pos - global_position
|
||||
var torque = r.x * force.y - r.y * force.x
|
||||
|
||||
# REFACTOR: Use 3D cross product (r x F) for torque instead of 2D (r.x*F.y - r.y*F.x)
|
||||
var torque = r.cross(force)
|
||||
accumulated_torque += torque
|
||||
|
||||
func _update_mass_and_inertia():
|
||||
mass = base_mass
|
||||
for child in get_children():
|
||||
if child is OrbitalBody2D:
|
||||
if child is OrbitalBody3D:
|
||||
child._update_mass_and_inertia() # Recurse into children
|
||||
mass += child.mass
|
||||
|
||||
@ -85,8 +82,6 @@ func _update_mass_and_inertia():
|
||||
|
||||
func _physics_process(delta):
|
||||
if not Engine.is_editor_hint():
|
||||
# Note: We're not integrating forces for anchored bodies
|
||||
# anchored bodies add forces to their parents and
|
||||
match physics_mode:
|
||||
PhysicsMode.INDEPENDENT:
|
||||
_integrate_forces(delta)
|
||||
@ -97,44 +92,41 @@ func _integrate_forces(delta):
|
||||
# Safety Check for Division by Zero
|
||||
var sim_mass = mass
|
||||
if sim_mass <= 0.0:
|
||||
# If mass is zero, stop all physics to prevent NaN explosion.
|
||||
accumulated_force = Vector2.ZERO
|
||||
accumulated_torque = 0.0
|
||||
accumulated_force = Vector3.ZERO
|
||||
accumulated_torque = Vector3.ZERO
|
||||
return
|
||||
|
||||
## 1. Calculate and accumulate gravitational force (F_g)
|
||||
#var total_gravity_force = OrbitalMechanics.calculate_n_body_gravity_forces(self)
|
||||
#
|
||||
## 2. Total all forces: F_total = F_g + F_accumulated_from_thrusters
|
||||
#var total_force = total_gravity_force +
|
||||
|
||||
# 3. Apply Linear Physics (F = ma)
|
||||
var linear_acceleration = accumulated_force / sim_mass # Division is now safe
|
||||
linear_velocity += linear_acceleration * delta
|
||||
global_position += linear_velocity * delta
|
||||
|
||||
# 4. Apply Rotational Physics (T = I * angular_acceleration)
|
||||
var angular_acceleration = accumulated_torque / inertia
|
||||
angular_velocity += angular_acceleration * delta
|
||||
rotation += angular_velocity * delta
|
||||
|
||||
# 5. Reset accumulated forces for the next frame
|
||||
accumulated_force = Vector2.ZERO
|
||||
accumulated_torque = 0.0
|
||||
# REFACTOR: Use the simplified 3D torque equation from your CharacterPawn3D
|
||||
if inertia > 0:
|
||||
var angular_acceleration = accumulated_torque / inertia
|
||||
angular_velocity += angular_acceleration * delta
|
||||
|
||||
# REFACTOR: Apply 3D rotation using the integrated angular velocity
|
||||
# (This is the same method your CharacterPawn3D uses)
|
||||
if angular_velocity.length_squared() > 0.0001:
|
||||
rotate(angular_velocity.normalized(), angular_velocity.length() * delta)
|
||||
|
||||
# Optional: Add damping
|
||||
# angular_velocity *= (1.0 - 0.1 * delta)
|
||||
|
||||
# 5. Reset accumulated forces for the next frame
|
||||
accumulated_force = Vector3.ZERO
|
||||
accumulated_torque = Vector3.ZERO
|
||||
|
||||
# This is the new, corrected function.
|
||||
func recalculate_physical_properties():
|
||||
# For non-composite bodies, the calculation is simple.
|
||||
if physics_mode != PhysicsMode.COMPOSITE:
|
||||
mass = base_mass
|
||||
# --- THE FIX ---
|
||||
# An independent body doesn't calculate inertia from parts.
|
||||
# We ensure it has a non-zero default value to prevent division by zero.
|
||||
if inertia <= 0.0:
|
||||
inertia = 1.0
|
||||
return
|
||||
|
||||
var all_parts: Array[OrbitalBody2D] = []
|
||||
var all_parts: Array[OrbitalBody3D] = []
|
||||
_collect_anchored_parts(all_parts)
|
||||
|
||||
if all_parts.is_empty():
|
||||
@ -144,39 +136,34 @@ func recalculate_physical_properties():
|
||||
|
||||
# --- Step 1: Calculate Total Mass and LOCAL Center of Mass ---
|
||||
var total_mass = 0.0
|
||||
var weighted_local_pos_sum = Vector2.ZERO
|
||||
# REFACTOR: Use Vector3
|
||||
var weighted_local_pos_sum = Vector3.ZERO
|
||||
for part in all_parts:
|
||||
total_mass += part.base_mass
|
||||
# We get the part's position *relative to the root module*
|
||||
var local_pos = part.global_position - self.global_position
|
||||
weighted_local_pos_sum += local_pos * part.base_mass
|
||||
|
||||
var local_center_of_mass = Vector2.ZERO
|
||||
var local_center_of_mass = Vector3.ZERO
|
||||
if total_mass > 0:
|
||||
local_center_of_mass = weighted_local_pos_sum / total_mass
|
||||
|
||||
# --- Step 2: Calculate Total Moment of Inertia around the LOCAL CoM ---
|
||||
var total_inertia = 0.0
|
||||
for part in all_parts:
|
||||
# Get the part's position relative to the root module again
|
||||
var local_pos = part.global_position - self.global_position
|
||||
# The radius is the distance from the part's local position to the ship's local center of mass
|
||||
# REFACTOR: This logic (Parallel Axis Theorem) is still correct for Vector3
|
||||
var r_squared = (local_pos - local_center_of_mass).length_squared()
|
||||
total_inertia += part.base_mass * r_squared
|
||||
|
||||
# --- Step 3: Assign the final values ---
|
||||
self.mass = total_mass
|
||||
# We apply a scaling factor here because our "units" are pixels.
|
||||
# This brings the final value into a range that feels good for gameplay.
|
||||
# You can tune this factor to make ships feel heavier or lighter.
|
||||
self.inertia = total_inertia * 0.01
|
||||
|
||||
#print("Physics Recalculated: Mass=%.2f kg, Inertia=%.2f" % [mass, inertia])
|
||||
if self.inertia <= 0.0: # Safety check
|
||||
self.inertia = 1.0
|
||||
|
||||
# A recursive helper function to get an array of all OrbitalBody2D children
|
||||
# A recursive helper function to get an array of all OrbitalBody3D children
|
||||
func _collect_anchored_parts(parts_array: Array):
|
||||
parts_array.append(self)
|
||||
for child in get_children():
|
||||
# TODO: this assumes that all OrbitalBody2D that are attached are done in a clean chain without breaks, which may not be the case
|
||||
if child is OrbitalBody2D and child.physics_mode == PhysicsMode.ANCHORED:
|
||||
child._collect_anchored_parts(parts_array)
|
||||
if child is OrbitalBody3D and child.physics_mode == PhysicsMode.ANCHORED:
|
||||
child._collect_anchored_parts(parts_array)
|
||||
@ -1 +1 @@
|
||||
uid://0isnsk356que
|
||||
uid://wlm40n8ywr
|
||||
|
||||
@ -98,7 +98,7 @@ func register_star_system(system_node):
|
||||
current_star_system = system_node
|
||||
print("GameManager: Star system registered.")
|
||||
|
||||
func register_ship(ship: OrbitalBody2D):
|
||||
func register_ship(ship: OrbitalBody3D):
|
||||
if not is_instance_valid(current_star_system):
|
||||
return
|
||||
|
||||
@ -119,8 +119,8 @@ func get_system_data() -> SystemData:
|
||||
return current_star_system.get_system_data()
|
||||
return null
|
||||
|
||||
func get_all_trackable_bodies() -> Array[OrbitalBody2D]:
|
||||
var all_bodies: Array[OrbitalBody2D] = []
|
||||
func get_all_trackable_bodies() -> Array[OrbitalBody3D]:
|
||||
var all_bodies: Array[OrbitalBody3D] = []
|
||||
if current_star_system:
|
||||
# First, get all the celestial bodies (planets, moons, etc.)
|
||||
var system_data = current_star_system.get_system_data()
|
||||
|
||||
@ -5,19 +5,10 @@ extends Node
|
||||
|
||||
# The scaled gravitational constant for the entire simulation.
|
||||
const G = 1.0 # Adjust this to control the "speed" of your simulation
|
||||
|
||||
const MIN_INFLUENCE_THRESHOLD = 0.00001
|
||||
const ROCHE_LIMIT_MASS_MULTIPLIER = 0.5
|
||||
|
||||
class BodyTuple:
|
||||
var body_a: OrbitalBody2D
|
||||
var body_b: OrbitalBody2D
|
||||
|
||||
var cached_forces: Dictionary[BodyTuple, Vector2] = {
|
||||
|
||||
}
|
||||
|
||||
# --- Centralized Physics Process ---
|
||||
func _physics_process(_delta: float) -> void:
|
||||
var star_system: StarSystem = GameManager.current_star_system
|
||||
if not star_system:
|
||||
@ -26,93 +17,72 @@ func _physics_process(_delta: float) -> void:
|
||||
var star = star_system.get_star()
|
||||
var planetary_systems = star_system.get_planetary_systems()
|
||||
|
||||
# TODO: Would this be true in case we are working with a system that is just a rouge planet or a brown dwarf?
|
||||
if not star:
|
||||
return
|
||||
|
||||
# 1: Calculate star system pull
|
||||
# a: Get the star and top level Barycenters
|
||||
var top_level_bodies: Array[OrbitalBody2D] = [star]
|
||||
var top_level_bodies: Array[OrbitalBody3D] = [star]
|
||||
top_level_bodies.append_array(planetary_systems)
|
||||
# b: calculate and apply pull between these
|
||||
apply_n_body_forces(top_level_bodies)
|
||||
|
||||
# 2: Calculate Barycenters local pull
|
||||
for system in planetary_systems:
|
||||
# a: Get each Planetary Barycenters OrbitalBody2Ds (including Ships, Satelites, and Stations fully within the Barycenter)
|
||||
var system_attractors = system.get_internal_attractors()
|
||||
# b: Calculate and apply pull within each Barycenter
|
||||
apply_n_body_forces(system_attractors)
|
||||
|
||||
# 3: Calculate top level Ships, Satelites, and Stations pull
|
||||
# a: Get top level OrbitalBody2Ds of non-celestial classes
|
||||
for star_orbiter in star_system.get_orbital_bodies():
|
||||
# b: Split into Star Orbiting and On-Approach using mass/distance ratios to Barycenters
|
||||
# TODO: Check for distance to Barycenter
|
||||
# c: For Star Orbiting objects -> Calculate and apply pull to star and Barycenter
|
||||
star_orbiter.apply_force(calculate_n_body_force(star_orbiter, top_level_bodies))
|
||||
# d: For On Approach -> Calculate and apply pull to star and distant Barycenters
|
||||
# as well as individual bodies within approaching Barycenter
|
||||
|
||||
func calculate_gravitational_force(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> Vector2:
|
||||
func calculate_gravitational_force(orbiter: OrbitalBody3D, primary: OrbitalBody3D) -> Vector3:
|
||||
if not is_instance_valid(orbiter) or not is_instance_valid(primary):
|
||||
return Vector2.ZERO
|
||||
# REFACTOR: Return Vector3.ZERO
|
||||
return Vector3.ZERO
|
||||
|
||||
var distance_sq = orbiter.global_position.distance_squared_to(primary.global_position)
|
||||
|
||||
if distance_sq < 1.0:
|
||||
return Vector2.ZERO
|
||||
return Vector3.ZERO
|
||||
|
||||
# --- Influence Pruning (Culling) ---
|
||||
# We check both directions of influence
|
||||
var influence_a = primary.mass / distance_sq
|
||||
var influence_b = orbiter.mass / distance_sq
|
||||
|
||||
if influence_a < MIN_INFLUENCE_THRESHOLD and influence_b < MIN_INFLUENCE_THRESHOLD:
|
||||
return Vector2.ZERO
|
||||
return Vector3.ZERO
|
||||
|
||||
var force_magnitude = (G * primary.mass * orbiter.mass) / distance_sq
|
||||
# REFACTOR: direction_to is now 3D, this logic is fine
|
||||
var direction = orbiter.global_position.direction_to(primary.global_position)
|
||||
return direction * force_magnitude
|
||||
|
||||
# Calculates the pull between a set number of bodies
|
||||
# Use carefully to simulate each level of the simulation
|
||||
# Iterate through every unique pair of bodies (i, j) where j > i
|
||||
func apply_n_body_forces(attractors: Array[OrbitalBody2D]):
|
||||
# Iterate through every unique pair of bodies (i, j) where j > i
|
||||
func apply_n_body_forces(attractors: Array[OrbitalBody3D]):
|
||||
for i in range(attractors.size()):
|
||||
var body_a: OrbitalBody2D = attractors[i]
|
||||
var body_a: OrbitalBody3D = attractors[i]
|
||||
if not is_instance_valid(body_a): continue
|
||||
|
||||
for j in range(i + 1, attractors.size()):
|
||||
var body_b: OrbitalBody2D = attractors[j]
|
||||
var body_b: OrbitalBody3D = attractors[j]
|
||||
if not is_instance_valid(body_b): continue
|
||||
|
||||
# Calculate the force vector ONCE
|
||||
# REFACTOR: force_vector is now Vector3
|
||||
var force_vector = calculate_gravitational_force(body_a, body_b)
|
||||
|
||||
# Apply the force symmetrically
|
||||
if force_vector != Vector2.ZERO:
|
||||
if force_vector != Vector3.ZERO:
|
||||
body_a.apply_force(force_vector)
|
||||
body_b.apply_force(-force_vector)
|
||||
|
||||
func calculate_n_body_force(body: OrbitalBody2D, attractors: Array[OrbitalBody2D]) -> Vector2:
|
||||
var total_pull: Vector2 = Vector2.ZERO
|
||||
func calculate_n_body_force(body: OrbitalBody3D, attractors: Array[OrbitalBody3D]) -> Vector3:
|
||||
var total_pull: Vector3 = Vector3.ZERO
|
||||
for attractor in attractors:
|
||||
total_pull += calculate_gravitational_force(body, attractor)
|
||||
return total_pull
|
||||
|
||||
func calculate_n_body_gravity_forces(body_to_affect: Node2D) -> Vector2:
|
||||
var total_force = Vector2.ZERO
|
||||
func calculate_n_body_gravity_forces(body_to_affect: Node3D) -> Vector3:
|
||||
var total_force = Vector3.ZERO
|
||||
if not is_instance_valid(body_to_affect):
|
||||
return total_force
|
||||
|
||||
# Get the list of all major gravitational bodies from the GameManager.
|
||||
var system_data = GameManager.get_system_data()
|
||||
if not system_data:
|
||||
return total_force
|
||||
|
||||
# We only consider planets and the star as major attractors for performance.
|
||||
var attractors = system_data.all_bodies()
|
||||
|
||||
for attractor in attractors:
|
||||
@ -121,39 +91,43 @@ func calculate_n_body_gravity_forces(body_to_affect: Node2D) -> Vector2:
|
||||
|
||||
return total_force
|
||||
|
||||
# Calculates the perfect initial velocity for a stable circular orbit.
|
||||
func calculate_circular_orbit_velocity(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> Vector2:
|
||||
func calculate_circular_orbit_velocity(orbiter: OrbitalBody3D, primary: OrbitalBody3D) -> Vector3:
|
||||
if not is_instance_valid(primary):
|
||||
return Vector2.ZERO
|
||||
return Vector3.ZERO
|
||||
|
||||
var distance = orbiter.global_position.distance_to(primary.global_position)
|
||||
if distance == 0:
|
||||
return Vector2.ZERO
|
||||
return Vector3.ZERO
|
||||
|
||||
# v = sqrt(G * M / r)
|
||||
var speed_magnitude = sqrt(G * primary.mass / distance)
|
||||
|
||||
var direction_to_orbiter = primary.global_position.direction_to(orbiter.global_position)
|
||||
var perpendicular_direction = Vector2(direction_to_orbiter.y, -direction_to_orbiter.x)
|
||||
|
||||
# REFACTOR: This is the biggest 2D -> 3D logic change.
|
||||
# We can't just get a simple perpendicular vector. We need to define
|
||||
# an orbital plane. We'll assume a "flat" system on the XZ plane,
|
||||
# so the "up" vector is Vector3.UP.
|
||||
# We find the perpendicular by crossing "up" with the direction to the orbiter.
|
||||
var perpendicular_direction = Vector3.UP.cross(direction_to_orbiter).normalized()
|
||||
|
||||
return perpendicular_direction * speed_magnitude
|
||||
|
||||
func _calculate_n_body_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVector2Array:
|
||||
# REFACTOR: Returns PackedVector3Array
|
||||
func _calculate_n_body_orbital_path(body_to_trace: OrbitalBody3D) -> PackedVector3Array:
|
||||
var num_steps = 10
|
||||
var time_step = 60
|
||||
|
||||
var ghost_position = body_to_trace.global_position
|
||||
var ghost_velocity = body_to_trace.linear_velocity
|
||||
|
||||
var path_points = PackedVector2Array()
|
||||
var path_points = PackedVector3Array()
|
||||
|
||||
for i in range(num_steps):
|
||||
# Create a temporary "ghost" body to calculate forces on.
|
||||
var ghost_body = OrbitalBody2D.new()
|
||||
var ghost_body = OrbitalBody3D.new()
|
||||
ghost_body.global_position = ghost_position
|
||||
ghost_body.mass = body_to_trace.mass
|
||||
|
||||
# Use our library to get the total gravitational force at the ghost's position.
|
||||
var total_force = calculate_n_body_gravity_forces(ghost_body)
|
||||
var acceleration = total_force / ghost_body.mass
|
||||
|
||||
@ -161,14 +135,14 @@ func _calculate_n_body_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVecto
|
||||
ghost_position += ghost_velocity * time_step
|
||||
path_points.append(ghost_position)
|
||||
|
||||
ghost_body.free() # Clean up the temporary node
|
||||
ghost_body.free()
|
||||
|
||||
return path_points
|
||||
|
||||
# Calculates an array of points for the orbit RELATIVE to the primary body.
|
||||
func _calculate_relative_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVector2Array:
|
||||
# REFACTOR: Returns PackedVector3Array
|
||||
func _calculate_relative_orbital_path(body_to_trace: OrbitalBody3D) -> PackedVector3Array:
|
||||
if not is_instance_valid(body_to_trace) or not body_to_trace.has_method("get_primary") or not is_instance_valid(body_to_trace.get_primary()):
|
||||
return PackedVector2Array()
|
||||
return PackedVector3Array()
|
||||
|
||||
var primary = body_to_trace.get_primary()
|
||||
var primary_mass = primary.mass
|
||||
@ -179,11 +153,10 @@ func _calculate_relative_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVec
|
||||
|
||||
var r_magnitude = ghost_relative_pos.length()
|
||||
if r_magnitude == 0:
|
||||
return PackedVector2Array()
|
||||
return PackedVector3Array()
|
||||
|
||||
var v_sq = ghost_relative_vel.length_squared()
|
||||
var mu = G * primary_mass
|
||||
|
||||
var specific_energy = v_sq / 2.0 - mu / r_magnitude
|
||||
|
||||
var num_steps = 200
|
||||
@ -195,13 +168,12 @@ func _calculate_relative_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVec
|
||||
var orbital_period = 2.0 * PI * sqrt(pow(semi_major_axis, 3) / mu)
|
||||
time_step = orbital_period / float(num_steps)
|
||||
|
||||
var path_points = PackedVector2Array()
|
||||
var path_points = PackedVector3Array()
|
||||
|
||||
for i in range(num_steps):
|
||||
var distance_sq = ghost_relative_pos.length_squared()
|
||||
if distance_sq < 1.0:
|
||||
break
|
||||
|
||||
var direction = -ghost_relative_pos.normalized()
|
||||
var force_magnitude = (G * primary_mass * body_mass) / distance_sq
|
||||
var force_vector = direction * force_magnitude
|
||||
@ -213,29 +185,22 @@ func _calculate_relative_orbital_path(body_to_trace: OrbitalBody2D) -> PackedVec
|
||||
|
||||
return path_points
|
||||
|
||||
# Calculates the Hill Sphere radius for a satellite.
|
||||
# This is the region where the satellite's gravity is dominant over its primary's.
|
||||
func calculate_hill_sphere(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> float:
|
||||
# --- These functions are scalar and need no changes ---
|
||||
|
||||
func calculate_hill_sphere(orbiter: OrbitalBody3D, primary: OrbitalBody3D) -> float:
|
||||
if not is_instance_valid(orbiter) or not is_instance_valid(primary) or primary.mass <= 0:
|
||||
return 0.0
|
||||
|
||||
var distance = orbiter.global_position.distance_to(primary.global_position)
|
||||
# The formula is: a * (m / 3M)^(1/3)
|
||||
var mass_ratio = orbiter.mass / (3.0 * primary.mass)
|
||||
if mass_ratio < 0: return 0.0
|
||||
|
||||
return distance * pow(mass_ratio, 1.0/3.0)
|
||||
|
||||
# Calculates a simplified Roche Limit, or minimum safe orbital distance.
|
||||
func calculate_simplified_roche_limit(primary: OrbitalBody2D) -> float:
|
||||
func calculate_simplified_roche_limit(primary: OrbitalBody3D) -> float:
|
||||
if not is_instance_valid(primary) or primary.mass <= 0:
|
||||
return 100.0 # Return a small default if primary is invalid
|
||||
|
||||
# We approximate a "radius" from the square root of the mass, then apply a multiplier.
|
||||
# This ensures more massive stars and planets have larger "keep-out" zones.
|
||||
return 100.0
|
||||
return sqrt(primary.mass) * ROCHE_LIMIT_MASS_MULTIPLIER
|
||||
|
||||
func get_orbital_time_in_seconds(orbiter: OrbitalBody2D, primary: OrbitalBody2D) -> float:
|
||||
func get_orbital_time_in_seconds(orbiter: OrbitalBody3D, primary: OrbitalBody3D) -> float:
|
||||
var mu = OrbitalMechanics.G * primary.mass
|
||||
var r = orbiter.global_position.distance_to(primary.global_position)
|
||||
return TAU * sqrt(pow(r, 3) / mu)
|
||||
|
||||
@ -20,7 +20,7 @@ func _ready():
|
||||
GameManager.start_game()
|
||||
|
||||
# --- Public API for accessing system data ---
|
||||
func get_star() -> OrbitalBody2D:
|
||||
func get_star() -> OrbitalBody3D:
|
||||
if is_instance_valid(system_data):
|
||||
return system_data.star
|
||||
return null
|
||||
@ -33,12 +33,12 @@ func get_planetary_systems() -> Array[Barycenter]:
|
||||
|
||||
return bodies
|
||||
|
||||
func get_orbital_bodies() -> Array[OrbitalBody2D]:
|
||||
var bodies: Array[OrbitalBody2D] = []
|
||||
func get_orbital_bodies() -> Array[OrbitalBody3D]:
|
||||
var bodies: Array[OrbitalBody3D] = []
|
||||
for child in get_children():
|
||||
if child is Star or child is Barycenter:
|
||||
continue
|
||||
if child is OrbitalBody2D:
|
||||
if child is OrbitalBody3D:
|
||||
bodies.append(child)
|
||||
|
||||
return bodies
|
||||
@ -50,4 +50,4 @@ class AsteroidBelt:
|
||||
var width : float
|
||||
var mass : float
|
||||
var centered_radius : float = 0.0
|
||||
var asteroids : Array[OrbitalBody2D]
|
||||
var asteroids : Array[OrbitalBody3D]
|
||||
|
||||
@ -2,72 +2,65 @@
|
||||
class_name StarSystemGenerator
|
||||
extends RefCounted
|
||||
|
||||
# --- Stable Mass Ratios & Generation Rules ---
|
||||
# --- (Constants are fine) ---
|
||||
const STAR_MASS = 50000000.0
|
||||
const PLANET_MASS = STAR_MASS / 10000.0 # Planet is 10,000x less massive than the star.
|
||||
const MOON_MASS = PLANET_MASS / 1000.0 # Moon is 1,000x less massive than its planet.
|
||||
const PLANET_MASS = STAR_MASS / 10000.0
|
||||
const MOON_MASS = PLANET_MASS / 1000.0
|
||||
const MIN_PLANETS = 3
|
||||
const MAX_PLANETS = 8
|
||||
const MAX_MOONS_PER_PLANET = 5
|
||||
const ORBIT_SAFETY_FACTOR = 5 # Increase space between orbits
|
||||
const ORBIT_SAFETY_FACTOR = 5
|
||||
|
||||
# --- The main public method ---
|
||||
func generate(star_system: StarSystem) -> SystemData:
|
||||
# 1. Create the root Barycenter for the entire system.
|
||||
var system_data = SystemData.new()
|
||||
|
||||
# 2. Create the star itself inside the root Barycenter.
|
||||
var star = Star.new()
|
||||
system_data.star = star
|
||||
star.name = "Star"
|
||||
star.base_mass = STAR_MASS
|
||||
star_system.add_child(star)
|
||||
|
||||
# 3. Procedurally generate and place the planetary systems.
|
||||
var num_planets = randi_range(MIN_PLANETS, MAX_PLANETS)
|
||||
var current_orbit_radius = 15000.0 # OrbitalMechanics.calculate_simplified_roche_limit(star) # Start with the first orbit
|
||||
var current_orbit_radius = 15000.0
|
||||
|
||||
for i in range(num_planets):
|
||||
# A. Create the Barycenter for the new planetary system.
|
||||
var planet_barycenter = Barycenter.new()
|
||||
|
||||
planet_barycenter.name = "PlanetSystem_%d" % (i + 1)
|
||||
star_system.add_child(planet_barycenter)
|
||||
|
||||
# B. Create the planet itself inside its Barycenter.
|
||||
var planet = Planet.new()
|
||||
system_data.planets.append(planet)
|
||||
planet.name = "Planet_%d" % (i + 1)
|
||||
planet.base_mass = randf_range(PLANET_MASS * 0.2, PLANET_MASS * 5.0)
|
||||
planet_barycenter.add_child(planet)
|
||||
planet.owner = planet_barycenter
|
||||
planet.position = Vector2.ZERO
|
||||
# REFACTOR: Set 3D position
|
||||
planet.position = Vector3.ZERO
|
||||
planet_barycenter.recalculate_total_mass()
|
||||
# C. Create moons for this planet.
|
||||
|
||||
_generate_moons(planet, planet_barycenter, system_data)
|
||||
|
||||
# D. Place the entire planetary system in a stable orbit.
|
||||
planet_barycenter.global_position = Vector2(current_orbit_radius, 0).rotated(randf_range(0, TAU))
|
||||
# REFACTOR: Place the planet in 3D space (on the XZ plane)
|
||||
# We rotate around the Y-axis (Vector3.UP)
|
||||
planet_barycenter.global_position = Vector3(current_orbit_radius, 0, 0).rotated(Vector3.UP, randf_range(0, TAU))
|
||||
|
||||
# REFACTOR: This now receives a Vector3 velocity
|
||||
planet_barycenter.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(planet_barycenter, star)
|
||||
|
||||
# Update the new edge of the star's influence
|
||||
# 1. Calculate the Hill Sphere (gravitational influence) for the planet we just placed.
|
||||
var hill_sphere = OrbitalMechanics.calculate_hill_sphere(planet_barycenter, star)
|
||||
# 2. Add this zone of influence (plus a safety margin) to the current radius
|
||||
# to determine the starting point for the NEXT planet. This ensures orbits never overlap.
|
||||
current_orbit_radius += hill_sphere * ORBIT_SAFETY_FACTOR
|
||||
|
||||
# --- Spawn the ship at the last planet's L4 point ---
|
||||
if i == num_planets - 1:
|
||||
_spawn_player_ship(star_system, star, planet_barycenter)
|
||||
|
||||
return system_data
|
||||
|
||||
func _generate_moons(planet: OrbitalBody2D, planet_barycenter: Barycenter, system_data: SystemData):
|
||||
var num_moons = randi_range(0, int(planet.mass / MOON_MASS / 2.0)) # Heavier planets get more moons
|
||||
# REFACTOR: Takes OrbitalBody3D
|
||||
func _generate_moons(planet: OrbitalBody3D, planet_barycenter: Barycenter, system_data: SystemData):
|
||||
var num_moons = randi_range(0, int(planet.mass / MOON_MASS / 2.0))
|
||||
num_moons = min(num_moons, MAX_MOONS_PER_PLANET)
|
||||
|
||||
var current_orbit_radius = 200.0 # OrbitalMechanics.calculate_simplified_roche_limit(planet) # Start with the first orbit
|
||||
var current_orbit_radius = 200.0
|
||||
|
||||
for i in range(num_moons):
|
||||
var moon = Moon.new()
|
||||
@ -78,38 +71,32 @@ func _generate_moons(planet: OrbitalBody2D, planet_barycenter: Barycenter, syste
|
||||
moon.name = "Moon_%d" % (i + 1)
|
||||
moon.base_mass = randf_range(MOON_MASS * 0.1, MOON_MASS * 2.0)
|
||||
|
||||
moon.position = Vector2(current_orbit_radius, 0).rotated(randf_range(0, TAU))
|
||||
# Velocity is calculated relative to the parent (the planet)
|
||||
# REFACTOR: Position in 3D, rotated around Y-axis
|
||||
moon.position = Vector3(current_orbit_radius, 0, 0).rotated(Vector3.UP, randf_range(0, TAU))
|
||||
moon.linear_velocity = OrbitalMechanics.calculate_circular_orbit_velocity(moon, planet_barycenter)
|
||||
|
||||
# Update the new edge of the planets's influence
|
||||
# 1. Calculate the Hill Sphere (gravitational influence) for the moon we just placed.
|
||||
var hill_sphere = OrbitalMechanics.calculate_hill_sphere(moon, planet_barycenter)
|
||||
# 2. Add this zone of influence (plus a safety margin) to the current radius
|
||||
# to determine the starting point for the NEXT planet. This ensures orbits never overlap.
|
||||
current_orbit_radius += hill_sphere * ORBIT_SAFETY_FACTOR
|
||||
|
||||
|
||||
# --- NEW FUNCTION: Spawns the player ship at a Lagrange point ---
|
||||
func _spawn_player_ship(star_system: StarSystem, star: OrbitalBody2D, planet_system: Barycenter):
|
||||
# L4 and L5 Lagrange points form an equilateral triangle with the star and planet.
|
||||
# We'll calculate L4 by rotating the star-planet vector by +60 degrees.
|
||||
# REFACTOR: Takes OrbitalBody3D
|
||||
func _spawn_player_ship(star_system: StarSystem, star: OrbitalBody3D, planet_system: Barycenter):
|
||||
# REFACTOR: Calculate L4/L5 in 3D
|
||||
var star_to_planet_vec = planet_system.global_position - star.global_position
|
||||
var l4_position = star.global_position + star_to_planet_vec.rotated(PI / 3.0)
|
||||
# Rotate around the Y-axis
|
||||
var l4_position = star.global_position + star_to_planet_vec.rotated(Vector3.UP, PI / 3.0)
|
||||
var l4_velocity = planet_system.linear_velocity.rotated(Vector3.UP, PI / 3.0)
|
||||
|
||||
# The ship's velocity at L4 must match the orbital characteristics of that point.
|
||||
# This is an approximation where we rotate the planet's velocity vector by 60 degrees.
|
||||
var l4_velocity = planet_system.linear_velocity.rotated(PI / 3.0)
|
||||
|
||||
# Instantiate, position, and configure the ship.
|
||||
var ship_instance = GameManager.config.default_ship_scene.instantiate()
|
||||
GameManager.register_ship(ship_instance)
|
||||
ship_instance.name = "PlayerShip"
|
||||
star_system.add_child(ship_instance) # Add ship to the root StarSystem node
|
||||
star_system.add_child(ship_instance)
|
||||
ship_instance.global_position = l4_position
|
||||
ship_instance.linear_velocity = l4_velocity
|
||||
ship_instance.rotation = l4_velocity.angle() + (PI / 2.0) # Point prograde
|
||||
|
||||
# Make sure the new ship is included in the physics simulation
|
||||
#_system_data_dict.all_bodies.append(ship_instance)
|
||||
print("Player ship spawned at L4 point of %s" % planet_system.name)
|
||||
# REFACTOR: Use look_at to orient the ship in 3D
|
||||
# We point it "prograde" (in the direction of its velocity)
|
||||
# We use Vector3.UP as the "up" reference
|
||||
ship_instance.look_at(ship_instance.global_position + l4_velocity.normalized(), Vector3.UP)
|
||||
|
||||
print("Player ship spawned at L4 point of %s" % planet_system.name)
|
||||
@ -1,23 +1,23 @@
|
||||
class_name SystemData
|
||||
extends Resource
|
||||
|
||||
var star : OrbitalBody2D
|
||||
var ships : Array[OrbitalBody2D]
|
||||
var planets : Array[OrbitalBody2D]
|
||||
var moons: Array[OrbitalBody2D]
|
||||
#var stations : Array[OrbitalBody2D]
|
||||
var star : OrbitalBody3D
|
||||
var ships : Array[OrbitalBody3D]
|
||||
var planets : Array[OrbitalBody3D]
|
||||
var moons: Array[OrbitalBody3D]
|
||||
#var stations : Array[OrbitalBody3D]
|
||||
#var belts : Array[AsteroidBelt]
|
||||
|
||||
func all_bodies() -> Array[OrbitalBody2D]:
|
||||
func all_bodies() -> Array[OrbitalBody3D]:
|
||||
|
||||
var bodies : Array[OrbitalBody2D] = [star]
|
||||
var bodies : Array[OrbitalBody3D] = [star]
|
||||
bodies.append_array(planets)
|
||||
#bodies.append_array(stations)
|
||||
|
||||
bodies.append_array(moons)
|
||||
bodies.append_array(ships)
|
||||
|
||||
#var all_asteroids : Array[OrbitalBody2D] = []
|
||||
#var all_asteroids : Array[OrbitalBody3D] = []
|
||||
#for belt in belts:
|
||||
#all_asteroids.append_array(belt.asteroids)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user