255 lines
8.3 KiB
GDScript
255 lines
8.3 KiB
GDScript
class_name BathymetricSonarRenderer
|
|
extends SubViewport
|
|
|
|
const SonarScan = preload("res://game/domain/sonar/sonar_scan.gd")
|
|
const SonarReliefMeshBuilder = preload(
|
|
"res://game/presentation/sonar/sonar_relief_mesh_builder.gd"
|
|
)
|
|
|
|
var _current_mesh_instance: MeshInstance3D
|
|
var _current_grid_instance: MeshInstance3D
|
|
var _previous_mesh_instance: MeshInstance3D
|
|
var _contact_root: Node3D
|
|
var _idle_prompt: Label
|
|
var _current_material: StandardMaterial3D
|
|
var _grid_material: StandardMaterial3D
|
|
var _previous_material: StandardMaterial3D
|
|
var _current_scan_id: StringName = &""
|
|
var _previous_scan_id: StringName = &""
|
|
var _built_current_rows := 0
|
|
var _built_current_triangle_count := 0
|
|
var _built_current_grid_segment_count := 0
|
|
|
|
|
|
func _ready() -> void:
|
|
size = Vector2i(960, 540)
|
|
render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
|
positional_shadow_atlas_size = 1024
|
|
own_world_3d = true
|
|
_build_render_world()
|
|
|
|
|
|
func present(
|
|
current_scan: SonarScan,
|
|
previous_scan: SonarScan,
|
|
visible_rows: int,
|
|
previous_opacity: float
|
|
) -> void:
|
|
if current_scan == null:
|
|
return
|
|
_idle_prompt.visible = false
|
|
if current_scan.scan_id != _current_scan_id:
|
|
_current_scan_id = current_scan.scan_id
|
|
_current_mesh_instance.mesh = ArrayMesh.new()
|
|
_current_grid_instance.mesh = ArrayMesh.new()
|
|
_built_current_rows = 0
|
|
_built_current_triangle_count = 0
|
|
_built_current_grid_segment_count = 0
|
|
_rebuild_contacts(current_scan)
|
|
_build_current_rows(current_scan, clampi(visible_rows, 0, current_scan.samples.rows))
|
|
_update_contact_visibility(current_scan, visible_rows)
|
|
|
|
if previous_scan == null:
|
|
_previous_scan_id = &""
|
|
_previous_mesh_instance.mesh = ArrayMesh.new()
|
|
_previous_mesh_instance.visible = false
|
|
else:
|
|
_previous_mesh_instance.visible = previous_opacity > 0.0
|
|
if previous_scan.scan_id != _previous_scan_id:
|
|
_previous_scan_id = previous_scan.scan_id
|
|
_previous_mesh_instance.mesh = SonarReliefMeshBuilder.build_full_mesh(
|
|
previous_scan,
|
|
_previous_material
|
|
)
|
|
_previous_material.albedo_color.a = clampf(previous_opacity, 0.0, 1.0)
|
|
|
|
|
|
func render_texture() -> Texture2D:
|
|
return get_texture()
|
|
|
|
|
|
func built_row_count() -> int:
|
|
return _built_current_rows
|
|
|
|
|
|
func built_triangle_count() -> int:
|
|
return _built_current_triangle_count
|
|
|
|
|
|
func built_grid_segment_count() -> int:
|
|
return _built_current_grid_segment_count
|
|
|
|
|
|
func idle_prompt_visible() -> bool:
|
|
return _idle_prompt != null and _idle_prompt.visible
|
|
|
|
|
|
func visible_contact_count() -> int:
|
|
var count := 0
|
|
for child: Node in _contact_root.get_children():
|
|
if child is Node3D and (child as Node3D).visible:
|
|
count += 1
|
|
return count
|
|
|
|
|
|
func _build_render_world() -> void:
|
|
var environment_node := WorldEnvironment.new()
|
|
var environment := Environment.new()
|
|
environment.background_mode = Environment.BG_COLOR
|
|
environment.background_color = Color("020407")
|
|
environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
|
environment.ambient_light_color = Color(0.18, 0.31, 0.34)
|
|
environment.ambient_light_energy = 0.42
|
|
environment_node.environment = environment
|
|
add_child(environment_node)
|
|
|
|
_current_material = _relief_material(false)
|
|
_grid_material = _surface_grid_material()
|
|
_previous_material = _relief_material(true)
|
|
_current_mesh_instance = MeshInstance3D.new()
|
|
_current_mesh_instance.name = "CurrentRelief"
|
|
_current_mesh_instance.mesh = ArrayMesh.new()
|
|
add_child(_current_mesh_instance)
|
|
_current_grid_instance = MeshInstance3D.new()
|
|
_current_grid_instance.name = "CurrentReliefGrid"
|
|
_current_grid_instance.mesh = ArrayMesh.new()
|
|
add_child(_current_grid_instance)
|
|
_previous_mesh_instance = MeshInstance3D.new()
|
|
_previous_mesh_instance.name = "PreviousRelief"
|
|
_previous_mesh_instance.mesh = ArrayMesh.new()
|
|
_previous_mesh_instance.visible = false
|
|
add_child(_previous_mesh_instance)
|
|
|
|
_contact_root = Node3D.new()
|
|
_contact_root.name = "Contacts"
|
|
add_child(_contact_root)
|
|
_add_submarine_marker()
|
|
|
|
var camera := Camera3D.new()
|
|
camera.name = "SonarCamera"
|
|
camera.projection = Camera3D.PROJECTION_ORTHOGONAL
|
|
camera.size = 116.0
|
|
camera.far = 240.0
|
|
camera.position = Vector3(0.0, 65.0, -30.0)
|
|
add_child(camera)
|
|
camera.look_at(Vector3(0.0, -38.0, 58.0), Vector3.UP)
|
|
camera.current = true
|
|
|
|
var key_light := DirectionalLight3D.new()
|
|
key_light.rotation_degrees = Vector3(-52.0, -28.0, 0.0)
|
|
key_light.light_energy = 1.25
|
|
key_light.shadow_enabled = true
|
|
add_child(key_light)
|
|
var fill_light := DirectionalLight3D.new()
|
|
fill_light.rotation_degrees = Vector3(-28.0, 145.0, 18.0)
|
|
fill_light.light_energy = 0.38
|
|
add_child(fill_light)
|
|
_build_idle_prompt()
|
|
|
|
|
|
func _build_idle_prompt() -> void:
|
|
_idle_prompt = Label.new()
|
|
_idle_prompt.name = "IdlePrompt"
|
|
_idle_prompt.position = Vector2(180.0, 238.0)
|
|
_idle_prompt.size = Vector2(600.0, 64.0)
|
|
_idle_prompt.text = "SONAR PRÊT · F POUR ÉMETTRE"
|
|
_idle_prompt.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
_idle_prompt.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
_idle_prompt.add_theme_font_size_override("font_size", 26)
|
|
_idle_prompt.add_theme_color_override("font_color", Color(0.38, 0.88, 0.86))
|
|
_idle_prompt.add_theme_color_override("font_outline_color", Color(0.0, 0.04, 0.05))
|
|
_idle_prompt.add_theme_constant_override("outline_size", 6)
|
|
add_child(_idle_prompt)
|
|
|
|
|
|
func _relief_material(transparent: bool) -> StandardMaterial3D:
|
|
var material := StandardMaterial3D.new()
|
|
material.vertex_color_use_as_albedo = true
|
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
if transparent:
|
|
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
material.albedo_color = Color(1.0, 1.0, 1.0, 0.0)
|
|
return material
|
|
|
|
|
|
func _surface_grid_material() -> StandardMaterial3D:
|
|
var material := StandardMaterial3D.new()
|
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
material.albedo_color = Color(0.32, 0.95, 0.91, 0.34)
|
|
material.emission_enabled = true
|
|
material.emission = Color(0.18, 0.78, 0.76)
|
|
material.emission_energy_multiplier = 1.35
|
|
return material
|
|
|
|
|
|
func _build_current_rows(scan: SonarScan, visible_rows: int) -> void:
|
|
var mesh := _current_mesh_instance.mesh as ArrayMesh
|
|
var grid_mesh := _current_grid_instance.mesh as ArrayMesh
|
|
while _built_current_rows < visible_rows:
|
|
if _built_current_rows > 0:
|
|
_built_current_triangle_count += SonarReliefMeshBuilder.add_row_band(
|
|
mesh, scan, _built_current_rows, _current_material
|
|
)
|
|
_built_current_grid_segment_count += SonarReliefMeshBuilder.add_grid_band(
|
|
grid_mesh, scan, _built_current_rows, _grid_material
|
|
)
|
|
_built_current_rows += 1
|
|
|
|
|
|
func _rebuild_contacts(scan: SonarScan) -> void:
|
|
for child: Node in _contact_root.get_children():
|
|
_contact_root.remove_child(child)
|
|
child.queue_free()
|
|
for contact in scan.contacts:
|
|
var marker := MeshInstance3D.new()
|
|
var box := BoxMesh.new()
|
|
box.size = Vector3(4.0, 2.25, 4.0)
|
|
marker.mesh = box
|
|
marker.position = contact.center_m + Vector3.UP * 1.4
|
|
marker.visible = false
|
|
marker.set_meta("forward_distance_m", contact.center_m.z)
|
|
var material := StandardMaterial3D.new()
|
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
material.albedo_color = SonarReliefMeshBuilder.COLOR_METAL
|
|
material.emission_enabled = true
|
|
material.emission = SonarReliefMeshBuilder.COLOR_METAL
|
|
material.emission_energy_multiplier = 4.0
|
|
marker.material_override = material
|
|
_contact_root.add_child(marker)
|
|
|
|
|
|
func _update_contact_visibility(scan: SonarScan, visible_rows: int) -> void:
|
|
var maximum_visible_distance := -INF
|
|
if visible_rows > 0:
|
|
var row_ratio := float(visible_rows - 1) / float(scan.samples.rows - 1)
|
|
maximum_visible_distance = lerpf(
|
|
scan.minimum_range_m,
|
|
scan.maximum_range_m,
|
|
row_ratio
|
|
)
|
|
for child: Node in _contact_root.get_children():
|
|
if child is Node3D:
|
|
(child as Node3D).visible = (
|
|
float(child.get_meta("forward_distance_m", INF))
|
|
<= maximum_visible_distance
|
|
)
|
|
|
|
|
|
func _add_submarine_marker() -> void:
|
|
var marker := MeshInstance3D.new()
|
|
marker.name = "SubmarineMarker"
|
|
var box := BoxMesh.new()
|
|
box.size = Vector3(3.2, 1.1, 5.8)
|
|
marker.mesh = box
|
|
marker.position = Vector3(0.0, 0.0, 1.0)
|
|
var material := StandardMaterial3D.new()
|
|
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
material.albedo_color = Color.WHITE
|
|
material.emission_enabled = true
|
|
material.emission = Color.WHITE
|
|
material.emission_energy_multiplier = 2.4
|
|
marker.material_override = material
|
|
add_child(marker)
|