Validate POC A and POC B foundations
This commit is contained in:
1 parent
44fd4b4b43
commit
c38e09a663
47 files changed
+4990
-35
No files matched your search
+398
@@ -0,0 +1,398 @@
|
||||
extends Node3D
|
||||
|
||||
const SonarHudScript = preload("res://src/sonar_hud.gd")
|
||||
const SonarModelScript = preload("res://src/sonar_model.gd")
|
||||
const SonarSystemScript = preload("res://src/sonar_system.gd")
|
||||
const START_POSITION := Vector3(0.0, -25.0, 22.0)
|
||||
const METAL_CONTACT_POSITION := Vector3(0.0, -34.0, -45.0)
|
||||
|
||||
var _submarine: SubmarineController
|
||||
var _sonar: Node
|
||||
var _hud: CanvasLayer
|
||||
var _ping_audio: AudioStreamPlayer
|
||||
var _ping_audio_time_remaining := 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_ensure_input_actions()
|
||||
_build_environment()
|
||||
_build_calibration_site()
|
||||
|
||||
_hud = SonarHudScript.new()
|
||||
_hud.name = "SonarHud"
|
||||
_hud.expanded_changed.connect(_on_sonar_expanded_changed)
|
||||
add_child(_hud)
|
||||
|
||||
_submarine = SubmarineController.new()
|
||||
_submarine.name = "Submarine"
|
||||
_submarine.position = START_POSITION
|
||||
_submarine.mission_origin = START_POSITION
|
||||
_submarine.telemetry_updated.connect(_hud.update_telemetry)
|
||||
add_child(_submarine)
|
||||
|
||||
_sonar = SonarSystemScript.new()
|
||||
_sonar.name = "SonarSystem"
|
||||
_sonar.ping_started.connect(_on_ping_started)
|
||||
_sonar.ping_progress.connect(_on_ping_progress)
|
||||
_sonar.ping_completed.connect(_on_ping_completed)
|
||||
add_child(_sonar)
|
||||
_sonar.setup(_submarine, self)
|
||||
|
||||
_ping_audio = AudioStreamPlayer.new()
|
||||
_ping_audio.name = "SonarAudio"
|
||||
add_child(_ping_audio)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_hud.update_sonar_pose(_submarine.global_transform)
|
||||
if _ping_audio_time_remaining > 0.0:
|
||||
_ping_audio_time_remaining = maxf(0.0, _ping_audio_time_remaining - delta)
|
||||
if _ping_audio_time_remaining <= 0.0:
|
||||
_ping_audio.stop()
|
||||
_ping_audio.stream = null
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("sonar_ping"):
|
||||
_sonar.request_ping()
|
||||
elif event.is_action_pressed("toggle_sonar_map"):
|
||||
_hud.toggle_expanded()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif event.is_action_pressed("toggle_mouse_capture") and _hud.is_expanded():
|
||||
_hud.set_expanded(false)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _on_ping_started() -> void:
|
||||
_hud.show_ping_started(_sonar.max_range)
|
||||
_play_sonar_ping()
|
||||
|
||||
|
||||
func _on_ping_progress(
|
||||
current_cells: Array[Dictionary],
|
||||
previous_cells: Array[Dictionary],
|
||||
progress: float
|
||||
) -> void:
|
||||
_hud.show_ping_progress(
|
||||
current_cells,
|
||||
previous_cells,
|
||||
_sonar.max_range,
|
||||
progress
|
||||
)
|
||||
|
||||
|
||||
func _on_ping_completed(
|
||||
current_cells: Array[Dictionary],
|
||||
previous_cells: Array[Dictionary]
|
||||
) -> void:
|
||||
_hud.show_ping_results(current_cells, previous_cells, _sonar.max_range)
|
||||
|
||||
|
||||
func _on_sonar_expanded_changed(is_expanded: bool) -> void:
|
||||
Input.mouse_mode = (
|
||||
Input.MOUSE_MODE_VISIBLE
|
||||
if is_expanded
|
||||
else Input.MOUSE_MODE_CAPTURED
|
||||
)
|
||||
|
||||
|
||||
func _build_environment() -> void:
|
||||
var world_environment := WorldEnvironment.new()
|
||||
var environment := Environment.new()
|
||||
environment.background_mode = Environment.BG_COLOR
|
||||
environment.background_color = Color("020d13")
|
||||
environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
environment.ambient_light_color = Color("2b6870")
|
||||
environment.ambient_light_energy = 0.46
|
||||
environment.fog_enabled = true
|
||||
environment.fog_light_color = Color("19434b")
|
||||
environment.fog_light_energy = 0.46
|
||||
environment.fog_density = 0.010
|
||||
world_environment.environment = environment
|
||||
add_child(world_environment)
|
||||
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-48.0, -22.0, 0.0)
|
||||
light.light_color = Color("86c8cf")
|
||||
light.light_energy = 0.34
|
||||
add_child(light)
|
||||
_add_water_particles()
|
||||
|
||||
|
||||
func _build_calibration_site() -> void:
|
||||
_add_bathymetric_terrain()
|
||||
_add_metal_contact()
|
||||
|
||||
|
||||
func _add_bathymetric_terrain() -> void:
|
||||
var terrain := StaticBody3D.new()
|
||||
terrain.name = "ContinuousCalibrationSeafloor"
|
||||
terrain.set_meta("sonar_material", SonarModelScript.MaterialCategory.ROCK)
|
||||
terrain.set_meta("sonar_reflectivity", 0.72)
|
||||
add_child(terrain)
|
||||
|
||||
var mesh := _build_bathymetric_terrain_mesh()
|
||||
var mesh_instance := MeshInstance3D.new()
|
||||
mesh_instance.mesh = mesh
|
||||
var material := StandardMaterial3D.new()
|
||||
material.albedo_color = Color("315f5c")
|
||||
material.roughness = 0.96
|
||||
mesh_instance.material_override = material
|
||||
terrain.add_child(mesh_instance)
|
||||
|
||||
var collision := CollisionShape3D.new()
|
||||
var shape := ConcavePolygonShape3D.new()
|
||||
shape.set_faces(mesh.get_faces())
|
||||
shape.backface_collision = true
|
||||
collision.shape = shape
|
||||
terrain.add_child(collision)
|
||||
|
||||
|
||||
func _build_bathymetric_terrain_mesh() -> ArrayMesh:
|
||||
var column_count := 61
|
||||
var row_count := 69
|
||||
var terrain_width := 150.0
|
||||
var terrain_length := 170.0
|
||||
var terrain_center_z := -28.0
|
||||
var vertices: Array[Vector3] = []
|
||||
var normals: Array[Vector3] = []
|
||||
var indices: Array[int] = []
|
||||
for row in row_count:
|
||||
var z := terrain_center_z + lerpf(
|
||||
terrain_length * 0.5,
|
||||
-terrain_length * 0.5,
|
||||
float(row) / float(row_count - 1)
|
||||
)
|
||||
for column in column_count:
|
||||
var x := lerpf(
|
||||
-terrain_width * 0.5,
|
||||
terrain_width * 0.5,
|
||||
float(column) / float(column_count - 1)
|
||||
)
|
||||
vertices.append(Vector3(x, _terrain_height_at(x, z), z))
|
||||
normals.append(Vector3.ZERO)
|
||||
|
||||
for row in row_count - 1:
|
||||
for column in column_count - 1:
|
||||
var near_left := row * column_count + column
|
||||
var near_right := near_left + 1
|
||||
var far_left := (row + 1) * column_count + column
|
||||
var far_right := far_left + 1
|
||||
_append_terrain_triangle(
|
||||
near_left,
|
||||
near_right,
|
||||
far_left,
|
||||
vertices,
|
||||
normals,
|
||||
indices
|
||||
)
|
||||
_append_terrain_triangle(
|
||||
near_right,
|
||||
far_right,
|
||||
far_left,
|
||||
vertices,
|
||||
normals,
|
||||
indices
|
||||
)
|
||||
|
||||
for normal_index in normals.size():
|
||||
normals[normal_index] = normals[normal_index].normalized()
|
||||
var arrays: Array = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array(vertices)
|
||||
arrays[Mesh.ARRAY_NORMAL] = PackedVector3Array(normals)
|
||||
arrays[Mesh.ARRAY_INDEX] = PackedInt32Array(indices)
|
||||
var mesh := ArrayMesh.new()
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||
return mesh
|
||||
|
||||
|
||||
func _append_terrain_triangle(
|
||||
first: int,
|
||||
second: int,
|
||||
third: int,
|
||||
vertices: Array[Vector3],
|
||||
normals: Array[Vector3],
|
||||
indices: Array[int]
|
||||
) -> void:
|
||||
var normal := (vertices[second] - vertices[first]).cross(
|
||||
vertices[third] - vertices[first]
|
||||
).normalized()
|
||||
if normal.y < 0.0:
|
||||
var swapped := second
|
||||
second = third
|
||||
third = swapped
|
||||
normal = -normal
|
||||
indices.append_array([first, second, third])
|
||||
normals[first] += normal
|
||||
normals[second] += normal
|
||||
normals[third] += normal
|
||||
|
||||
|
||||
func _terrain_height_at(x: float, z: float) -> float:
|
||||
var base := -39.4 + (z + 12.0) * 0.018
|
||||
base += sin(x * 0.11) * 0.28 + cos(z * 0.085) * 0.24
|
||||
var height := base
|
||||
height += _gaussian_height(x, z, -12.0, -8.0, 9.2, 6.0, 8.0)
|
||||
height += _gaussian_height(x, z, 14.0, -15.0, 8.2, 6.5, 8.5)
|
||||
height += _gaussian_height(x, z, -10.0, -42.0, 16.0, 7.5, 14.0)
|
||||
height += _gaussian_height(x, z, 10.0, -42.0, 16.0, 7.5, 14.0)
|
||||
height += _gaussian_height(x, z, 0.0, -53.0, 12.0, 15.0, 7.5)
|
||||
# This narrow negative ridge cuts a readable entrance into the mound. It is
|
||||
# intentionally a bathymetric approximation rather than an impossible
|
||||
# overhang in a height field.
|
||||
height -= _gaussian_height(x, z, 0.0, -41.0, 19.0, 4.2, 16.0)
|
||||
return maxf(height, base - 1.0)
|
||||
|
||||
|
||||
func _gaussian_height(
|
||||
x: float,
|
||||
z: float,
|
||||
center_x: float,
|
||||
center_z: float,
|
||||
amplitude: float,
|
||||
width_x: float,
|
||||
width_z: float
|
||||
) -> float:
|
||||
var normalized_x := (x - center_x) / maxf(width_x, 0.001)
|
||||
var normalized_z := (z - center_z) / maxf(width_z, 0.001)
|
||||
return amplitude * exp(-0.5 * (normalized_x * normalized_x + normalized_z * normalized_z))
|
||||
|
||||
|
||||
func _add_rock_box(
|
||||
node_name: String,
|
||||
box_position: Vector3,
|
||||
box_size: Vector3,
|
||||
box_rotation_degrees: Vector3 = Vector3.ZERO
|
||||
) -> StaticBody3D:
|
||||
var body := StaticBody3D.new()
|
||||
body.name = node_name
|
||||
body.position = box_position
|
||||
body.rotation_degrees = box_rotation_degrees
|
||||
body.set_meta("sonar_material", SonarModelScript.MaterialCategory.ROCK)
|
||||
body.set_meta("sonar_reflectivity", 0.68)
|
||||
add_child(body)
|
||||
|
||||
var mesh_instance := MeshInstance3D.new()
|
||||
var mesh := BoxMesh.new()
|
||||
mesh.size = box_size
|
||||
mesh_instance.mesh = mesh
|
||||
var material := StandardMaterial3D.new()
|
||||
material.albedo_color = Color("315b59")
|
||||
material.roughness = 0.92
|
||||
mesh_instance.material_override = material
|
||||
body.add_child(mesh_instance)
|
||||
|
||||
var collision := CollisionShape3D.new()
|
||||
var shape := BoxShape3D.new()
|
||||
shape.size = box_size
|
||||
collision.shape = shape
|
||||
body.add_child(collision)
|
||||
return body
|
||||
|
||||
|
||||
func _add_metal_contact() -> void:
|
||||
var contact := StaticBody3D.new()
|
||||
contact.name = "MetalCalibrationObject"
|
||||
contact.position = Vector3(
|
||||
METAL_CONTACT_POSITION.x,
|
||||
_terrain_height_at(METAL_CONTACT_POSITION.x, METAL_CONTACT_POSITION.z) + 2.5,
|
||||
METAL_CONTACT_POSITION.z
|
||||
)
|
||||
contact.rotation_degrees.y = 16.0
|
||||
contact.set_meta("sonar_material", SonarModelScript.MaterialCategory.METAL)
|
||||
contact.set_meta("sonar_reflectivity", 1.0)
|
||||
add_child(contact)
|
||||
|
||||
var mesh_instance := MeshInstance3D.new()
|
||||
var mesh := BoxMesh.new()
|
||||
mesh.size = Vector3(8.0, 5.0, 7.0)
|
||||
mesh_instance.mesh = mesh
|
||||
var material := StandardMaterial3D.new()
|
||||
material.albedo_color = Color("855a35")
|
||||
material.metallic = 0.88
|
||||
material.roughness = 0.30
|
||||
mesh_instance.material_override = material
|
||||
contact.add_child(mesh_instance)
|
||||
|
||||
var collision := CollisionShape3D.new()
|
||||
var shape := BoxShape3D.new()
|
||||
shape.size = Vector3(8.0, 5.0, 7.0)
|
||||
collision.shape = shape
|
||||
contact.add_child(collision)
|
||||
|
||||
|
||||
func _add_water_particles() -> void:
|
||||
var particles := GPUParticles3D.new()
|
||||
particles.name = "CalibrationWaterParticles"
|
||||
particles.position = Vector3(0.0, -28.0, -15.0)
|
||||
particles.amount = 280
|
||||
particles.lifetime = 13.0
|
||||
particles.preprocess = 13.0
|
||||
particles.visibility_aabb = AABB(
|
||||
Vector3(-55.0, -24.0, -65.0),
|
||||
Vector3(110.0, 48.0, 130.0)
|
||||
)
|
||||
var motion := ParticleProcessMaterial.new()
|
||||
motion.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
|
||||
motion.emission_box_extents = Vector3(50.0, 20.0, 58.0)
|
||||
motion.direction = Vector3(0.2, 0.08, 0.05)
|
||||
motion.spread = 45.0
|
||||
motion.initial_velocity_min = 0.02
|
||||
motion.initial_velocity_max = 0.09
|
||||
motion.gravity = Vector3.ZERO
|
||||
motion.scale_min = 0.035
|
||||
motion.scale_max = 0.10
|
||||
particles.process_material = motion
|
||||
var quad := QuadMesh.new()
|
||||
quad.size = Vector2(0.07, 0.07)
|
||||
var material := StandardMaterial3D.new()
|
||||
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
material.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
|
||||
material.albedo_color = Color(0.58, 0.86, 0.90, 0.35)
|
||||
quad.material = material
|
||||
particles.draw_pass_1 = quad
|
||||
add_child(particles)
|
||||
|
||||
|
||||
func _play_sonar_ping() -> void:
|
||||
var generator := AudioStreamGenerator.new()
|
||||
generator.mix_rate = 22050.0
|
||||
generator.buffer_length = 0.5
|
||||
_ping_audio.stream = generator
|
||||
_ping_audio.play()
|
||||
var playback := _ping_audio.get_stream_playback() as AudioStreamGeneratorPlayback
|
||||
if playback == null:
|
||||
return
|
||||
var duration := 0.42
|
||||
_ping_audio_time_remaining = duration + 0.08
|
||||
var frame_count := int(generator.mix_rate * duration)
|
||||
for frame_index in frame_count:
|
||||
var time := float(frame_index) / generator.mix_rate
|
||||
var frequency := lerpf(1180.0, 640.0, time / duration)
|
||||
var envelope := exp(-time * 7.5) * minf(time * 80.0, 1.0)
|
||||
var sample := sin(TAU * frequency * time) * envelope * 0.22
|
||||
playback.push_frame(Vector2(sample, sample))
|
||||
|
||||
|
||||
func _ensure_input_actions() -> void:
|
||||
_register_key_action("move_forward", KEY_W)
|
||||
_register_key_action("move_backward", KEY_S)
|
||||
_register_key_action("move_left", KEY_A)
|
||||
_register_key_action("move_right", KEY_D)
|
||||
_register_key_action("ascend", KEY_SPACE)
|
||||
_register_key_action("descend", KEY_CTRL)
|
||||
_register_key_action("boost", KEY_SHIFT)
|
||||
_register_key_action("abandon_vehicle", KEY_X)
|
||||
_register_key_action("toggle_mouse_capture", KEY_ESCAPE)
|
||||
_register_key_action("sonar_ping", KEY_F)
|
||||
_register_key_action("toggle_sonar_map", KEY_TAB)
|
||||
|
||||
|
||||
func _register_key_action(action_name: StringName, physical_keycode: Key) -> void:
|
||||
if not InputMap.has_action(action_name):
|
||||
InputMap.add_action(action_name)
|
||||
if InputMap.action_get_events(action_name).is_empty():
|
||||
var key_event := InputEventKey.new()
|
||||
key_event.physical_keycode = physical_keycode
|
||||
InputMap.action_add_event(action_name, key_event)
|
||||
Reference in new issue
Block a user