WIP Gravitational refactor
This commit is contained in:
@ -2,6 +2,9 @@ class_name CelestialBody extends OrbitalBody3D
|
||||
|
||||
@export var radius: float = 100.0
|
||||
|
||||
func _ready():
|
||||
auto_proxy_gravity = false
|
||||
|
||||
func set_radius(value: float):
|
||||
radius = value
|
||||
|
||||
|
||||
@ -65,47 +65,11 @@ func turn_off():
|
||||
await get_tree().physics_frame
|
||||
#print(" - firing: %s" % is_firing)
|
||||
|
||||
# --- Godot Physics Callback ---
|
||||
func _physics_process(delta: float):
|
||||
super(delta)
|
||||
if not enabled:
|
||||
is_firing = false
|
||||
|
||||
|
||||
func _integrate_forces(state: PhysicsDirectBodyState3D):
|
||||
# Call the base class integration
|
||||
super(state)
|
||||
|
||||
# If the thruster is active, apply a constant central force in its local "up" direction.
|
||||
if is_firing:
|
||||
apply_thrust_force()
|
||||
|
||||
# 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 = 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 * local_force
|
||||
|
||||
# 3. Apply the force to itself.
|
||||
apply_force_recursive(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
|
||||
|
||||
# # --- 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
|
||||
|
||||
# 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]))
|
||||
apply_central_force(Vector3.UP * max_thrust)
|
||||
|
||||
@ -5,8 +5,8 @@ class_name OrbitalBody3D extends RigidBody3D
|
||||
# Defines the physical behavior of this body.
|
||||
enum PhysicsMode {
|
||||
INDEPENDENT, # An independent body with its own physics simulation (planets, characters in EVA).
|
||||
COMPOSITE, # A body that aggregates mass and forces from ANCHORED children (ships, modules).
|
||||
ANCHORED # A component that is "bolted on" and defers physics to a COMPOSITE parent.
|
||||
COMPOSITE, # A body that aggregates mass and forces from ANCHORED children (ships, modules).
|
||||
ANCHORED # A component that is "bolted on" and defers physics to a COMPOSITE parent.
|
||||
}
|
||||
|
||||
@export var physics_mode: PhysicsMode = PhysicsMode.INDEPENDENT
|
||||
@ -14,23 +14,25 @@ enum PhysicsMode {
|
||||
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 OrbitalBody3D children
|
||||
@export var base_mass: float = 1.0
|
||||
|
||||
# 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
|
||||
# --- Gravity Proxy System ---
|
||||
# If this is set, we stop calculating our own gravity and just copy this parent.
|
||||
var gravity_proxy_parent: OrbitalBody3D = null
|
||||
# If true, this body will automatically attach to a parent OrbitalBody3D
|
||||
# as a gravity proxy. Set this to FALSE for planets/moons that need
|
||||
# to calculate their own independent orbits.
|
||||
@export var auto_proxy_gravity: bool = true
|
||||
|
||||
# We cache this frame's acceleration so our children can read it.
|
||||
# This allows us to chain proxies (Pawn -> Ship -> Station).
|
||||
var current_gravitational_acceleration: Vector3 = Vector3.ZERO
|
||||
|
||||
# Variables to accumulate forces applied during the current physics frame
|
||||
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():
|
||||
freeze_mode = FreezeMode.FREEZE_MODE_KINEMATIC
|
||||
if physics_mode == PhysicsMode.ANCHORED:
|
||||
@ -41,6 +43,35 @@ func _ready():
|
||||
recalculate_physical_properties()
|
||||
set_physics_process(not Engine.is_editor_hint())
|
||||
|
||||
func _notification(what):
|
||||
# Automatically update gravity proxy when the scene hierarchy changes
|
||||
if what == NOTIFICATION_PARENTED:
|
||||
_update_gravity_proxy()
|
||||
|
||||
func _update_gravity_proxy():
|
||||
if not auto_proxy_gravity:
|
||||
gravity_proxy_parent = null
|
||||
return
|
||||
|
||||
# 1. Search up the tree for an OrbitalBody3D ancestor
|
||||
var candidate = get_parent()
|
||||
var new_proxy = null
|
||||
|
||||
while candidate:
|
||||
if candidate is OrbitalBody3D:
|
||||
# Found a valid parent physics body
|
||||
new_proxy = candidate
|
||||
break
|
||||
candidate = candidate.get_parent()
|
||||
|
||||
# 2. Assign the proxy
|
||||
if new_proxy != gravity_proxy_parent:
|
||||
gravity_proxy_parent = new_proxy
|
||||
if new_proxy:
|
||||
print(name, " auto-parented gravity proxy to: ", new_proxy.name)
|
||||
else:
|
||||
print(name, " detached from gravity proxy (Independent Mode).")
|
||||
|
||||
# --- PUBLIC FORCE APPLICATION METHODS ---
|
||||
# REFACTOR: All arguments are now Vector3
|
||||
func apply_force_recursive(force: Vector3, pos: Vector3 = self.global_position):
|
||||
@ -84,14 +115,6 @@ func _update_mass_and_inertia():
|
||||
|
||||
print("Node: %s, Mass: %f" % [self, mass])
|
||||
|
||||
func _physics_process(delta):
|
||||
pass
|
||||
# if not Engine.is_editor_hint():
|
||||
# match physics_mode:
|
||||
# PhysicsMode.INDEPENDENT:
|
||||
# _integrate_forces(delta)
|
||||
# PhysicsMode.COMPOSITE:
|
||||
# _integrate_forces(delta)
|
||||
|
||||
func _integrate_forces(state: PhysicsDirectBodyState3D):
|
||||
# Safety Check for Division by Zero
|
||||
@ -99,18 +122,18 @@ func _integrate_forces(state: PhysicsDirectBodyState3D):
|
||||
accumulated_force = Vector3.ZERO
|
||||
accumulated_torque = Vector3.ZERO
|
||||
return
|
||||
|
||||
# --- 1. DETERMINE GRAVITY ---
|
||||
if is_instance_valid(gravity_proxy_parent):
|
||||
# OPTIMIZED PATH: Copy the parent's acceleration (Gravity Proxy)
|
||||
current_gravitational_acceleration = gravity_proxy_parent.current_gravitational_acceleration
|
||||
else:
|
||||
# FULL PATH: Retrieve the pre-calculated N-Body acceleration from the cache
|
||||
current_gravitational_acceleration = OrbitalMechanics.get_calculated_force(self) / mass
|
||||
|
||||
# 3. Apply Linear Physics (F = ma)
|
||||
# var linear_acceleration = accumulated_force / mass # Division is now safe
|
||||
state.apply_central_force(accumulated_force)
|
||||
|
||||
# 4. Apply Rotational Physics (T = I * angular_acceleration)
|
||||
# REFACTOR: Use the simplified 3D torque equation from your CharacterPawn3D
|
||||
#if inertia.length() > 0:
|
||||
# var angular_acceleration = accumulated_torque / inertia
|
||||
# print("Inertia for %s: %s" % [self, inertia])
|
||||
# print("Angular Acceleration for %s: %s" % [self, angular_acceleration])
|
||||
# angular_velocity += angular_acceleration * state.step
|
||||
# --- 2. APPLY FORCES ---
|
||||
# Apply Gravity (F = m * a)
|
||||
state.apply_central_force(current_gravitational_acceleration * mass)
|
||||
|
||||
# 5. Reset accumulated forces for the next frame
|
||||
accumulated_force = Vector3.ZERO
|
||||
|
||||
@ -5,10 +5,10 @@ var config: GameConfig
|
||||
|
||||
# --- Dictionaries to track players and their objects ---
|
||||
var player_controllers: Dictionary = {} # Key: player_id, Value: PlayerController node
|
||||
var player_pawns: Dictionary = {} # Key: player_id, Value: CharacterPawn3D node
|
||||
var player_pawns: Dictionary = {} # Key: player_id, Value: CharacterPawn3D node
|
||||
|
||||
# This variable will hold the reference to the currently active star system.
|
||||
var current_star_system : StarSystem = null
|
||||
var current_star_system: StarSystem = null
|
||||
|
||||
var registered_spawners: Array[Spawner] = []
|
||||
var waiting_players: Array[int] = [] # A queue for players waiting to spawn
|
||||
@ -68,6 +68,13 @@ func _try_spawn_waiting_player():
|
||||
pawn.set_multiplayer_authority(player_id)
|
||||
|
||||
spawn_point.add_child(pawn)
|
||||
|
||||
# Traverse up to find the physics body (Ship/Module) we just spawned inside
|
||||
var parent_body = _get_orbital_body_ancestor(spawn_point)
|
||||
if parent_body:
|
||||
# Match the ship's speed so we don't slam into the wall
|
||||
pawn.linear_velocity = parent_body.linear_velocity
|
||||
print("GameManager: Pawn inherited velocity ", pawn.linear_velocity, " from ", parent_body.name)
|
||||
|
||||
print("GameManager peer %s: Player %d spawned successfully." % [multiplayer.get_unique_id(), player_id])
|
||||
else:
|
||||
@ -137,3 +144,12 @@ func get_all_trackable_bodies() -> Array[OrbitalBody3D]:
|
||||
func request_server_action(action_name: String, args: Array = []):
|
||||
# This function's body only runs on the SERVER.
|
||||
print("Server received request: ", action_name, " with args: ", args)
|
||||
|
||||
# Helper to find the physics root (Module/Ship) from a child node (Spawner)
|
||||
func _get_orbital_body_ancestor(node: Node) -> OrbitalBody3D:
|
||||
var current = node
|
||||
while current:
|
||||
if current is OrbitalBody3D:
|
||||
return current
|
||||
current = current.get_parent()
|
||||
return null
|
||||
|
||||
@ -8,8 +8,17 @@ 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
|
||||
|
||||
# --- GRAVITY ACCELERATION CACHE ---
|
||||
# The calculated acceleration for this frame. Key: Object ID, Value: Vector3
|
||||
var gravity_force_cache: Dictionary = {}
|
||||
|
||||
# --- PUBLIC API ---
|
||||
func get_calculated_force(body: OrbitalBody3D) -> Vector3:
|
||||
return gravity_force_cache.get(body.get_instance_id(), Vector3.ZERO)
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
gravity_force_cache.clear()
|
||||
|
||||
var star_system: StarSystem = GameManager.current_star_system
|
||||
if not star_system:
|
||||
return
|
||||
@ -22,51 +31,60 @@ func _physics_process(_delta: float) -> void:
|
||||
|
||||
var top_level_bodies: Array[OrbitalBody3D] = [star]
|
||||
top_level_bodies.append_array(planetary_systems)
|
||||
apply_n_body_forces(top_level_bodies)
|
||||
calculate_n_body_forces(top_level_bodies)
|
||||
|
||||
for system in planetary_systems:
|
||||
var system_attractors = system.get_internal_attractors()
|
||||
apply_n_body_forces(system_attractors)
|
||||
calculate_n_body_forces(system_attractors)
|
||||
|
||||
for star_orbiter in star_system.get_orbital_bodies():
|
||||
star_orbiter.apply_force_recursive(calculate_n_body_force(star_orbiter, top_level_bodies))
|
||||
calculate_n_body_force(star_orbiter, top_level_bodies)
|
||||
|
||||
|
||||
func calculate_gravitational_force(orbiter: OrbitalBody3D, primary: OrbitalBody3D) -> Vector3:
|
||||
if not is_instance_valid(orbiter) or not is_instance_valid(primary):
|
||||
func calculate_gravitational_force(body_a: OrbitalBody3D, body_b: OrbitalBody3D) -> Vector3:
|
||||
if not is_instance_valid(body_a) or not is_instance_valid(body_b):
|
||||
# REFACTOR: Return Vector3.ZERO
|
||||
return Vector3.ZERO
|
||||
|
||||
var distance_sq = orbiter.global_position.distance_squared_to(primary.global_position)
|
||||
var distance_sq = body_a.global_position.distance_squared_to(body_b.global_position)
|
||||
|
||||
if distance_sq < 1.0:
|
||||
return Vector3.ZERO
|
||||
|
||||
var influence_a = primary.mass / distance_sq
|
||||
var influence_b = orbiter.mass / distance_sq
|
||||
var influence_a = body_b.mass / distance_sq
|
||||
var influence_b = body_a.mass / distance_sq
|
||||
|
||||
if influence_a < MIN_INFLUENCE_THRESHOLD and influence_b < MIN_INFLUENCE_THRESHOLD:
|
||||
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)
|
||||
var force_magnitude = (G * body_b.mass * body_a.mass) / distance_sq
|
||||
|
||||
var direction = body_a.global_position.direction_to(body_b.global_position)
|
||||
return direction * force_magnitude
|
||||
|
||||
func apply_n_body_forces(attractors: Array[OrbitalBody3D]):
|
||||
func calculate_n_body_forces(attractors: Array[OrbitalBody3D]):
|
||||
for i in range(attractors.size()):
|
||||
var body_a: OrbitalBody3D = attractors[i]
|
||||
if not is_instance_valid(body_a): continue
|
||||
|
||||
# Ensure cache entry exists (Vector3.ZERO)
|
||||
if not body_a.get_instance_id() in gravity_force_cache:
|
||||
gravity_force_cache[body_a.get_instance_id()] = Vector3.ZERO
|
||||
|
||||
for j in range(i + 1, attractors.size()):
|
||||
var body_b: OrbitalBody3D = attractors[j]
|
||||
if not is_instance_valid(body_b): continue
|
||||
|
||||
# Ensure cache entry exists
|
||||
if not body_b.get_instance_id() in gravity_force_cache:
|
||||
gravity_force_cache[body_b.get_instance_id()] = Vector3.ZERO
|
||||
|
||||
# REFACTOR: force_vector is now Vector3
|
||||
var force_vector = calculate_gravitational_force(body_a, body_b)
|
||||
|
||||
if force_vector != Vector3.ZERO:
|
||||
body_a.apply_force_recursive(force_vector)
|
||||
body_b.apply_force_recursive(-force_vector)
|
||||
gravity_force_cache[body_a.get_instance_id()] += (force_vector)
|
||||
gravity_force_cache[body_b.get_instance_id()] -= (force_vector)
|
||||
|
||||
func calculate_n_body_force(body: OrbitalBody3D, attractors: Array[OrbitalBody3D]) -> Vector3:
|
||||
var total_pull: Vector3 = Vector3.ZERO
|
||||
@ -164,7 +182,7 @@ func _calculate_relative_orbital_path(body_to_trace: OrbitalBody3D) -> PackedVec
|
||||
if specific_energy >= 0:
|
||||
time_step = 0.1
|
||||
else:
|
||||
var semi_major_axis = -mu / (2.0 * specific_energy)
|
||||
var semi_major_axis = - mu / (2.0 * specific_energy)
|
||||
var orbital_period = 2.0 * PI * sqrt(pow(semi_major_axis, 3) / mu)
|
||||
time_step = orbital_period / float(num_steps)
|
||||
|
||||
@ -174,7 +192,7 @@ func _calculate_relative_orbital_path(body_to_trace: OrbitalBody3D) -> PackedVec
|
||||
var distance_sq = ghost_relative_pos.length_squared()
|
||||
if distance_sq < 1.0:
|
||||
break
|
||||
var direction = -ghost_relative_pos.normalized()
|
||||
var direction = - ghost_relative_pos.normalized()
|
||||
var force_magnitude = (G * primary_mass * body_mass) / distance_sq
|
||||
var force_vector = direction * force_magnitude
|
||||
var acceleration = force_vector / body_mass
|
||||
@ -185,7 +203,6 @@ func _calculate_relative_orbital_path(body_to_trace: OrbitalBody3D) -> PackedVec
|
||||
|
||||
return path_points
|
||||
|
||||
# --- 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:
|
||||
@ -193,7 +210,7 @@ func calculate_hill_sphere(orbiter: OrbitalBody3D, primary: OrbitalBody3D) -> fl
|
||||
var distance = orbiter.global_position.distance_to(primary.global_position)
|
||||
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)
|
||||
return distance * pow(mass_ratio, 1.0 / 3.0)
|
||||
|
||||
func calculate_simplified_roche_limit(primary: OrbitalBody3D) -> float:
|
||||
if not is_instance_valid(primary) or primary.mass <= 0:
|
||||
|
||||
Reference in New Issue
Block a user