Files
millimeters-of-aluminum/scripts/orbital_body_2d.gd

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)