168 lines
5.8 KiB
GDScript
168 lines
5.8 KiB
GDScript
# orbital_body_3d.gd
|
|
# REFACTOR: Extends Node3D instead of Node2D
|
|
class_name OrbitalBody3D
|
|
extends Node3D
|
|
|
|
# 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
|
|
@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: 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():
|
|
recalculate_physical_properties()
|
|
set_physics_process(not Engine.is_editor_hint())
|
|
|
|
# --- PUBLIC FORCE APPLICATION METHODS ---
|
|
# 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)
|
|
PhysicsMode.ANCHORED:
|
|
# 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 OrbitalBody3D:
|
|
# Recursively call the parent's apply_force method.
|
|
p.apply_force(force, pos)
|
|
return # Stop at the first OrbitalBody3D parent
|
|
p = p.get_parent()
|
|
|
|
push_error("Anchored OrbitalBody3D has become dislodged and is now Composite.")
|
|
physics_mode = PhysicsMode.COMPOSITE
|
|
apply_force(force, position)
|
|
|
|
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
|
|
|
|
# 'r' is the vector from the center of mass (global_position) to the point of force application (position).
|
|
var r = pos - global_position
|
|
|
|
# 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 OrbitalBody3D:
|
|
child._update_mass_and_inertia() # Recurse into children
|
|
mass += child.mass
|
|
|
|
print("Node: %s, Mass: %f" % [self, mass])
|
|
|
|
func _physics_process(delta):
|
|
if not Engine.is_editor_hint():
|
|
match physics_mode:
|
|
PhysicsMode.INDEPENDENT:
|
|
_integrate_forces(delta)
|
|
PhysicsMode.COMPOSITE:
|
|
_integrate_forces(delta)
|
|
|
|
func _integrate_forces(delta):
|
|
# Safety Check for Division by Zero
|
|
var sim_mass = mass
|
|
if sim_mass <= 0.0:
|
|
accumulated_force = Vector3.ZERO
|
|
accumulated_torque = Vector3.ZERO
|
|
return
|
|
|
|
# 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)
|
|
# 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
|
|
|
|
func recalculate_physical_properties():
|
|
if physics_mode != PhysicsMode.COMPOSITE:
|
|
mass = base_mass
|
|
if inertia <= 0.0:
|
|
inertia = 1.0
|
|
return
|
|
|
|
var all_parts: Array[OrbitalBody3D] = []
|
|
_collect_anchored_parts(all_parts)
|
|
|
|
if all_parts.is_empty():
|
|
mass = base_mass
|
|
inertia = 1.0
|
|
return
|
|
|
|
# --- Step 1: Calculate Total Mass and LOCAL Center of Mass ---
|
|
var total_mass = 0.0
|
|
# REFACTOR: Use Vector3
|
|
var weighted_local_pos_sum = Vector3.ZERO
|
|
for part in all_parts:
|
|
total_mass += part.base_mass
|
|
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 ---
|
|
var total_inertia = 0.0
|
|
for part in all_parts:
|
|
var local_pos = part.global_position - self.global_position
|
|
# 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
|
|
self.inertia = total_inertia * 0.01
|
|
if self.inertia <= 0.0: # Safety check
|
|
self.inertia = 1.0
|
|
|
|
# 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) |