Files
millimeters-of-aluminum/scenes/ship/builder/pieces/structural_piece.gd
2025-12-05 15:50:48 +01:00

130 lines
3.7 KiB
GDScript

class_name StructuralPiece extends ShipPiece
# The definition of this piece.
@export var structure_data: StructureData
# Track who we are welded to so we don't double-weld
var connected_neighbors: Array[StructuralPiece] = []
var mount_areas: Array[PieceMount] = []
# Flag to distinguish between a preview piece (no physics) and a placed piece
var is_preview: bool = false:
set(value):
is_preview = value
_update_preview_visuals()
func _init():
base_mass = 50.0
physics_mode = PhysicsMode.INDEPENDENT
auto_proxy_gravity = true
func _ready():
super._ready()
if is_preview:
collision_layer = 0
collision_mask = 0
return
sleeping = false
can_sleep = false
# If we have data, generate the mount points
if structure_data:
_generate_mount_detectors()
# Attempt to weld to anything we are already touching
_initial_weld_scan()
func _generate_mount_detectors():
for i in range(structure_data.mounts.size()):
var mount_def = structure_data.mounts[i]
var area = PieceMount.new() # Uses the class_name
area.name = "Mount_%d" % i
area.mount_type = mount_def.get("type", 0) # Set the type!
add_child(area)
# Position
area.position = mount_def.get("position", Vector3.ZERO)
# Orientation: Align Area -Z with Mount Normal
var normal = mount_def.get("normal", Vector3.BACK)
var up = mount_def.get("up", Vector3.UP)
if normal.cross(up).is_zero_approx():
up = Vector3.RIGHT if abs(normal.y) > 0.9 else Vector3.UP
if normal.length_squared() > 0.01:
area.transform.basis = Basis.looking_at(normal, up)
# Shape
var shape = CollisionShape3D.new()
var sphere = SphereShape3D.new()
sphere.radius = 0.15
shape.shape = sphere
area.add_child(shape)
# Layer setup is handled in PieceMount._ready() now, or explicit here:
area.collision_layer = 1 << 14
area.collision_mask = 1 << 14
area.monitoring = true
area.monitorable = true
mount_areas.append(area)
func try_weld():
_scan_and_weld_neighbors()
func _initial_weld_scan():
await get_tree().physics_frame
await get_tree().physics_frame
if not is_instance_valid(self): return
_scan_and_weld_neighbors()
func _scan_and_weld_neighbors():
for my_area in mount_areas:
for other_area in my_area.get_overlapping_areas():
var other_piece = other_area.get_parent()
if other_piece is StructuralPiece and other_piece != self:
# Here we could check mount compatibility (types, alignment)
if not other_piece in connected_neighbors:
_create_weld_to(other_piece)
func _create_weld_to(neighbor: StructuralPiece):
var joint = Generic6DOFJoint3D.new()
_lock_joint_axis(joint)
add_child(joint)
# Position joint halfway between pieces
joint.global_position = (self.global_position + neighbor.global_position) / 2.0
joint.node_a = self.get_path()
joint.node_b = neighbor.get_path()
connected_neighbors.append(neighbor)
neighbor.connected_neighbors.append(self)
func _lock_joint_axis(joint: Generic6DOFJoint3D):
for axis in [Generic6DOFJoint3D.PARAM_LINEAR_LOWER_LIMIT, Generic6DOFJoint3D.PARAM_LINEAR_UPPER_LIMIT]:
joint.set_param_x(axis, 0.0)
joint.set_param_y(axis, 0.0)
joint.set_param_z(axis, 0.0)
for axis in [Generic6DOFJoint3D.PARAM_ANGULAR_LOWER_LIMIT, Generic6DOFJoint3D.PARAM_ANGULAR_UPPER_LIMIT]:
joint.set_param_x(axis, 0.0)
joint.set_param_y(axis, 0.0)
joint.set_param_z(axis, 0.0)
func _update_preview_visuals():
var mesh_instance = find_child("MeshInstance3D")
if mesh_instance and mesh_instance.mesh:
var mat = StandardMaterial3D.new()
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.albedo_color = Color(0.5, 0.8, 1.0, 0.5)
mesh_instance.material_override = mat
func _exit_tree():
for neighbor in connected_neighbors:
if is_instance_valid(neighbor):
neighbor.connected_neighbors.erase(self)