242 lines
6.9 KiB
GDScript
242 lines
6.9 KiB
GDScript
class_name ProceduralPiece extends StructuralPiece
|
|
|
|
@export var vertices: Array[Vector3] = []
|
|
@export var thickness: float = 0.02
|
|
|
|
const PLATE_SHADER = preload("res://scenes/ship/builder/pieces/structural_plate.gdshader")
|
|
|
|
# This is inherited from StructuralPiece, but we use the setter to update visuals
|
|
# var is_preview: bool (Inherited)
|
|
|
|
func _ready():
|
|
# If data has vertices (from the Resource), use them.
|
|
if structure_data and structure_data.vertices.size() > 0:
|
|
vertices = structure_data.vertices
|
|
|
|
# Or if configured procedurally
|
|
# elif structure_data and structure_data.procedural_params:
|
|
# _configure_from_data()
|
|
|
|
if not vertices.is_empty():
|
|
_generate_geometry()
|
|
|
|
super._ready()
|
|
|
|
# Configure this piece as a simple Strut (Beam)
|
|
func configure_strut(start: Vector3, end: Vector3):
|
|
vertices = [start, end]
|
|
|
|
# Create ephemeral data for this specific instance
|
|
structure_data = StructureData.new()
|
|
structure_data.piece_name = "Strut"
|
|
structure_data.type = StructureData.PieceType.STRUT
|
|
|
|
# Define Mounts at both ends
|
|
var dir = (end - start).normalized()
|
|
|
|
# Mount 0: At Start, facing away from End (-dir)
|
|
structure_data.add_mount(start, -dir)
|
|
|
|
# Mount 1: At End, facing away from Start (+dir)
|
|
structure_data.add_mount(end, dir)
|
|
|
|
_generate_geometry()
|
|
|
|
# Configure this piece as a Plate (Triangle/Quad)
|
|
func configure_plate(points: Array[Vector3]):
|
|
vertices = points
|
|
|
|
structure_data = StructureData.new()
|
|
structure_data.piece_name = "Plate"
|
|
structure_data.type = StructureData.PieceType.PLATE
|
|
|
|
# Add Mounts along edges (simplified: midpoints of edges)
|
|
var center = Vector3.ZERO
|
|
for p in points: center += p
|
|
center /= points.size()
|
|
|
|
for i in range(points.size()):
|
|
var p1 = points[i]
|
|
var p2 = points[(i + 1) % points.size()]
|
|
var mid = (p1 + p2) / 2.0
|
|
var edge_norm = (mid - center).normalized()
|
|
|
|
structure_data.add_mount(mid, edge_norm)
|
|
|
|
_generate_geometry()
|
|
|
|
func _generate_geometry():
|
|
for c in get_children():
|
|
if c is MeshInstance3D or c is CollisionShape3D:
|
|
c.queue_free()
|
|
|
|
if vertices.size() == 2:
|
|
_build_strut_mesh()
|
|
elif vertices.size() >= 3:
|
|
_build_plate_mesh()
|
|
|
|
func _build_strut_mesh():
|
|
# Generate a Box or Cylinder between v[0] and v[1]
|
|
var start = vertices[0]
|
|
var end = vertices[1]
|
|
var length = start.distance_to(end)
|
|
var mid = (start + end) / 2.0
|
|
|
|
var mesh_inst = MeshInstance3D.new()
|
|
var box = BoxMesh.new()
|
|
box.size = Vector3(thickness, thickness, length)
|
|
mesh_inst.mesh = box
|
|
add_child(mesh_inst)
|
|
|
|
# Orient mesh
|
|
mesh_inst.position = mid
|
|
if length > 0.001:
|
|
mesh_inst.look_at(end, Vector3.UP)
|
|
|
|
# Collision
|
|
var col = CollisionShape3D.new()
|
|
var shape = BoxShape3D.new()
|
|
shape.size = box.size
|
|
col.shape = shape
|
|
col.position = mid
|
|
col.rotation = mesh_inst.rotation
|
|
add_child(col)
|
|
|
|
|
|
func _build_plate_mesh():
|
|
var st = SurfaceTool.new()
|
|
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
|
|
|
var half_thick = thickness / 2.0
|
|
var top_verts = []
|
|
var bottom_verts = []
|
|
|
|
# UV Calculation Helpers
|
|
var min_v = Vector2(INF, INF)
|
|
var max_v = Vector2(-INF, -INF)
|
|
for v in vertices:
|
|
min_v.x = min(min_v.x, v.x)
|
|
min_v.y = min(min_v.y, v.y)
|
|
max_v.x = max(max_v.x, v.x)
|
|
max_v.y = max(max_v.y, v.y)
|
|
var size_v = max_v - min_v
|
|
|
|
for v in vertices:
|
|
top_verts.append(Vector3(v.x, v.y, half_thick))
|
|
bottom_verts.append(Vector3(v.x, v.y, -half_thick))
|
|
|
|
# --- 1. Top Face (Inside - Red Channel) ---
|
|
st.set_color(Color(1, 0, 0, 1))
|
|
st.set_normal(Vector3.BACK)
|
|
|
|
# Fan Triangulation
|
|
for i in range(1, top_verts.size() - 1):
|
|
_add_vertex_with_uv(st, top_verts[0], min_v, size_v)
|
|
_add_vertex_with_uv(st, top_verts[i], min_v, size_v)
|
|
_add_vertex_with_uv(st, top_verts[i+1], min_v, size_v)
|
|
|
|
# --- 2. Bottom Face (Outside - Blue Channel) ---
|
|
st.set_color(Color(0, 0, 1, 1))
|
|
st.set_normal(Vector3.FORWARD)
|
|
|
|
for i in range(1, bottom_verts.size() - 1):
|
|
_add_vertex_with_uv(st, bottom_verts[0], min_v, size_v)
|
|
_add_vertex_with_uv(st, bottom_verts[i+1], min_v, size_v)
|
|
_add_vertex_with_uv(st, bottom_verts[i], min_v, size_v)
|
|
|
|
# --- 3. Side Faces (Sides - Black) ---
|
|
st.set_color(Color(0, 0, 0, 1))
|
|
|
|
for i in range(vertices.size()):
|
|
var next_i = (i + 1) % vertices.size()
|
|
var v1 = top_verts[i]
|
|
var v2 = top_verts[next_i]
|
|
var v3 = bottom_verts[next_i]
|
|
var v4 = bottom_verts[i]
|
|
|
|
var side_vec = (v2 - v1).normalized()
|
|
var normal = side_vec.cross(Vector3.BACK).normalized()
|
|
st.set_normal(normal)
|
|
|
|
# Sides don't need specific UVs for the margin shader
|
|
st.set_uv(Vector2.ZERO)
|
|
st.add_vertex(v1); st.add_vertex(v2); st.add_vertex(v4)
|
|
st.add_vertex(v2); st.add_vertex(v3); st.add_vertex(v4)
|
|
|
|
var mesh_inst = MeshInstance3D.new()
|
|
mesh_inst.mesh = st.commit()
|
|
_apply_material(mesh_inst)
|
|
add_child(mesh_inst)
|
|
|
|
# Generate Labels
|
|
_generate_serial_labels(half_thick)
|
|
|
|
# Collision (Convex)
|
|
# For collision, we just feed ALL the points (top and bottom) to the convex hull generator.
|
|
var col = CollisionShape3D.new()
|
|
var shape = ConvexPolygonShape3D.new()
|
|
var all_points = top_verts.duplicate()
|
|
all_points.append_array(bottom_verts)
|
|
shape.points = all_points
|
|
col.shape = shape
|
|
add_child(col)
|
|
|
|
|
|
func _add_vertex_with_uv(st: SurfaceTool, v: Vector3, min_v: Vector2, size_v: Vector2):
|
|
var uv = Vector2(
|
|
(v.x - min_v.x) / size_v.x,
|
|
(v.y - min_v.y) / size_v.y
|
|
)
|
|
st.set_uv(uv)
|
|
st.add_vertex(v)
|
|
|
|
|
|
func _apply_material(mesh_inst: MeshInstance3D):
|
|
if is_preview:
|
|
var mat = StandardMaterial3D.new()
|
|
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
mat.albedo_color = Color(0.2, 0.6, 1.0, 0.4) # Semi-transparent Blue
|
|
mat.emission_enabled = true
|
|
mat.emission = Color(0.2, 0.6, 1.0)
|
|
mat.emission_energy_multiplier = 1.0
|
|
mat.cull_mode = BaseMaterial3D.CULL_DISABLED # See inside
|
|
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED # Glowy
|
|
# super._apply_material(mesh_inst) # Use simple transparent blue
|
|
mesh_inst.material_override = mat
|
|
else:
|
|
var mat = ShaderMaterial.new()
|
|
# Use Shader Material
|
|
mat.shader = PLATE_SHADER
|
|
|
|
mesh_inst.material_override = mat
|
|
|
|
func _generate_serial_labels(offset_z: float):
|
|
# Generate a mock serial based on instance ID or data
|
|
var serial_text = "IND-%d\nMDL-%s" % [get_instance_id() % 10000, structure_data.shape.left(3).to_upper()]
|
|
|
|
# Top Label (Inside)
|
|
var label_in = Label3D.new()
|
|
label_in.text = serial_text
|
|
label_in.font_size = 12
|
|
label_in.position = Vector3(0, 0, offset_z + 0.005) # Slight offset to avoid Z-fighting
|
|
label_in.pixel_size = 0.002
|
|
label_in.modulate = Color(0.5, 0.5, 0.5, 0.8)
|
|
label_in.rotation_degrees = Vector3(0, 0, 0)
|
|
add_child(label_in)
|
|
|
|
# Bottom Label (Outside)
|
|
var label_out = Label3D.new()
|
|
label_out.text = serial_text
|
|
label_out.font_size = 12
|
|
label_out.position = Vector3(0, 0, -offset_z - 0.005)
|
|
label_out.pixel_size = 0.002
|
|
label_out.modulate = Color(0.3, 0.3, 0.3, 0.8)
|
|
label_out.rotation_degrees = Vector3(180, 0, 0) # Face down
|
|
add_child(label_out)
|
|
|
|
# Override the parent's update function to use our specific material logic
|
|
func _update_preview_visuals():
|
|
for child in get_children():
|
|
if child is MeshInstance3D:
|
|
_apply_material(child)
|