181 lines
6.2 KiB
GDScript
181 lines
6.2 KiB
GDScript
# orbital_body_3d.gd
|
|
# REFACTOR: Extends Node3D instead of Node2D
|
|
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.
|
|
}
|
|
|
|
@export var physics_mode: PhysicsMode = PhysicsMode.INDEPENDENT
|
|
|
|
var current_grid_authority: OrbitalBody3D = null
|
|
|
|
# Mass of this individual component
|
|
@export var base_mass: float = 1.0
|
|
|
|
# --- 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
|
|
|
|
|
|
func _ready():
|
|
freeze_mode = FreezeMode.FREEZE_MODE_KINEMATIC
|
|
|
|
recalculate_physical_properties()
|
|
set_physics_process(not Engine.is_editor_hint())
|
|
|
|
if physics_mode == PhysicsMode.ANCHORED:
|
|
_update_gravity_proxy()
|
|
linear_velocity = gravity_proxy_parent.linear_velocity if is_instance_valid(gravity_proxy_parent) else Vector3.ZERO
|
|
# print(name, " initialized as ANCHORED with gravity proxy: ", gravity_proxy_parent)
|
|
|
|
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 ---
|
|
|
|
func _update_mass_and_inertia():
|
|
mass = base_mass
|
|
for child in get_children():
|
|
if child is OrbitalBody3D:
|
|
child._update_mass_and_inertia() # Recurse into children
|
|
mass += child.mass
|
|
|
|
print("Node: %s, Mass: %f" % [self, mass])
|
|
|
|
|
|
func _integrate_forces(state: PhysicsDirectBodyState3D):
|
|
# if not is_multiplayer_authority(): return
|
|
# Safety Check for Division by Zero
|
|
if mass <= 0.0:
|
|
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
|
|
|
|
# --- 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
|
|
accumulated_torque = Vector3.ZERO
|
|
|
|
# --- PHYSICAL PROPERTIES RECALCULATION FOR COMPOSITE BODIES ---
|
|
func recalculate_physical_properties():
|
|
if physics_mode != PhysicsMode.COMPOSITE:
|
|
print("Recalculating physical properties for non-COMPOSITE body:", self)
|
|
mass = base_mass
|
|
if inertia == Vector3.ZERO:
|
|
inertia = Vector3(1.0, 1.0, 1.0)
|
|
return
|
|
|
|
var all_parts: Array[OrbitalBody3D] = []
|
|
_collect_anchored_parts(all_parts)
|
|
|
|
if all_parts.is_empty():
|
|
mass = base_mass
|
|
inertia = Vector3(1.0, 1.0, 1.0)
|
|
return
|
|
|
|
# --- Step 1: Calculate Total Mass and LOCAL Center of Mass ---
|
|
var total_mass: float = 0.0
|
|
var weighted_local_pos_sum: Vector3 = Vector3.ZERO
|
|
|
|
for part in all_parts:
|
|
total_mass += part.base_mass
|
|
# Calculate position relative to the Module root
|
|
var local_pos = part.global_position - self.global_position
|
|
weighted_local_pos_sum += local_pos * part.base_mass
|
|
|
|
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 ---
|
|
# using the Parallel Axis Theorem for Point Masses: I = sum( m * r^2 )
|
|
# where r is the perpendicular distance to the axis of rotation.
|
|
var total_inertia = Vector3.ZERO
|
|
|
|
for part in all_parts:
|
|
var local_pos = part.global_position - self.global_position
|
|
var r_vec = local_pos - local_center_of_mass # Vector from CoM to part
|
|
|
|
# Distance squared to the X, Y, and Z axes respectively:
|
|
var r_sq_x = r_vec.y * r_vec.y + r_vec.z * r_vec.z
|
|
var r_sq_y = r_vec.x * r_vec.x + r_vec.z * r_vec.z
|
|
var r_sq_z = r_vec.x * r_vec.x + r_vec.y * r_vec.y
|
|
|
|
total_inertia.x += part.base_mass * r_sq_x
|
|
total_inertia.y += part.base_mass * r_sq_y
|
|
total_inertia.z += part.base_mass * r_sq_z
|
|
|
|
# --- Step 3: Assign the final values ---
|
|
self.mass = total_mass
|
|
|
|
# Explicitly tell the physics engine where the new center of mass is
|
|
self.center_of_mass_mode = RigidBody3D.CENTER_OF_MASS_MODE_CUSTOM
|
|
self.center_of_mass = local_center_of_mass
|
|
|
|
# Enforce a minimum inertia to prevent physics explosions
|
|
if total_inertia.length_squared() < 0.1:
|
|
total_inertia = Vector3(1.0, 1.0, 1.0)
|
|
|
|
self.inertia = total_inertia
|
|
print("Recalculated Ship: Mass %.1f, COM %s, Inertia %s" % [mass, center_of_mass, inertia])
|
|
|
|
# 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():
|
|
if child is OrbitalBody3D and child.physics_mode == PhysicsMode.ANCHORED:
|
|
child._collect_anchored_parts(parts_array)
|