Quality Gate / Quality Gate (pull_request) Successful in 9m4s
Instance twelve Blender-scripted glTF models in the cockpit scene and rebuild the cabin around them: gauge panel, sonar, switch panel, alert box, speaker, porthole, hull wall, caged lamps, gas bottle, handrails and side consoles. Presentation only; no domain, application or world rule changes beyond enabling glow in the test environment. - Needles, alert lamps and sonar screen are driven by the existing snapshots; the alert box now carries SYS/ATTN/ALRM. - Depth dial reads percent of certified depth. - Sonar display restyled as a turquoise holographic relief with a shader grid and height scale; SubViewport matches the glass (960x604). - Cabin lighting retuned: warm pooled lamp light, cool porthole rim, lamp shadows, dial backlight. - Procedural stand-ins removed (bulkhead, tanks, consoles, levers, indicator columns, placeholder pipe run, dial face PNGs). - Tests adapted where nodes disappeared; one assertion removed (pipe_segment_count) until the pipe kit is placed. - Model .glb files are committed without LFS; textures Godot extracts next to them at import are ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
2.7 KiB
GDScript
86 lines
2.7 KiB
GDScript
class_name SonarReliefMeshBuilder
|
|
extends RefCounted
|
|
|
|
const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd")
|
|
const SonarSample = preload("res://game/domain/sonar/sonar_sample.gd")
|
|
const SonarScan = preload("res://game/domain/sonar/sonar_scan.gd")
|
|
|
|
## Grid lines are drawn by the relief shader on the cell edges: every built
|
|
## cell carries one row edge and one column edge of the grid.
|
|
const GRID_SEGMENTS_PER_CELL := 2
|
|
## Two triangles per cell, wound like the sample grid.
|
|
const CELL_CORNER_ORDER: Array[int] = [0, 2, 1, 1, 2, 3]
|
|
|
|
|
|
static func build_full_mesh(scan: SonarScan, material: Material) -> ArrayMesh:
|
|
var mesh := ArrayMesh.new()
|
|
for row in range(1, scan.samples.rows):
|
|
add_row_band(mesh, scan, row, material)
|
|
return mesh
|
|
|
|
|
|
## Adds the cells between `row - 1` and `row`; returns the triangle count.
|
|
## Per vertex: UV = (column, row) of the sample grid, COLOR.r = height relative
|
|
## to the scan (0 low, 1 peak), COLOR.g = 1 on metal, COLOR.b = echo intensity.
|
|
static func add_row_band(
|
|
mesh: ArrayMesh,
|
|
scan: SonarScan,
|
|
row: int,
|
|
material: Material
|
|
) -> int:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var colors := PackedColorArray()
|
|
var uvs := PackedVector2Array()
|
|
for column in range(scan.samples.columns - 1):
|
|
var corners: Array[SonarSample] = [
|
|
scan.samples.sample_at(row - 1, column),
|
|
scan.samples.sample_at(row - 1, column + 1),
|
|
scan.samples.sample_at(row, column),
|
|
scan.samples.sample_at(row, column + 1),
|
|
]
|
|
if not (
|
|
corners[0].observed
|
|
and corners[1].observed
|
|
and corners[2].observed
|
|
and corners[3].observed
|
|
):
|
|
continue
|
|
var grid: Array[Vector2] = [
|
|
Vector2(column, row - 1),
|
|
Vector2(column + 1, row - 1),
|
|
Vector2(column, row),
|
|
Vector2(column + 1, row),
|
|
]
|
|
for corner: int in CELL_CORNER_ORDER:
|
|
var sample := corners[corner]
|
|
vertices.append(sample.position_m)
|
|
normals.append(sample.normal)
|
|
colors.append(_sample_data(scan, sample))
|
|
uvs.append(grid[corner])
|
|
if vertices.is_empty():
|
|
return 0
|
|
var arrays: Array = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_NORMAL] = normals
|
|
arrays[Mesh.ARRAY_COLOR] = colors
|
|
arrays[Mesh.ARRAY_TEX_UV] = uvs
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
mesh.surface_set_material(mesh.get_surface_count() - 1, material)
|
|
return vertices.size() / 3
|
|
|
|
|
|
static func _sample_data(scan: SonarScan, sample: SonarSample) -> Color:
|
|
var height := clampf(
|
|
inverse_lerp(
|
|
scan.minimum_elevation_m,
|
|
scan.maximum_elevation_m,
|
|
sample.elevation_m
|
|
),
|
|
0.0,
|
|
1.0
|
|
)
|
|
var metal := 1.0 if sample.material == SonarMaterial.Value.METAL else 0.0
|
|
return Color(height, metal, clampf(sample.intensity, 0.0, 1.0))
|