class_name WreckActor extends StaticBody3D ## A wreck resting on a site: two visual levels of detail from one glTF and a ## separate, much lighter trimesh collision. The sonar reads the body's ## metadata, so this node is the one owner of how a wreck echoes. const LOD0_NODE_NAME := "WreckLOD0" const LOD1_NODE_NAME := "WreckLOD1" const COLLISION_NODE_NAME := "WreckCollision" const COLLISION_SHAPE_NAME := "Collision" const VISUAL_NAME := "Visual" ## Hysteresis around the LOD handover so a hovering submarine never flickers. const LOD_MARGIN_M := 4.0 @export var visual_scene: PackedScene @export var collision_scene: PackedScene ## Beyond this distance the coarse LOD is drawn instead of the detailed one. @export_range(10.0, 400.0, 1.0) var lod_switch_distance_m := 45.0 @export_range(0.0, 1.0, 0.01) var sonar_reflectivity := 0.9 func _ready() -> void: set_meta("sonar_material", SonarMaterial.Value.METAL) set_meta("sonar_reflectivity", sonar_reflectivity) _build_visual() _build_collision() func _build_visual() -> void: if visual_scene == null: push_error("WreckActor %s has no visual scene" % name) return var visual := visual_scene.instantiate() as Node3D visual.name = VISUAL_NAME add_child(visual) var lod0 := visual.find_child(LOD0_NODE_NAME, true, false) as MeshInstance3D var lod1 := visual.find_child(LOD1_NODE_NAME, true, false) as MeshInstance3D if lod0 == null or lod1 == null: push_error("WreckActor %s visual scene lacks %s or %s" % [ name, LOD0_NODE_NAME, LOD1_NODE_NAME ]) return lod0.visibility_range_end = lod_switch_distance_m lod0.visibility_range_end_margin = LOD_MARGIN_M lod1.visibility_range_begin = lod_switch_distance_m lod1.visibility_range_begin_margin = LOD_MARGIN_M func _build_collision() -> void: if collision_scene == null: push_error("WreckActor %s has no collision scene" % name) return var source := collision_scene.instantiate() as Node3D var collision_mesh := source.find_child(COLLISION_NODE_NAME, true, false) as MeshInstance3D if collision_mesh == null or collision_mesh.mesh == null: push_error("WreckActor %s collision scene lacks %s" % [name, COLLISION_NODE_NAME]) source.free() return var shape := CollisionShape3D.new() shape.name = COLLISION_SHAPE_NAME shape.shape = collision_mesh.mesh.create_trimesh_shape() shape.transform = collision_mesh.transform source.free() add_child(shape)