Files
Ransom-s-wake/game/world/sites/dive_site.gd
T
OlivierandClaude Opus 5.5 682e1f18e5 perf(sites): validate the site once and hand collision the field array
DiveSite.configure() builds the height field once (TerrainHeightField
validates the data) instead of running the definition validation and
then the field construction again; the composition root relies on the
error returned by configure(). The collision shape takes the field's
row-major elevations directly instead of copying 263k cells one by one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:52:04 +02:00

114 lines
3.4 KiB
GDScript

class_name DiveSite
extends Node3D
const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd")
const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd")
const SitePlacement = preload("res://game/domain/sites/site_placement.gd")
const BathymetricTerrainActor = preload(
"res://game/world/sites/bathymetric_terrain_actor.gd"
)
const METAL_REFLECTIVITY := 1.0
const BEACON_COLOR := Color(0.2, 0.78, 0.9)
@onready var _terrain: BathymetricTerrainActor = %Terrain
@onready var _placements_root: Node3D = %Placements
var _definition: DiveSiteDefinition
var _placeholder_count := 0
func configure(definition: DiveSiteDefinition) -> String:
if definition == null:
return "dive site definition is missing"
var field := definition.height_field()
if field == null:
return "dive site definition is invalid"
var terrain_error := _terrain.configure(field)
if not terrain_error.is_empty():
return terrain_error
_definition = definition
for child in _placements_root.get_children():
_placements_root.remove_child(child)
child.free()
_placeholder_count = 0
for placement: SitePlacement in definition.placements:
_place(placement)
return ""
func definition() -> DiveSiteDefinition:
return _definition
func terrain() -> BathymetricTerrainActor:
return _terrain
func spawn_transform() -> Transform3D:
return _definition.spawn_transform() if _definition != null else Transform3D.IDENTITY
func placeholder_count() -> int:
return _placeholder_count
func _place(placement: SitePlacement) -> void:
if placement.is_sonar_metal():
_placements_root.add_child(_metal_placeholder(placement))
_placeholder_count += 1
elif placement.kind == SitePlacement.Kind.VESSEL_ANCHOR:
_placements_root.add_child(_beacon(placement))
_placeholder_count += 1
func _metal_placeholder(placement: SitePlacement) -> StaticBody3D:
var body := StaticBody3D.new()
body.name = String(placement.placement_id)
body.position = placement.position_m
body.rotation.y = placement.yaw_radians
body.set_meta("sonar_material", SonarMaterial.Value.METAL)
body.set_meta("sonar_reflectivity", METAL_REFLECTIVITY)
var mesh := BoxMesh.new()
mesh.size = placement.footprint_m
var mesh_instance := MeshInstance3D.new()
mesh_instance.name = "Mesh"
mesh_instance.mesh = mesh
var material := StandardMaterial3D.new()
material.albedo_color = Color(0.24, 0.18, 0.08)
material.metallic = 0.88
material.roughness = 0.27
mesh_instance.material_override = material
body.add_child(mesh_instance)
var shape := BoxShape3D.new()
shape.size = placement.footprint_m
var collision := CollisionShape3D.new()
collision.name = "Collision"
collision.shape = shape
body.add_child(collision)
return body
func _beacon(placement: SitePlacement) -> MeshInstance3D:
var mesh := SphereMesh.new()
mesh.radius = 0.55
mesh.height = 1.1
var beacon := MeshInstance3D.new()
beacon.name = String(placement.placement_id)
beacon.mesh = mesh
beacon.position = placement.position_m
var material := StandardMaterial3D.new()
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
material.albedo_color = BEACON_COLOR
material.emission_enabled = true
material.emission = BEACON_COLOR
material.emission_energy_multiplier = 5.0
beacon.material_override = material
var light := OmniLight3D.new()
light.name = "BeaconLight"
light.omni_range = 12.0
light.light_color = BEACON_COLOR
light.light_energy = 4.0
beacon.add_child(light)
return beacon