feat(world): generic dive site scene fed by a baked site definition
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e590f2bc7
commit
b1d32f7d81
8 files changed
+286
-6
No files matched your search
@@ -0,0 +1,113 @@
|
||||
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 error := definition.validation_error()
|
||||
if not error.is_empty():
|
||||
return error
|
||||
var terrain_error := _terrain.configure(definition.height_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
|
||||
@@ -0,0 +1,47 @@
|
||||
[gd_scene load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://game/world/sites/dive_site.gd" id="1_site"]
|
||||
[ext_resource type="Script" path="res://game/world/sites/bathymetric_terrain_actor.gd" id="2_terrain"]
|
||||
|
||||
[sub_resource type="Environment" id="EnvironmentUnderwater"]
|
||||
background_mode = 1
|
||||
background_color = Color(0.005, 0.045, 0.065, 1)
|
||||
ambient_light_source = 3
|
||||
ambient_light_color = Color(0.12, 0.32, 0.37, 1)
|
||||
ambient_light_energy = 0.42
|
||||
glow_enabled = true
|
||||
glow_levels/1 = 1.0
|
||||
glow_levels/2 = 1.0
|
||||
glow_levels/3 = 0.6
|
||||
glow_levels/4 = 0.2
|
||||
glow_intensity = 1.0
|
||||
glow_blend_mode = 0
|
||||
glow_hdr_threshold = 1.0
|
||||
fog_enabled = true
|
||||
fog_light_color = Color(0.07, 0.25, 0.3, 1)
|
||||
fog_light_energy = 0.48
|
||||
fog_density = 0.014
|
||||
fog_height = -4.0
|
||||
fog_height_density = 0.025
|
||||
volumetric_fog_enabled = true
|
||||
volumetric_fog_density = 0.025
|
||||
volumetric_fog_length = 72.0
|
||||
|
||||
[node name="DiveSite" type="Node3D"]
|
||||
script = ExtResource("1_site")
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("EnvironmentUnderwater")
|
||||
|
||||
[node name="AmbientDirection" type="DirectionalLight3D" parent="."]
|
||||
rotation_degrees = Vector3(-52, -24, 0)
|
||||
light_color = Color(0.39, 0.65, 0.7, 1)
|
||||
light_energy = 0.38
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="Terrain" type="StaticBody3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("2_terrain")
|
||||
|
||||
[node name="Placements" type="Node3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
@@ -68,10 +68,16 @@ func capture(request: SonarCaptureRequest) -> SonarCaptureResult:
|
||||
var material := SonarMaterial.Value.ROCK
|
||||
var reflectivity := 0.72
|
||||
if collider != null:
|
||||
material = int(collider.get_meta("sonar_material", material))
|
||||
reflectivity = float(
|
||||
collider.get_meta("sonar_reflectivity", reflectivity)
|
||||
)
|
||||
if collider.has_method("sonar_material_at"):
|
||||
material = int(collider.call("sonar_material_at", world_position))
|
||||
reflectivity = float(
|
||||
collider.call("sonar_reflectivity_at", world_position)
|
||||
)
|
||||
else:
|
||||
material = int(collider.get_meta("sonar_material", material))
|
||||
reflectivity = float(
|
||||
collider.get_meta("sonar_reflectivity", reflectivity)
|
||||
)
|
||||
var incidence := clampf(absf(world_normal.dot(Vector3.UP)), 0.18, 1.0)
|
||||
var attenuation := lerpf(1.0, 0.55, distance_ratio)
|
||||
var intensity := clampf(incidence * reflectivity * attenuation, 0.0, 1.0)
|
||||
|
||||
Reference in new issue
Block a user