Quality Gate / Quality Gate (pull_request) Successful in 6m49s
First wreck in the production world layer. A WreckActor (StaticBody3D) instantiates a two-LOD glTF visual, builds a trimesh collision from a separate lighter glTF, and carries the sonar metadata (metal, 0.9 reflectivity) so the bathymetry rays echo it. The sunlit shallows site places the MV Hebat Allah 62 m ahead of the submarine on the floor. The assets derive from a CC BY 4.0 photogrammetry scan by Holger Buss (Sketchfab). The raw 274 MB scan stays outside the repo; the committed GLBs come from a reproducible Blender pipeline under scripts/assets/: skirt cut, 44 m rescale, merge by distance (the scan is a triangle soup), fragment removal, 0.4 m voxel remesh, decimation to 58k/14.5k/2.9k triangles, smart UVs and a 2K albedo rebaked from a luminance-rebuilt texture (the scan's red channel is dead). docs/ASSET_LICENSES.md starts the licence registry required by the design plan and records the attribution owed in the credits. The site suite gains twelve assertions covering sonar metadata, collision, LOD handover and triangle budgets. Godot extracts the embedded albedo next to the glb on import; those PNGs are ignored like the cockpit ones. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
66 lines
2.3 KiB
GDScript
66 lines
2.3 KiB
GDScript
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)
|