feat(cockpit): replace procedural cockpit with twelve scripted models
Quality Gate / Quality Gate (pull_request) Successful in 9m4s
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>
This commit is contained in:
1 parent
912123087f
commit
2f9b7c0894
45 files changed
+1353
-1603
No files matched your search
@@ -1,37 +1,54 @@
|
||||
# Cockpit texture provenance
|
||||
# Cockpit asset provenance
|
||||
|
||||
These textures were generated specifically for Ransom's Wake on 3 October 2026
|
||||
## Textures
|
||||
|
||||
The texture below was generated specifically for Ransom's Wake on 3 October 2026
|
||||
with OpenAI's built-in image generation tool. No third-party image was used as
|
||||
an input or reference.
|
||||
|
||||
## `naval_painted_steel_albedo.png`
|
||||
### `naval_painted_steel_albedo.png`
|
||||
|
||||
Prompt summary: seamless, orthographic PBR-style albedo of dark blue-green
|
||||
naval painted steel, with restrained salt-air oxidation, micro-scratches,
|
||||
paint chips and grime; neutral illumination; no borders, rivets, text or
|
||||
watermark.
|
||||
|
||||
## `analog_gauge_face_albedo.png`
|
||||
The source output was resized to 1024 × 1024 and stripped of metadata for
|
||||
runtime use. The original generated output remains in the local Codex image
|
||||
generation store and is not required by the game.
|
||||
|
||||
Prompt summary: orthographic vintage naval gauge face in aged ivory enamel,
|
||||
with fine crazing, soot, speckling and a faint printed arc; no bezel, needle,
|
||||
hub, glass, text, numbers, logo or watermark.
|
||||
The generated gauge faces (`analog_gauge_face_albedo.png` and the four
|
||||
`*_gauge_face_albedo_v2.png`) were removed on 6 October 2026: the dials are now
|
||||
printed on the gauge panel model below.
|
||||
|
||||
Both source outputs were resized to 1024 × 1024 and stripped of metadata for
|
||||
runtime use. The original generated outputs remain in the local Codex image
|
||||
generation store and are not required by the game.
|
||||
## Models
|
||||
|
||||
## Dedicated instrument faces
|
||||
`models/gauge_panel.glb`, `models/sonar.glb`, `models/switch_panel.glb`,
|
||||
`models/alert_box.glb`, `models/speaker.glb`, `models/porthole.glb`,
|
||||
`models/hull_wall.glb`, `models/cage_lamp.glb`, `models/gas_bottle.glb`,
|
||||
`models/handrail_l.glb`, `models/handrail_r.glb` and `models/side_console.glb`
|
||||
were produced for Ransom's Wake
|
||||
by Blender scripts written for the project, from concept sheets generated for
|
||||
the project. No third-party mesh, texture or scan was used. Their textures
|
||||
(base colour, ORM, normal and, where relevant, emission) are embedded in the
|
||||
`.glb` files: 2048² for the gauge panel, sonar and switch panel, a 4096 × 1024
|
||||
strip for the four printed dials, 2048² for the porthole and hull wall, 1024²
|
||||
for the other props.
|
||||
|
||||
`oxygen_gauge_face_albedo_v2.png`, `battery_gauge_face_albedo_v2.png`,
|
||||
`hull_gauge_face_albedo_v2.png` and `depth_gauge_face_albedo_v2.png` were
|
||||
generated on 4 October 2026 with the same built-in tool. The approved cockpit
|
||||
target and then the oxygen face were used only as project-owned style
|
||||
references.
|
||||
Dial and label lettering is rendered into the textures with Arial and Impact,
|
||||
which are Windows system fonts. **Their licence must be checked before the game
|
||||
is distributed.**
|
||||
|
||||
Prompt summary: orthographic mechanical submarine gauges in aged ivory enamel,
|
||||
with exact French labels and units, dense numerical scales, restrained patina,
|
||||
and green, amber and red operating sectors. The generated needles, bezels and
|
||||
glass were explicitly excluded because those parts are modeled and animated in
|
||||
Godot. The source outputs were resized to 1024 × 1024 and given a circular alpha
|
||||
mask for flat, distortion-free mounting inside the physical bezels.
|
||||
The Blender sources are not in this repository. A model is changed by editing
|
||||
its generating script and re-exporting, never by editing the import in Godot.
|
||||
The cockpit scene references the `.glb` files directly, so a replaced file
|
||||
updates the cockpit without touching the scene.
|
||||
|
||||
With Godot's default import settings the embedded textures are extracted as
|
||||
`models/<model>_<texture>.png` next to each `.glb`. Those PNG files are import
|
||||
by-products, regenerated from the `.glb`, and are not sources.
|
||||
|
||||
As of 6 October 2026 the `.glb` files are deliberately **not committed**
|
||||
(their textures are still being balanced, and the repository uses plain Git
|
||||
without LFS). Until they are, `cockpit_interior.tscn` only loads on a machine
|
||||
that has them in `models/`.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,62 @@
|
||||
class_name CockpitAlertBox
|
||||
extends Node3D
|
||||
|
||||
## Drives the three lamps of the imported alert box model. Exactly one lamp is
|
||||
## lit for the alert level carried by the dive snapshot; the lamps are also
|
||||
## distinguished by position and by their engraved ALERTE / ATTENTION / DANGER
|
||||
## plates, so the reading does not depend on colour alone.
|
||||
|
||||
const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd")
|
||||
const CockpitMeshFactory = preload("res://game/presentation/cockpit/cockpit_mesh_factory.gd")
|
||||
|
||||
const LAMP_NAMES: Array[String] = ["Lamp_Alerte", "Lamp_Attention", "Lamp_Danger"]
|
||||
const LIT_ENERGY := 4.0
|
||||
const UNLIT_ENERGY := 0.0
|
||||
|
||||
var _lamp_materials: Array[BaseMaterial3D] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for lamp_name: String in LAMP_NAMES:
|
||||
var lamp := find_child(lamp_name, true, false) as MeshInstance3D
|
||||
var material := CockpitMeshFactory.own_surface_material(lamp)
|
||||
if material == null:
|
||||
push_error("Alert box model has no usable %s lamp" % lamp_name)
|
||||
continue
|
||||
material.emission_energy_multiplier = UNLIT_ENERGY
|
||||
_lamp_materials.append(material)
|
||||
|
||||
|
||||
func present(alert_level: DiveAlertLevel.Value) -> void:
|
||||
var active_index := _lamp_index_for(alert_level)
|
||||
for lamp_index in _lamp_materials.size():
|
||||
_lamp_materials[lamp_index].emission_energy_multiplier = (
|
||||
LIT_ENERGY if lamp_index == active_index else UNLIT_ENERGY
|
||||
)
|
||||
|
||||
|
||||
func indicator_count() -> int:
|
||||
return _lamp_materials.size()
|
||||
|
||||
|
||||
func active_indicator_count() -> int:
|
||||
var count := 0
|
||||
for material: BaseMaterial3D in _lamp_materials:
|
||||
if material.emission_energy_multiplier > UNLIT_ENERGY:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func active_indicator_name() -> String:
|
||||
for lamp_index in _lamp_materials.size():
|
||||
if _lamp_materials[lamp_index].emission_energy_multiplier > UNLIT_ENERGY:
|
||||
return LAMP_NAMES[lamp_index]
|
||||
return ""
|
||||
|
||||
|
||||
func _lamp_index_for(alert_level: DiveAlertLevel.Value) -> int:
|
||||
if alert_level >= DiveAlertLevel.Value.CRITICAL:
|
||||
return 2
|
||||
if alert_level >= DiveAlertLevel.Value.ATTENTION:
|
||||
return 1
|
||||
return 0
|
||||
@@ -1,265 +1,101 @@
|
||||
class_name CockpitAnalogGauges
|
||||
extends Node3D
|
||||
|
||||
const CockpitMaterialPalette = preload("res://game/presentation/cockpit/cockpit_material_palette.gd")
|
||||
## Drives the imported gauge panel model: four needles and its six pilot lights.
|
||||
## The model scene is instanced by `cockpit_interior.tscn`; this script only
|
||||
## rotates needles and sets emission, it never rebuilds geometry.
|
||||
|
||||
const CockpitMeshFactory = preload("res://game/presentation/cockpit/cockpit_mesh_factory.gd")
|
||||
const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd")
|
||||
|
||||
var _materials: CockpitMaterialPalette
|
||||
var _oxygen_needle: Node3D
|
||||
var _battery_needle: Node3D
|
||||
var _hull_needle: Node3D
|
||||
var _depth_needle: Node3D
|
||||
var _indicator_materials: Array[StandardMaterial3D] = []
|
||||
var _active_indicator_count := 0
|
||||
## Printed dial sweep: ratio 0 points lower-left, ratio 1 lower-right, clockwise.
|
||||
const NEEDLE_DEGREES_AT_ZERO := 120.0
|
||||
const NEEDLE_DEGREES_AT_FULL := -120.0
|
||||
## The snapshot carries no per-instrument lamp state, so the panel pilot lights
|
||||
## stay dark; the alert level is reported by `CockpitAlertBox`.
|
||||
const PILOT_LIGHT_REST_ENERGY := 0.0
|
||||
## Scales the dial backlight baked in the model (1.0 = as exported), so the
|
||||
## paper reads as lit ivory without outshining the cabin's warm lamps.
|
||||
const DIAL_BACKLIGHT_ENERGY := 0.7
|
||||
|
||||
const NEEDLE_NAMES: Array[String] = [
|
||||
"Needle_Oxygen",
|
||||
"Needle_Battery",
|
||||
"Needle_Hull",
|
||||
"Needle_Depth",
|
||||
]
|
||||
|
||||
var _needles: Dictionary[String, Node3D] = {}
|
||||
var _dials: MeshInstance3D
|
||||
var _pilot_light_materials: Array[BaseMaterial3D] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for needle_name: String in NEEDLE_NAMES:
|
||||
var needle := find_child(needle_name, true, false) as Node3D
|
||||
if needle == null:
|
||||
push_error("Gauge panel model has no %s node" % needle_name)
|
||||
continue
|
||||
_needles[needle_name] = needle
|
||||
_dials = find_child("GaugeDials", true, false) as MeshInstance3D
|
||||
var dial_material := CockpitMeshFactory.own_surface_material(_dials)
|
||||
if dial_material != null:
|
||||
dial_material.emission_energy_multiplier = DIAL_BACKLIGHT_ENERGY
|
||||
for light: Node in find_children("Light_*", "MeshInstance3D", true, false):
|
||||
var material := CockpitMeshFactory.own_surface_material(light as MeshInstance3D)
|
||||
if material != null:
|
||||
material.emission_energy_multiplier = PILOT_LIGHT_REST_ENERGY
|
||||
_pilot_light_materials.append(material)
|
||||
|
||||
func _init(materials: CockpitMaterialPalette) -> void:
|
||||
_materials = materials
|
||||
name = "AnalogGauges"
|
||||
_build_instrument_cluster()
|
||||
_build_indicator_bank()
|
||||
_oxygen_needle = _add_gauge(
|
||||
"OxygenGauge",
|
||||
Vector3(-0.52, 0.12, 0.145),
|
||||
0.94,
|
||||
_materials.oxygen_gauge_face
|
||||
)
|
||||
_battery_needle = _add_gauge(
|
||||
"BatteryGauge",
|
||||
Vector3(-0.22, 0.12, 0.145),
|
||||
0.94,
|
||||
_materials.battery_gauge_face
|
||||
)
|
||||
_hull_needle = _add_gauge(
|
||||
"HullGauge",
|
||||
Vector3(0.09, 0.12, 0.145),
|
||||
0.86,
|
||||
_materials.hull_gauge_face
|
||||
)
|
||||
_depth_needle = _add_gauge(
|
||||
"DepthGauge",
|
||||
Vector3(0.47, 0.12, 0.145),
|
||||
1.12,
|
||||
_materials.depth_gauge_face
|
||||
)
|
||||
|
||||
func present(
|
||||
oxygen_ratio: float,
|
||||
battery_ratio: float,
|
||||
hull_ratio: float,
|
||||
depth_ratio: float,
|
||||
alert_level: DiveAlertLevel.Value
|
||||
depth_ratio: float
|
||||
) -> void:
|
||||
_set_needle_ratio(_oxygen_needle, oxygen_ratio)
|
||||
_set_needle_ratio(_battery_needle, battery_ratio)
|
||||
_set_needle_ratio(_hull_needle, hull_ratio)
|
||||
_set_needle_ratio(_depth_needle, depth_ratio)
|
||||
_update_indicator_bank(alert_level)
|
||||
_set_needle_ratio("Needle_Oxygen", oxygen_ratio)
|
||||
_set_needle_ratio("Needle_Battery", battery_ratio)
|
||||
_set_needle_ratio("Needle_Hull", hull_ratio)
|
||||
_set_needle_ratio("Needle_Depth", depth_ratio)
|
||||
|
||||
|
||||
func gauge_count() -> int:
|
||||
return _needles.size()
|
||||
|
||||
|
||||
func pilot_light_count() -> int:
|
||||
return _pilot_light_materials.size()
|
||||
|
||||
|
||||
func lit_pilot_light_count() -> int:
|
||||
var count := 0
|
||||
for material: BaseMaterial3D in _pilot_light_materials:
|
||||
if material.emission_energy_multiplier > 0.0:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func needle_degrees(needle_name: String) -> float:
|
||||
var needle: Node3D = _needles.get(needle_name)
|
||||
return 0.0 if needle == null else needle.rotation_degrees.z
|
||||
|
||||
|
||||
func battery_needle_degrees() -> float:
|
||||
return 0.0 if _battery_needle == null else _battery_needle.rotation_degrees.z
|
||||
|
||||
func indicator_count() -> int:
|
||||
return _indicator_materials.size()
|
||||
|
||||
func gauge_count() -> int:
|
||||
return 4
|
||||
|
||||
func active_indicator_count() -> int:
|
||||
return _active_indicator_count
|
||||
return needle_degrees("Needle_Battery")
|
||||
|
||||
|
||||
func _build_instrument_cluster() -> void:
|
||||
CockpitMeshFactory.add_box(
|
||||
"InstrumentClusterHousing",
|
||||
self,
|
||||
Vector3(-0.04, 0.105, 0.095),
|
||||
Vector3(1.42, 0.31, 0.105),
|
||||
_materials.overhead_steel
|
||||
)
|
||||
CockpitMeshFactory.add_box(
|
||||
"InstrumentClusterFoot",
|
||||
self,
|
||||
Vector3(-0.04, -0.025, 0.075),
|
||||
Vector3(1.48, 0.040, 0.14),
|
||||
_materials.dark_steel
|
||||
)
|
||||
|
||||
func _build_indicator_bank() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "IndicatorBank"
|
||||
add_child(root)
|
||||
var colors: Array[Color] = [
|
||||
Color(0.08, 0.72, 0.38),
|
||||
Color(1.0, 0.46, 0.035),
|
||||
Color(0.92, 0.035, 0.018),
|
||||
]
|
||||
var captions: Array[String] = ["SYS", "ATTN", "ALRM"]
|
||||
for indicator_index in colors.size():
|
||||
var y := 0.165 - indicator_index * 0.050
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"%sBezel" % captions[indicator_index],
|
||||
root,
|
||||
Vector3(0.68, y, 0.156),
|
||||
0.016,
|
||||
0.012,
|
||||
_materials.brass,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
var material := _indicator_material(colors[indicator_index])
|
||||
_indicator_materials.append(material)
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"%sLens" % captions[indicator_index],
|
||||
root,
|
||||
Vector3(0.68, y, 0.165),
|
||||
0.009,
|
||||
0.008,
|
||||
material,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
var label := Label3D.new()
|
||||
label.name = "%sLabel" % captions[indicator_index]
|
||||
label.text = captions[indicator_index]
|
||||
label.font_size = 8
|
||||
label.pixel_size = 0.00042
|
||||
label.modulate = Color(0.70, 0.67, 0.56)
|
||||
label.position = Vector3(0.71, y, 0.168)
|
||||
root.add_child(label)
|
||||
|
||||
func _indicator_material(color: Color) -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.albedo_color = color * 0.12
|
||||
material.metallic = 0.05
|
||||
material.roughness = 0.16
|
||||
material.emission_enabled = true
|
||||
material.emission = color
|
||||
material.emission_energy_multiplier = 0.04
|
||||
return material
|
||||
|
||||
func _update_indicator_bank(alert_level: DiveAlertLevel.Value) -> void:
|
||||
var active_index := 0
|
||||
if alert_level >= DiveAlertLevel.Value.CRITICAL:
|
||||
active_index = 2
|
||||
elif alert_level >= DiveAlertLevel.Value.ATTENTION:
|
||||
active_index = 1
|
||||
for indicator_index in _indicator_materials.size():
|
||||
_indicator_materials[indicator_index].emission_energy_multiplier = (
|
||||
2.8 if indicator_index == active_index else 0.04
|
||||
)
|
||||
_active_indicator_count = 1
|
||||
func dial_texture_is_loaded() -> bool:
|
||||
if _dials == null or _dials.mesh == null or _dials.mesh.get_surface_count() == 0:
|
||||
return false
|
||||
var material := _dials.mesh.surface_get_material(0) as BaseMaterial3D
|
||||
return material != null and material.albedo_texture != null
|
||||
|
||||
|
||||
func _add_gauge(
|
||||
gauge_name: String,
|
||||
position: Vector3,
|
||||
gauge_scale: float,
|
||||
face_material: Material
|
||||
) -> Node3D:
|
||||
var gauge := Node3D.new()
|
||||
gauge.name = gauge_name
|
||||
gauge.position = position
|
||||
gauge.scale = Vector3(gauge_scale, gauge_scale, gauge_scale)
|
||||
add_child(gauge)
|
||||
CockpitMeshFactory.add_box(
|
||||
"MountingPlate",
|
||||
gauge,
|
||||
Vector3(0.0, 0.0, -0.025),
|
||||
Vector3(0.225, 0.225, 0.055),
|
||||
_materials.overhead_steel
|
||||
)
|
||||
for screw_position: Vector2 in [
|
||||
Vector2(-0.086, -0.086),
|
||||
Vector2(0.086, -0.086),
|
||||
Vector2(-0.086, 0.086),
|
||||
Vector2(0.086, 0.086),
|
||||
]:
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"MountingScrew",
|
||||
gauge,
|
||||
Vector3(screw_position.x, screw_position.y, 0.008),
|
||||
0.009,
|
||||
0.012,
|
||||
_materials.brass,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
_add_bezel(gauge, 0.108, 0.046, _materials.dark_steel)
|
||||
_add_bezel(gauge, 0.099, 0.041, _materials.brushed_steel)
|
||||
var face := MeshInstance3D.new()
|
||||
var printed_face := QuadMesh.new()
|
||||
printed_face.size = Vector2(0.178, 0.178)
|
||||
face.mesh = printed_face
|
||||
face.position.z = 0.031
|
||||
face.material_override = face_material
|
||||
gauge.add_child(face)
|
||||
var needle_root := Node3D.new()
|
||||
needle_root.name = "Needle"
|
||||
needle_root.position.z = 0.038
|
||||
gauge.add_child(needle_root)
|
||||
CockpitMeshFactory.add_box(
|
||||
"NeedleBlade",
|
||||
needle_root,
|
||||
Vector3(0.0, 0.030, 0.0),
|
||||
Vector3(0.006, 0.060, 0.006),
|
||||
_materials.dark_steel
|
||||
)
|
||||
CockpitMeshFactory.add_box(
|
||||
"NeedleTip",
|
||||
needle_root,
|
||||
Vector3(0.0, 0.061, 0.001),
|
||||
Vector3(0.004, 0.024, 0.006),
|
||||
_materials.needle_paint
|
||||
)
|
||||
CockpitMeshFactory.add_box(
|
||||
"NeedleCounterweight",
|
||||
needle_root,
|
||||
Vector3(0.0, -0.014, -0.001),
|
||||
Vector3(0.013, 0.030, 0.007),
|
||||
_materials.dark_steel
|
||||
)
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"NeedleHub",
|
||||
needle_root,
|
||||
Vector3.ZERO,
|
||||
0.018,
|
||||
0.012,
|
||||
_materials.dark_steel,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
var glass := MeshInstance3D.new()
|
||||
glass.name = "GaugeGlass"
|
||||
var glass_mesh := CylinderMesh.new()
|
||||
glass_mesh.top_radius = 0.089
|
||||
glass_mesh.bottom_radius = 0.089
|
||||
glass_mesh.height = 0.004
|
||||
glass_mesh.radial_segments = 40
|
||||
glass.mesh = glass_mesh
|
||||
glass.material_override = _materials.gauge_glass
|
||||
glass.rotation_degrees.x = 90.0
|
||||
glass.position.z = 0.050
|
||||
glass.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
gauge.add_child(glass)
|
||||
return needle_root
|
||||
|
||||
|
||||
func _add_bezel(
|
||||
gauge: Node3D,
|
||||
radius: float,
|
||||
height: float,
|
||||
material: Material
|
||||
) -> void:
|
||||
var bezel := MeshInstance3D.new()
|
||||
var bezel_mesh := CylinderMesh.new()
|
||||
bezel_mesh.top_radius = radius
|
||||
bezel_mesh.bottom_radius = radius
|
||||
bezel_mesh.height = height
|
||||
bezel_mesh.radial_segments = 48
|
||||
bezel.mesh = bezel_mesh
|
||||
bezel.material_override = material
|
||||
bezel.rotation_degrees.x = 90.0
|
||||
gauge.add_child(bezel)
|
||||
|
||||
|
||||
func _set_needle_ratio(needle: Node3D, ratio: float) -> void:
|
||||
func _set_needle_ratio(needle_name: String, ratio: float) -> void:
|
||||
var needle: Node3D = _needles.get(needle_name)
|
||||
if needle != null:
|
||||
needle.rotation_degrees.z = lerpf(125.0, -125.0, clampf(ratio, 0.0, 1.0))
|
||||
needle.rotation_degrees.z = lerpf(
|
||||
NEEDLE_DEGREES_AT_ZERO,
|
||||
NEEDLE_DEGREES_AT_FULL,
|
||||
clampf(ratio, 0.0, 1.0)
|
||||
)
|
||||
@@ -1,26 +1,12 @@
|
||||
class_name CockpitDashboardDetailing
|
||||
extends Node3D
|
||||
|
||||
const CockpitMaterialPalette = preload(
|
||||
"res://game/presentation/cockpit/cockpit_material_palette.gd"
|
||||
)
|
||||
const CockpitMeshFactory = preload(
|
||||
"res://game/presentation/cockpit/cockpit_mesh_factory.gd"
|
||||
)
|
||||
|
||||
var _materials: CockpitMaterialPalette
|
||||
var _control_count := 0
|
||||
## The dashboard keeps its hidden digital readouts (their labels still carry the
|
||||
## exact values); this hides the placeholder parts the gauge panel replaced.
|
||||
|
||||
|
||||
func _init(materials: CockpitMaterialPalette) -> void:
|
||||
_materials = materials
|
||||
func _init() -> void:
|
||||
name = "DashboardDetailing"
|
||||
_build_structural_frame()
|
||||
_build_switch_banks()
|
||||
|
||||
|
||||
func control_count() -> int:
|
||||
return _control_count
|
||||
|
||||
|
||||
func hide_obsolete_elements(cockpit: Node3D) -> void:
|
||||
@@ -42,79 +28,3 @@ func hide_obsolete_elements(cockpit: Node3D) -> void:
|
||||
var readout := cockpit.get_node_or_null("Dashboard/%s" % readout_name) as Node3D
|
||||
if readout != null:
|
||||
readout.visible = false
|
||||
|
||||
|
||||
func _build_structural_frame() -> void:
|
||||
CockpitMeshFactory.add_box(
|
||||
"TopRail",
|
||||
self,
|
||||
Vector3(0.0, 0.255, 0.115),
|
||||
Vector3(1.50, 0.055, 0.055),
|
||||
_materials.dark_steel
|
||||
)
|
||||
CockpitMeshFactory.add_box(
|
||||
"BottomRail",
|
||||
self,
|
||||
Vector3(0.0, -0.205, 0.115),
|
||||
Vector3(1.50, 0.050, 0.055),
|
||||
_materials.dark_steel
|
||||
)
|
||||
for x: float in [-0.755, 0.755]:
|
||||
CockpitMeshFactory.add_box(
|
||||
"SideRail",
|
||||
self,
|
||||
Vector3(x, 0.025, 0.115),
|
||||
Vector3(0.050, 0.46, 0.055),
|
||||
_materials.dark_steel
|
||||
)
|
||||
for screw_position: Vector2 in [
|
||||
Vector2(-0.72, -0.18),
|
||||
Vector2(-0.72, 0.23),
|
||||
Vector2(0.72, -0.18),
|
||||
Vector2(0.72, 0.23),
|
||||
Vector2(-0.24, -0.18),
|
||||
Vector2(0.24, -0.18),
|
||||
]:
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"PanelScrew",
|
||||
self,
|
||||
Vector3(screw_position.x, screw_position.y, 0.151),
|
||||
0.010,
|
||||
0.010,
|
||||
_materials.brass,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
|
||||
|
||||
func _build_switch_banks() -> void:
|
||||
_add_switch_bank(-0.59, "AUX")
|
||||
_add_switch_bank(0.59, "POMPES")
|
||||
|
||||
|
||||
func _add_switch_bank(center_x: float, caption: String) -> void:
|
||||
CockpitMeshFactory.add_box(
|
||||
"SwitchPlate",
|
||||
self,
|
||||
Vector3(center_x, -0.205, 0.145),
|
||||
Vector3(0.25, 0.060, 0.020),
|
||||
_materials.overhead_steel
|
||||
)
|
||||
for offset: float in [-0.075, -0.025, 0.025, 0.075]:
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"ToggleSwitch",
|
||||
self,
|
||||
Vector3(center_x + offset, -0.195, 0.171),
|
||||
0.006,
|
||||
0.030,
|
||||
_materials.dark_steel,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
_control_count += 1
|
||||
var label := Label3D.new()
|
||||
label.name = "%sLabel" % caption.to_pascal_case()
|
||||
label.text = caption
|
||||
label.font_size = 10
|
||||
label.pixel_size = 0.00055
|
||||
label.modulate = Color(0.72, 0.69, 0.56)
|
||||
label.position = Vector3(center_x, -0.235, 0.165)
|
||||
add_child(label)
|
||||
@@ -4,6 +4,7 @@ extends Node3D
|
||||
const DiveUpdate = preload("res://game/domain/dive/dive_update.gd")
|
||||
const DiveAlertLevel = preload("res://game/domain/dive/dive_alert_level.gd")
|
||||
const VehicleTelemetry = preload("res://game/application/dive/vehicle_telemetry.gd")
|
||||
const CockpitAlertBox = preload("res://game/presentation/cockpit/cockpit_alert_box.gd")
|
||||
const CockpitAnalogGauges = preload("res://game/presentation/cockpit/cockpit_analog_gauges.gd")
|
||||
const CockpitDashboardDetailing = preload(
|
||||
"res://game/presentation/cockpit/cockpit_dashboard_detailing.gd"
|
||||
@@ -19,13 +20,17 @@ const CockpitSonarConsole = preload(
|
||||
const CockpitPracticalLighting = preload(
|
||||
"res://game/presentation/cockpit/cockpit_practical_lighting.gd"
|
||||
)
|
||||
const CockpitSwitchPanel = preload(
|
||||
"res://game/presentation/cockpit/cockpit_switch_panel.gd"
|
||||
)
|
||||
|
||||
var _materials: CockpitMaterialPalette
|
||||
var _painted_steel: Material
|
||||
var _dark_steel: Material
|
||||
var _brass: Material
|
||||
var _copper: Material
|
||||
var _analog_gauges: CockpitAnalogGauges
|
||||
var _alert_box: CockpitAlertBox
|
||||
var _switch_panel: CockpitSwitchPanel
|
||||
var _gas_bottle: Node3D
|
||||
var _dashboard_detailing: CockpitDashboardDetailing
|
||||
var _side_equipment: CockpitSideEquipment
|
||||
var _sonar_console: CockpitSonarConsole
|
||||
@@ -35,9 +40,6 @@ var _practical_lighting: CockpitPracticalLighting
|
||||
func _ready() -> void:
|
||||
_create_materials()
|
||||
_apply_materials_to_existing_cockpit()
|
||||
_build_pressure_hull_ribs()
|
||||
_build_life_support_pipes()
|
||||
_build_panel_hardware()
|
||||
_build_dashboard_instrumentation()
|
||||
_build_equipment_assemblies()
|
||||
|
||||
@@ -46,32 +48,24 @@ func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void:
|
||||
if update == null or update.snapshot == null or telemetry == null:
|
||||
return
|
||||
var snapshot = update.snapshot
|
||||
_analog_gauges.present(
|
||||
snapshot.oxygen.ratio,
|
||||
snapshot.battery.ratio,
|
||||
snapshot.hull.ratio,
|
||||
clampf(
|
||||
snapshot.depth.current_m
|
||||
/ maxf(snapshot.depth.certified_m, 0.001),
|
||||
0.0,
|
||||
1.0
|
||||
),
|
||||
snapshot.alerts.maximum
|
||||
)
|
||||
var alert_level: DiveAlertLevel.Value = snapshot.alerts.maximum
|
||||
if _analog_gauges != null:
|
||||
_analog_gauges.present(
|
||||
snapshot.oxygen.ratio,
|
||||
snapshot.battery.ratio,
|
||||
snapshot.hull.ratio,
|
||||
clampf(
|
||||
snapshot.depth.current_m
|
||||
/ maxf(snapshot.depth.certified_m, 0.001),
|
||||
0.0,
|
||||
1.0
|
||||
)
|
||||
)
|
||||
if _alert_box != null:
|
||||
_alert_box.present(alert_level)
|
||||
_practical_lighting.present(alert_level)
|
||||
|
||||
|
||||
func pressure_hull_rib_count() -> int:
|
||||
var ribs := get_node_or_null("PressureHullRibs")
|
||||
return 0 if ribs == null else ribs.get_child_count()
|
||||
|
||||
|
||||
func pipe_segment_count() -> int:
|
||||
var pipes := get_node_or_null("LifeSupportPipes")
|
||||
return 0 if pipes == null else pipes.get_child_count()
|
||||
|
||||
|
||||
func battery_needle_degrees() -> float:
|
||||
return 0.0 if _analog_gauges == null else _analog_gauges.battery_needle_degrees()
|
||||
|
||||
@@ -80,25 +74,30 @@ func textures_are_loaded() -> bool:
|
||||
return (
|
||||
_materials != null
|
||||
and _materials.overhead_steel.albedo_texture != null
|
||||
and _materials.oxygen_gauge_face.albedo_texture != null
|
||||
and _materials.battery_gauge_face.albedo_texture != null
|
||||
and _materials.hull_gauge_face.albedo_texture != null
|
||||
and _materials.depth_gauge_face.albedo_texture != null
|
||||
and _analog_gauges != null
|
||||
and _analog_gauges.dial_texture_is_loaded()
|
||||
)
|
||||
|
||||
|
||||
func dashboard_control_count() -> int:
|
||||
return 0 if _dashboard_detailing == null else _dashboard_detailing.control_count()
|
||||
return 0 if _switch_panel == null else _switch_panel.control_count()
|
||||
|
||||
|
||||
func side_equipment_count() -> int:
|
||||
if _side_equipment == null:
|
||||
return 0
|
||||
return _side_equipment.tank_count() + _side_equipment.junction_box_count()
|
||||
return _side_equipment.junction_box_count() + (0 if _gas_bottle == null else 1)
|
||||
|
||||
|
||||
func side_control_count() -> int:
|
||||
return 0 if _side_equipment == null else _side_equipment.control_count()
|
||||
if _side_equipment == null:
|
||||
return 0
|
||||
var bottle_valve_count := (
|
||||
0
|
||||
if _gas_bottle == null
|
||||
else _gas_bottle.find_children("*ValveWheel", "Node3D", true, false).size()
|
||||
)
|
||||
return _side_equipment.control_count() + bottle_valve_count
|
||||
|
||||
|
||||
func sonar_control_count() -> int:
|
||||
@@ -117,8 +116,6 @@ func _create_materials() -> void:
|
||||
_materials = CockpitMaterialPalette.new()
|
||||
_painted_steel = _materials.painted_steel
|
||||
_dark_steel = _materials.dark_steel
|
||||
_brass = _materials.brass
|
||||
_copper = _materials.copper
|
||||
|
||||
|
||||
func _apply_materials_to_existing_cockpit() -> void:
|
||||
@@ -127,39 +124,12 @@ func _apply_materials_to_existing_cockpit() -> void:
|
||||
[
|
||||
"CabinShell/LeftWall",
|
||||
"CabinShell/RightWall",
|
||||
"LeftConsole",
|
||||
"RightConsole",
|
||||
]
|
||||
)
|
||||
_apply_material_to_paths(
|
||||
_dark_steel,
|
||||
[
|
||||
"CabinShell/RearBulkhead",
|
||||
"PortholeAssembly/LeftBulkhead",
|
||||
"PortholeAssembly/RightBulkhead",
|
||||
"PortholeAssembly/BottomBulkhead",
|
||||
"ThrottleLever",
|
||||
"VerticalLever",
|
||||
]
|
||||
)
|
||||
_apply_material_to_paths(_dark_steel, ["CabinShell/RearBulkhead"])
|
||||
_apply_material_to_paths(
|
||||
_materials.overhead_steel,
|
||||
[
|
||||
"CabinShell/Ceiling",
|
||||
"PortholeAssembly/TopBulkhead",
|
||||
"Dashboard/MainPanel",
|
||||
]
|
||||
)
|
||||
_apply_material_to_paths(
|
||||
_brass,
|
||||
[
|
||||
"PortholeAssembly/WindowFrame",
|
||||
"Dashboard/ResourcesFrame",
|
||||
"Dashboard/NavigationFrame",
|
||||
"Dashboard/IntegrityFrame",
|
||||
"ThrottleKnob",
|
||||
"VerticalKnob",
|
||||
]
|
||||
["CabinShell/Ceiling"]
|
||||
)
|
||||
func _apply_material_to_paths(material: Material, paths: Array[String]) -> void:
|
||||
var cockpit := get_parent()
|
||||
@@ -169,128 +139,26 @@ func _apply_material_to_paths(material: Material, paths: Array[String]) -> void:
|
||||
mesh.material_override = material
|
||||
|
||||
|
||||
func _build_pressure_hull_ribs() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "PressureHullRibs"
|
||||
add_child(root)
|
||||
for rib_index in 2:
|
||||
var rib := MeshInstance3D.new()
|
||||
rib.name = "CircularRib%d" % (rib_index + 1)
|
||||
var torus := TorusMesh.new()
|
||||
torus.inner_radius = 1.00
|
||||
torus.outer_radius = 1.075
|
||||
torus.rings = 48
|
||||
torus.ring_segments = 10
|
||||
rib.mesh = torus
|
||||
rib.material_override = _dark_steel
|
||||
rib.position = Vector3(0.0, 0.22, -0.90 + rib_index * 1.35)
|
||||
rib.rotation_degrees.x = 90.0
|
||||
root.add_child(rib)
|
||||
|
||||
for side: float in [-1.0, 1.0]:
|
||||
CockpitMeshFactory.add_box(
|
||||
"AngledShoulder",
|
||||
root,
|
||||
Vector3(side * 0.88, 0.84, -0.36),
|
||||
Vector3(0.11, 0.62, 1.52),
|
||||
_painted_steel,
|
||||
Vector3(0.0, 0.0, side * -18.0)
|
||||
)
|
||||
CockpitMeshFactory.add_box(
|
||||
"LowerStringer",
|
||||
root,
|
||||
Vector3(side * 0.91, -0.38, -0.18),
|
||||
Vector3(0.10, 0.12, 1.70),
|
||||
_dark_steel
|
||||
)
|
||||
|
||||
|
||||
func _build_life_support_pipes() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "LifeSupportPipes"
|
||||
add_child(root)
|
||||
for side: float in [-1.0, 1.0]:
|
||||
var x: float = side * 0.83
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"VerticalPipe",
|
||||
root,
|
||||
Vector3(x, 0.33, -1.12),
|
||||
0.027,
|
||||
1.18,
|
||||
_copper
|
||||
)
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"ReturnPipe",
|
||||
root,
|
||||
Vector3(side * 0.73, 0.47, -1.09),
|
||||
0.019,
|
||||
0.92,
|
||||
_brass
|
||||
)
|
||||
for clamp_y: float in [-0.04, 0.35, 0.72]:
|
||||
var clamp := MeshInstance3D.new()
|
||||
clamp.name = "PipeClamp"
|
||||
var torus := TorusMesh.new()
|
||||
torus.inner_radius = 0.027
|
||||
torus.outer_radius = 0.041
|
||||
torus.rings = 16
|
||||
torus.ring_segments = 6
|
||||
clamp.mesh = torus
|
||||
clamp.material_override = _dark_steel
|
||||
clamp.position = Vector3(x, clamp_y, -1.12)
|
||||
root.add_child(clamp)
|
||||
CockpitMeshFactory.add_sphere(
|
||||
"PipeJunction",
|
||||
root,
|
||||
Vector3(x, 0.91, -1.12),
|
||||
0.052,
|
||||
_copper
|
||||
)
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"UpperBranchPipe",
|
||||
root,
|
||||
Vector3(x, 0.84, -1.12),
|
||||
Vector3(side * 0.72, 0.77, -1.10),
|
||||
0.019,
|
||||
_brass
|
||||
)
|
||||
|
||||
|
||||
func _build_panel_hardware() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "PanelHardware"
|
||||
add_child(root)
|
||||
for x: float in [-0.73, -0.49, -0.24, 0.24, 0.49, 0.73]:
|
||||
for y: float in [-0.18, 0.22]:
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"PanelFastener",
|
||||
root,
|
||||
Vector3(x, y, -1.105),
|
||||
0.014,
|
||||
0.018,
|
||||
_dark_steel,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
|
||||
|
||||
|
||||
func _build_dashboard_instrumentation() -> void:
|
||||
var dashboard := get_parent().get_node_or_null("Dashboard") as Node3D
|
||||
if dashboard == null:
|
||||
return
|
||||
_dashboard_detailing = CockpitDashboardDetailing.new(_materials)
|
||||
_dashboard_detailing = CockpitDashboardDetailing.new()
|
||||
dashboard.add_child(_dashboard_detailing)
|
||||
_dashboard_detailing.hide_obsolete_elements(get_parent())
|
||||
_analog_gauges = CockpitAnalogGauges.new(_materials)
|
||||
dashboard.add_child(_analog_gauges)
|
||||
_analog_gauges = dashboard.get_node_or_null("AnalogGauges") as CockpitAnalogGauges
|
||||
|
||||
|
||||
func _build_equipment_assemblies() -> void:
|
||||
_side_equipment = CockpitSideEquipment.new(_materials)
|
||||
add_child(_side_equipment)
|
||||
var sonar_monitor := get_parent().get_node_or_null("SonarMonitor") as Node3D
|
||||
if sonar_monitor != null:
|
||||
_sonar_console = CockpitSonarConsole.new(_materials)
|
||||
sonar_monitor.add_child(_sonar_console)
|
||||
var cockpit := get_parent()
|
||||
_sonar_console = cockpit.get_node_or_null("SonarMonitor") as CockpitSonarConsole
|
||||
_alert_box = cockpit.get_node_or_null("AlertBox") as CockpitAlertBox
|
||||
_switch_panel = cockpit.get_node_or_null("SwitchPanel") as CockpitSwitchPanel
|
||||
_gas_bottle = cockpit.get_node_or_null("GasBottle") as Node3D
|
||||
_practical_lighting = CockpitPracticalLighting.new(_materials)
|
||||
add_child(_practical_lighting)
|
||||
# The alert box hangs between the left lamp and the gas bottle: casting, it
|
||||
# puts the bottle in full shadow.
|
||||
CockpitMeshFactory.disable_shadow_casting(_alert_box)
|
||||
@@ -12,6 +12,9 @@ const CockpitDetailing = preload(
|
||||
const COLOR_NORMAL := Color(0.45, 0.89, 0.90, 1.0)
|
||||
const COLOR_ATTENTION := Color(1.0, 0.75, 0.20, 1.0)
|
||||
const COLOR_CRITICAL := Color(1.0, 0.42, 0.31, 1.0)
|
||||
## 1.0 shows the sonar picture as rendered; slightly above lets its brightest
|
||||
## lines bloom without clipping the surface colours.
|
||||
const SONAR_SCREEN_ENERGY := 1.25
|
||||
|
||||
@onready var _resources_label: Label3D = %ResourcesLabel
|
||||
@onready var _navigation_label: Label3D = %NavigationLabel
|
||||
@@ -84,12 +87,18 @@ func return_text() -> String:
|
||||
|
||||
func attach_sonar_texture(texture: Texture2D) -> void:
|
||||
_sonar_texture = texture
|
||||
# The glass shows the picture by emission only: black albedo, no specular,
|
||||
# so cabin lights add nothing to it, and the energy can lift the brightest
|
||||
# lines just above 1.0 for the environment glow to pick up.
|
||||
var material := StandardMaterial3D.new()
|
||||
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
material.albedo_texture = texture
|
||||
material.albedo_color = Color.BLACK
|
||||
material.metallic_specular = 0.0
|
||||
material.roughness = 1.0
|
||||
material.emission_enabled = true
|
||||
material.emission = Color.WHITE
|
||||
material.emission_operator = BaseMaterial3D.EMISSION_OP_MULTIPLY
|
||||
material.emission_texture = texture
|
||||
material.emission_energy_multiplier = 1.7
|
||||
material.emission_energy_multiplier = SONAR_SCREEN_ENERGY
|
||||
_sonar_screen.material_override = material
|
||||
_sonar_placeholder.visible = false
|
||||
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
[gd_scene load_steps=21 format=3]
|
||||
[gd_scene load_steps=31 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://game/presentation/cockpit/cockpit_instrument_panel.gd" id="1_instruments"]
|
||||
[ext_resource type="Script" path="res://game/presentation/cockpit/cockpit_detailing.gd" id="2_detailing"]
|
||||
[ext_resource type="Script" path="res://game/presentation/cockpit/cockpit_pressure_bulkhead.gd" id="3_bulkhead"]
|
||||
[ext_resource type="Script" path="res://game/presentation/cockpit/cockpit_analog_gauges.gd" id="4_gauges"]
|
||||
[ext_resource type="Script" path="res://game/presentation/cockpit/cockpit_sonar_console.gd" id="5_sonar_console"]
|
||||
[ext_resource type="Script" path="res://game/presentation/cockpit/cockpit_alert_box.gd" id="6_alert_box"]
|
||||
[ext_resource type="Script" path="res://game/presentation/cockpit/cockpit_switch_panel.gd" id="7_switch_panel"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/gauge_panel.glb" id="8_gauge_panel_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/sonar.glb" id="9_sonar_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/switch_panel.glb" id="10_switch_panel_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/alert_box.glb" id="11_alert_box_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/speaker.glb" id="12_speaker_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/hull_wall.glb" id="13_hull_wall_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/porthole.glb" id="14_porthole_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/gas_bottle.glb" id="15_gas_bottle_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/handrail_l.glb" id="16_handrail_l_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/handrail_r.glb" id="17_handrail_r_model"]
|
||||
[ext_resource type="PackedScene" path="res://game/presentation/cockpit/assets/models/side_console.glb" id="18_side_console_model"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="SteelMaterial"]
|
||||
albedo_color = Color(0.055, 0.07, 0.07, 1)
|
||||
@@ -23,13 +38,6 @@ roughness = 0.43
|
||||
albedo_color = Color(0.018, 0.024, 0.025, 1)
|
||||
roughness = 0.88
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="GlassMaterial"]
|
||||
transparency = 1
|
||||
albedo_color = Color(0.08, 0.29, 0.32, 0.12)
|
||||
metallic = 0.05
|
||||
roughness = 0.16
|
||||
cull_mode = 2
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="ScreenMaterial"]
|
||||
albedo_color = Color(0.006, 0.025, 0.028, 1)
|
||||
metallic = 0.15
|
||||
@@ -62,33 +70,9 @@ emission_energy_multiplier = 2.8
|
||||
[sub_resource type="BoxMesh" id="UnitBoxMesh"]
|
||||
size = Vector3(1, 1, 1)
|
||||
|
||||
[sub_resource type="TorusMesh" id="PortholeTorusMesh"]
|
||||
inner_radius = 0.56
|
||||
outer_radius = 0.71
|
||||
rings = 48
|
||||
ring_segments = 16
|
||||
|
||||
[sub_resource type="CylinderMesh" id="GlassCylinderMesh"]
|
||||
top_radius = 0.56
|
||||
bottom_radius = 0.56
|
||||
height = 0.012
|
||||
radial_segments = 64
|
||||
|
||||
[sub_resource type="CylinderMesh" id="BoltMesh"]
|
||||
top_radius = 0.026
|
||||
bottom_radius = 0.026
|
||||
height = 0.045
|
||||
radial_segments = 12
|
||||
|
||||
[sub_resource type="QuadMesh" id="ScreenQuadMesh"]
|
||||
size = Vector2(1, 1)
|
||||
|
||||
[sub_resource type="CylinderMesh" id="LeverMesh"]
|
||||
top_radius = 0.018
|
||||
bottom_radius = 0.023
|
||||
height = 0.24
|
||||
radial_segments = 12
|
||||
|
||||
[sub_resource type="SphereMesh" id="KnobMesh"]
|
||||
radius = 0.046
|
||||
height = 0.092
|
||||
@@ -111,20 +95,20 @@ script = ExtResource("2_detailing")
|
||||
[node name="CabinShell" type="Node3D" parent="."]
|
||||
|
||||
[node name="LeftWall" type="MeshInstance3D" parent="CabinShell"]
|
||||
position = Vector3(-1.03, 0.24, -0.15)
|
||||
scale = Vector3(0.16, 1.82, 2.7)
|
||||
position = Vector3(-1.25, 0.47, 0.53)
|
||||
scale = Vector3(0.16, 2.62, 1.1)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("PaintedMaterial")
|
||||
|
||||
[node name="RightWall" type="MeshInstance3D" parent="CabinShell"]
|
||||
position = Vector3(1.03, 0.24, -0.15)
|
||||
scale = Vector3(0.16, 1.82, 2.7)
|
||||
position = Vector3(1.25, 0.47, 0.53)
|
||||
scale = Vector3(0.16, 2.62, 1.1)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("PaintedMaterial")
|
||||
|
||||
[node name="Ceiling" type="MeshInstance3D" parent="CabinShell"]
|
||||
position = Vector3(0, 1.22, -0.2)
|
||||
scale = Vector3(2.15, 0.16, 2.6)
|
||||
position = Vector3(0, 1.72, 0.53)
|
||||
scale = Vector3(2.66, 0.16, 1.1)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
@@ -135,29 +119,11 @@ mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("RubberMaterial")
|
||||
|
||||
[node name="RearBulkhead" type="MeshInstance3D" parent="CabinShell"]
|
||||
position = Vector3(0, 0.22, 1.08)
|
||||
scale = Vector3(2.08, 1.75, 0.16)
|
||||
position = Vector3(0, 0.47, 1.08)
|
||||
scale = Vector3(2.66, 2.62, 0.16)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="LeftRib" type="MeshInstance3D" parent="CabinShell"]
|
||||
position = Vector3(-0.88, 0.26, -0.45)
|
||||
scale = Vector3(0.11, 1.75, 0.18)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("BrassMaterial")
|
||||
|
||||
[node name="RightRib" type="MeshInstance3D" parent="CabinShell"]
|
||||
position = Vector3(0.88, 0.26, -0.45)
|
||||
scale = Vector3(0.11, 1.75, 0.18)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("BrassMaterial")
|
||||
|
||||
[node name="CeilingRib" type="MeshInstance3D" parent="CabinShell"]
|
||||
position = Vector3(0, 1.05, -0.45)
|
||||
scale = Vector3(1.82, 0.1, 0.18)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("BrassMaterial")
|
||||
|
||||
[node name="PortholeAssembly" type="Node3D" parent="."]
|
||||
position = Vector3(0, 0.47, -1.32)
|
||||
|
||||
@@ -165,107 +131,15 @@ position = Vector3(0, 0.47, -1.32)
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("3_bulkhead")
|
||||
|
||||
[node name="LeftBulkhead" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(-0.94, 0, 0)
|
||||
scale = Vector3(0.6, 1.82, 0.2)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
[node name="HullWall" parent="PortholeAssembly/CurvedPressureBulkhead" instance=ExtResource("13_hull_wall_model")]
|
||||
|
||||
[node name="RightBulkhead" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0.94, 0, 0)
|
||||
scale = Vector3(0.6, 1.82, 0.2)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="TopBulkhead" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0, 0.91, 0)
|
||||
scale = Vector3(1.55, 0.52, 0.2)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="BottomBulkhead" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0, -0.91, 0)
|
||||
scale = Vector3(1.55, 0.52, 0.2)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="WindowFrame" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
unique_name_in_owner = true
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("PortholeTorusMesh")
|
||||
material_override = SubResource("BrassMaterial")
|
||||
|
||||
[node name="RubberSeal" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
scale = Vector3(0.91, 0.91, 0.91)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("PortholeTorusMesh")
|
||||
material_override = SubResource("RubberMaterial")
|
||||
|
||||
[node name="WindowGlass" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0, 0, -0.01)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
cast_shadow = 0
|
||||
mesh = SubResource("GlassCylinderMesh")
|
||||
material_override = SubResource("GlassMaterial")
|
||||
|
||||
[node name="BoltTop" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0, 0.665, 0.1)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("BoltMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="BoltBottom" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0, -0.665, 0.1)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("BoltMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="BoltLeft" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(-0.665, 0, 0.1)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("BoltMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="BoltRight" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0.665, 0, 0.1)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("BoltMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="BoltUpperLeft" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(-0.47, 0.47, 0.1)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("BoltMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="BoltUpperRight" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0.47, 0.47, 0.1)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("BoltMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="BoltLowerLeft" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(-0.47, -0.47, 0.1)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("BoltMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="BoltLowerRight" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||
position = Vector3(0.47, -0.47, 0.1)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("BoltMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
[node name="Porthole" parent="PortholeAssembly/CurvedPressureBulkhead" instance=ExtResource("14_porthole_model")]
|
||||
|
||||
[node name="Dashboard" type="Node3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(0, 0.05, -1.22)
|
||||
rotation_degrees = Vector3(-12, 0, 0)
|
||||
|
||||
[node name="MainPanel" type="MeshInstance3D" parent="Dashboard"]
|
||||
scale = Vector3(1.56, 0.44, 0.14)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("PaintedMaterial")
|
||||
|
||||
[node name="ResourcesFrame" type="MeshInstance3D" parent="Dashboard"]
|
||||
position = Vector3(-0.49, -0.115, 0.14)
|
||||
scale = Vector3(0.34, 0.065, 0.025)
|
||||
@@ -331,7 +205,7 @@ modulate = Color(0.76, 0.74, 0.6, 1)
|
||||
|
||||
[node name="ReturnLabel" type="Label3D" parent="Dashboard"]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(0.61, 0.245, 0.17)
|
||||
position = Vector3(0.22, 0.266, 0.158)
|
||||
pixel_size = 0.00062
|
||||
font_size = 12
|
||||
outline_size = 2
|
||||
@@ -340,89 +214,86 @@ modulate = Color(1, 0.75, 0.2, 1)
|
||||
|
||||
[node name="StatusLabel" type="Label3D" parent="Dashboard"]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(-0.61, 0.245, 0.17)
|
||||
position = Vector3(-0.36, 0.266, 0.158)
|
||||
pixel_size = 0.00062
|
||||
font_size = 12
|
||||
outline_size = 2
|
||||
text = "SYSTÈMES · NORMAL"
|
||||
modulate = Color(0.45, 0.89, 0.9, 1)
|
||||
|
||||
[node name="LeftConsole" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.82, -0.23, -1.19)
|
||||
rotation_degrees = Vector3(-8, 18, 4)
|
||||
scale = Vector3(0.32, 0.16, 0.56)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("PaintedMaterial")
|
||||
[node name="AnalogGauges" type="Node3D" parent="Dashboard"]
|
||||
position = Vector3(0, -0.06, 0.15)
|
||||
script = ExtResource("4_gauges")
|
||||
|
||||
[node name="RightConsole" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0.82, -0.23, -1.19)
|
||||
rotation_degrees = Vector3(-8, -18, -4)
|
||||
scale = Vector3(0.32, 0.16, 0.56)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("PaintedMaterial")
|
||||
|
||||
[node name="ThrottleLever" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.79, -0.06, -1.13)
|
||||
rotation_degrees = Vector3(18, 0, -10)
|
||||
scale = Vector3(0.72, 0.72, 0.72)
|
||||
mesh = SubResource("LeverMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="ThrottleKnob" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.81, 0.025, -1.1)
|
||||
scale = Vector3(0.72, 0.72, 0.72)
|
||||
mesh = SubResource("KnobMesh")
|
||||
material_override = SubResource("BrassMaterial")
|
||||
|
||||
[node name="VerticalLever" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0.79, -0.06, -1.13)
|
||||
rotation_degrees = Vector3(18, 0, 10)
|
||||
scale = Vector3(0.72, 0.72, 0.72)
|
||||
mesh = SubResource("LeverMesh")
|
||||
material_override = SubResource("SteelMaterial")
|
||||
|
||||
[node name="VerticalKnob" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0.81, 0.025, -1.1)
|
||||
scale = Vector3(0.72, 0.72, 0.72)
|
||||
mesh = SubResource("KnobMesh")
|
||||
material_override = SubResource("BrassMaterial")
|
||||
[node name="GaugePanelModel" parent="Dashboard/AnalogGauges" instance=ExtResource("8_gauge_panel_model")]
|
||||
|
||||
[node name="SonarMonitor" type="Node3D" parent="."]
|
||||
position = Vector3(0.54, 0.59, -1.04)
|
||||
position = Vector3(0.54, 0.48, -1.04)
|
||||
rotation_degrees = Vector3(-5, -42, -2)
|
||||
scale = Vector3(0.6, 0.6, 0.6)
|
||||
script = ExtResource("5_sonar_console")
|
||||
|
||||
[node name="Housing" type="MeshInstance3D" parent="SonarMonitor"]
|
||||
scale = Vector3(0.6, 0.37, 0.1)
|
||||
mesh = SubResource("UnitBoxMesh")
|
||||
material_override = SubResource("PaintedMaterial")
|
||||
|
||||
[node name="SonarScreen" type="MeshInstance3D" parent="SonarMonitor"]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(-0.025, 0.012, 0.06)
|
||||
scale = Vector3(0.36, 0.235, 1)
|
||||
mesh = SubResource("ScreenQuadMesh")
|
||||
material_override = SubResource("ScreenMaterial")
|
||||
[node name="SonarModel" parent="SonarMonitor" instance=ExtResource("9_sonar_model")]
|
||||
scale = Vector3(0.31, 0.31, 0.31)
|
||||
|
||||
[node name="SonarPlaceholder" type="Label3D" parent="SonarMonitor"]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(-0.025, 0.012, 0.068)
|
||||
pixel_size = 0.0009
|
||||
position = Vector3(-0.0164, 0.002, 0.026)
|
||||
pixel_size = 0.00054
|
||||
font_size = 16
|
||||
outline_size = 2
|
||||
text = "SONAR\nMODULE EN ATTENTE"
|
||||
text = "SONAR
|
||||
MODULE EN ATTENTE"
|
||||
modulate = Color(0.34, 0.6, 0.6, 1)
|
||||
|
||||
[node name="AlertBox" type="Node3D" parent="."]
|
||||
position = Vector3(-0.86, 0.555, -1.214)
|
||||
rotation_degrees = Vector3(0, 15, 0)
|
||||
script = ExtResource("6_alert_box")
|
||||
|
||||
[node name="AlertBoxModel" parent="AlertBox" instance=ExtResource("11_alert_box_model")]
|
||||
scale = Vector3(0.5, 0.5, 0.5)
|
||||
|
||||
[node name="SwitchPanel" type="Node3D" parent="."]
|
||||
position = Vector3(-0.74, -0.125, -0.97)
|
||||
rotation_degrees = Vector3(0, 30, 0)
|
||||
script = ExtResource("7_switch_panel")
|
||||
|
||||
[node name="SwitchPanelModel" parent="SwitchPanel" instance=ExtResource("10_switch_panel_model")]
|
||||
scale = Vector3(0.37, 0.37, 0.37)
|
||||
|
||||
[node name="Speaker" parent="." instance=ExtResource("12_speaker_model")]
|
||||
position = Vector3(0.83, 0.84, -1.22)
|
||||
rotation_degrees = Vector3(0, -15, 0)
|
||||
scale = Vector3(0.56, 0.56, 0.56)
|
||||
|
||||
[node name="GasBottle" parent="." instance=ExtResource("15_gas_bottle_model")]
|
||||
position = Vector3(-1.02, 0.36, -1.225)
|
||||
rotation_degrees = Vector3(0, 53.3, 0)
|
||||
scale = Vector3(0.65, 0.65, 0.65)
|
||||
|
||||
[node name="HandrailLeft" parent="." instance=ExtResource("16_handrail_l_model")]
|
||||
position = Vector3(-0.6, 0.24, -1.1)
|
||||
|
||||
[node name="HandrailRight" parent="." instance=ExtResource("17_handrail_r_model")]
|
||||
position = Vector3(0.6, 0.24, -1.1)
|
||||
|
||||
[node name="SideConsoleLeft" parent="." instance=ExtResource("18_side_console_model")]
|
||||
position = Vector3(-0.632, -0.61, -1.215)
|
||||
|
||||
[node name="SideConsoleRight" parent="." instance=ExtResource("18_side_console_model")]
|
||||
position = Vector3(0.632, -0.61, -1.215)
|
||||
scale = Vector3(-1, 1, 1)
|
||||
|
||||
[node name="EmergencyDial" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(-0.7, 0.32, -1.15)
|
||||
scale = Vector3(0.18, 0.18, 0.18)
|
||||
position = Vector3(-0.845, 0.47, -1.283)
|
||||
scale = Vector3(0.3, 0.3, 0.3)
|
||||
rotation_degrees = Vector3(90, 0, 0)
|
||||
mesh = SubResource("DialMesh")
|
||||
material_override = SubResource("RedMaterial")
|
||||
|
||||
[node name="EmergencyLabel" type="Label3D" parent="."]
|
||||
position = Vector3(-0.7, 0.275, -1.125)
|
||||
pixel_size = 0.00072
|
||||
position = Vector3(-0.845, 0.418, -1.28)
|
||||
pixel_size = 0.0015
|
||||
font_size = 12
|
||||
outline_size = 2
|
||||
text = "ÉJECTION · X"
|
||||
@@ -449,15 +320,15 @@ material_override = SubResource("RedMaterial")
|
||||
[node name="CabinLight" type="OmniLight3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(0, 0.92, -0.28)
|
||||
light_color = Color(0.36, 0.68, 0.69, 1)
|
||||
light_energy = 1.25
|
||||
light_color = Color(1, 0.7, 0.46, 1)
|
||||
light_energy = 0.04
|
||||
shadow_enabled = true
|
||||
omni_range = 3.3
|
||||
|
||||
[node name="PanelLight" type="OmniLight3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(0, 0.18, -0.63)
|
||||
light_color = Color(0.23, 0.72, 0.76, 1)
|
||||
light_energy = 0.58
|
||||
light_color = Color(1, 0.72, 0.5, 1)
|
||||
light_energy = 0.4
|
||||
shadow_enabled = false
|
||||
omni_range = 1.35
|
||||
omni_range = 0.8
|
||||
@@ -4,18 +4,6 @@ extends RefCounted
|
||||
const NAVAL_STEEL_TEXTURE_PATH := (
|
||||
"res://game/presentation/cockpit/assets/naval_painted_steel_albedo.png"
|
||||
)
|
||||
const OXYGEN_GAUGE_FACE_TEXTURE_PATH := (
|
||||
"res://game/presentation/cockpit/assets/oxygen_gauge_face_albedo_v2.png"
|
||||
)
|
||||
const BATTERY_GAUGE_FACE_TEXTURE_PATH := (
|
||||
"res://game/presentation/cockpit/assets/battery_gauge_face_albedo_v2.png"
|
||||
)
|
||||
const HULL_GAUGE_FACE_TEXTURE_PATH := (
|
||||
"res://game/presentation/cockpit/assets/hull_gauge_face_albedo_v2.png"
|
||||
)
|
||||
const DEPTH_GAUGE_FACE_TEXTURE_PATH := (
|
||||
"res://game/presentation/cockpit/assets/depth_gauge_face_albedo_v2.png"
|
||||
)
|
||||
|
||||
var painted_steel: Material
|
||||
var dark_steel: Material
|
||||
@@ -24,21 +12,8 @@ var brushed_steel: StandardMaterial3D
|
||||
var brass: Material
|
||||
var copper: Material
|
||||
var rubber: Material
|
||||
var oxygen_gauge_face: StandardMaterial3D
|
||||
var battery_gauge_face: StandardMaterial3D
|
||||
var hull_gauge_face: StandardMaterial3D
|
||||
var depth_gauge_face: StandardMaterial3D
|
||||
var gauge_glass: StandardMaterial3D
|
||||
var needle_paint: StandardMaterial3D
|
||||
var instrument_screen: ShaderMaterial
|
||||
var warm_emission: StandardMaterial3D
|
||||
var safety_yellow: StandardMaterial3D
|
||||
var signal_red: StandardMaterial3D
|
||||
var indicator_green: StandardMaterial3D
|
||||
var indicator_amber: StandardMaterial3D
|
||||
var indicator_red: StandardMaterial3D
|
||||
var sonar_grid: StandardMaterial3D
|
||||
var warm_glass: StandardMaterial3D
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
@@ -79,21 +54,8 @@ func _init() -> void:
|
||||
0.12
|
||||
)
|
||||
rubber = _plain_material(Color(0.012, 0.018, 0.018), 0.0, 0.92)
|
||||
oxygen_gauge_face = _dedicated_gauge_material(OXYGEN_GAUGE_FACE_TEXTURE_PATH)
|
||||
battery_gauge_face = _dedicated_gauge_material(BATTERY_GAUGE_FACE_TEXTURE_PATH)
|
||||
hull_gauge_face = _dedicated_gauge_material(HULL_GAUGE_FACE_TEXTURE_PATH)
|
||||
depth_gauge_face = _dedicated_gauge_material(DEPTH_GAUGE_FACE_TEXTURE_PATH)
|
||||
gauge_glass = _glass_material()
|
||||
needle_paint = _plain_material(Color(0.68, 0.055, 0.022), 0.12, 0.44)
|
||||
instrument_screen = _screen_material()
|
||||
warm_emission = _emissive_material(Color(0.96, 0.52, 0.18), 2.4)
|
||||
safety_yellow = _plain_material(Color(0.48, 0.31, 0.035), 0.48, 0.53)
|
||||
signal_red = _plain_material(Color(0.42, 0.025, 0.015), 0.38, 0.48)
|
||||
indicator_green = _emissive_material(Color(0.05, 0.82, 0.24), 2.8)
|
||||
indicator_amber = _emissive_material(Color(1.0, 0.43, 0.035), 2.8)
|
||||
indicator_red = _emissive_material(Color(0.9, 0.045, 0.018), 2.8)
|
||||
sonar_grid = _emissive_material(Color(0.02, 0.20, 0.22), 0.55)
|
||||
warm_glass = _warm_glass_material()
|
||||
|
||||
|
||||
func _weathered_material(
|
||||
@@ -185,27 +147,6 @@ func _load_texture(resource_path: String) -> Texture2D:
|
||||
return texture
|
||||
|
||||
|
||||
func _dedicated_gauge_material(resource_path: String) -> StandardMaterial3D:
|
||||
var material := _textured_material(
|
||||
_load_texture(resource_path),
|
||||
Color(0.94, 0.92, 0.86),
|
||||
0.02,
|
||||
0.66
|
||||
)
|
||||
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
return material
|
||||
|
||||
|
||||
func _glass_material() -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
material.albedo_color = Color(0.42, 0.68, 0.66, 0.13)
|
||||
material.metallic = 0.12
|
||||
material.roughness = 0.08
|
||||
material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
return material
|
||||
|
||||
|
||||
func _screen_material() -> ShaderMaterial:
|
||||
var shader := Shader.new()
|
||||
shader.code = """
|
||||
@@ -238,14 +179,3 @@ func _emissive_material(color: Color, energy: float) -> StandardMaterial3D:
|
||||
material.emission = color
|
||||
material.emission_energy_multiplier = energy
|
||||
return material
|
||||
|
||||
|
||||
func _warm_glass_material() -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
material.albedo_color = Color(1.0, 0.48, 0.12, 0.72)
|
||||
material.roughness = 0.18
|
||||
material.emission_enabled = true
|
||||
material.emission = Color(1.0, 0.28, 0.045)
|
||||
material.emission_energy_multiplier = 2.6
|
||||
return material
|
||||
@@ -87,3 +87,27 @@ static func add_sphere(
|
||||
instance.position = position
|
||||
parent.add_child(instance)
|
||||
return instance
|
||||
|
||||
|
||||
## Gives an imported mesh instance its own copy of its first surface material,
|
||||
## so driving a property (emission) never edits the resource shared by the model.
|
||||
static func own_surface_material(instance: MeshInstance3D) -> BaseMaterial3D:
|
||||
if instance == null or instance.mesh == null or instance.mesh.get_surface_count() == 0:
|
||||
return null
|
||||
var shared := instance.mesh.surface_get_material(0) as BaseMaterial3D
|
||||
if shared == null:
|
||||
return null
|
||||
var owned := shared.duplicate() as BaseMaterial3D
|
||||
instance.set_surface_override_material(0, owned)
|
||||
return owned
|
||||
|
||||
|
||||
## Stops every mesh under `root` (root included) from casting shadows.
|
||||
static func disable_shadow_casting(root: Node) -> void:
|
||||
if root == null:
|
||||
return
|
||||
var meshes: Array[Node] = root.find_children("*", "MeshInstance3D", true, false)
|
||||
meshes.append(root)
|
||||
for node: Node in meshes:
|
||||
if node is MeshInstance3D:
|
||||
(node as MeshInstance3D).cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
@@ -9,6 +9,28 @@ const CockpitMeshFactory = preload(
|
||||
"res://game/presentation/cockpit/cockpit_mesh_factory.gd"
|
||||
)
|
||||
|
||||
const CAGE_LAMP_SCENE = preload("res://game/presentation/cockpit/assets/models/cage_lamp.glb")
|
||||
|
||||
## Flange centre of the right-hand lamp, on the flat end cap of the pressure
|
||||
## hull beside the porthole (the left lamp mirrors x). The cap is flat there, so
|
||||
## the lamp is seated square to it, facing the cabin.
|
||||
const LAMP_MOUNT_OFFSET := Vector3(0.636, 1.106, -1.3)
|
||||
## Centre of the glass globe in the lamp model: where the light is emitted.
|
||||
const LAMP_GLOBE_CENTRE := Vector3(0.0, 0.0, 0.064)
|
||||
## Globe emission (1.5 as exported): lower, so the globe does not bloom into a blob.
|
||||
const LAMP_GLASS_ENERGY := 1.0
|
||||
## Tungsten-like caged lamps: the cabin's main light.
|
||||
const WARM_LAMP_COLOR := Color(1.0, 0.66, 0.4)
|
||||
const WARM_LAMP_ENERGY := 0.5
|
||||
## Same dimming ratio as before when the red emergency light takes over.
|
||||
const WARM_LAMP_CRITICAL_ENERGY := 0.23
|
||||
## Short range and a steep falloff: each lamp makes a pool on the wall around
|
||||
## it and leaves the lower and outer hull dark.
|
||||
const WARM_LAMP_RANGE_M := 1.6
|
||||
const WARM_LAMP_ATTENUATION := 2.0
|
||||
## Cabin parts are centimetre-scale: the default bias detaches their shadows.
|
||||
const WARM_LAMP_SHADOW_BIAS := 0.03
|
||||
|
||||
var _materials: CockpitMaterialPalette
|
||||
var _warm_lights: Array[OmniLight3D] = []
|
||||
var _emergency_light: OmniLight3D
|
||||
@@ -17,8 +39,8 @@ var _emergency_light: OmniLight3D
|
||||
func _init(materials: CockpitMaterialPalette) -> void:
|
||||
_materials = materials
|
||||
name = "PracticalLighting"
|
||||
_add_caged_lamp(-0.76, -8.0)
|
||||
_add_caged_lamp(0.76, 8.0)
|
||||
_add_caged_lamp(-1.0)
|
||||
_add_caged_lamp(1.0)
|
||||
_add_fill_light()
|
||||
_add_emergency_light()
|
||||
|
||||
@@ -26,75 +48,69 @@ func _init(materials: CockpitMaterialPalette) -> void:
|
||||
func present(alert_level: DiveAlertLevel.Value) -> void:
|
||||
_emergency_light.visible = alert_level >= DiveAlertLevel.Value.CRITICAL
|
||||
for light: OmniLight3D in _warm_lights:
|
||||
light.light_energy = 0.22 if alert_level >= DiveAlertLevel.Value.CRITICAL else 0.48
|
||||
light.light_energy = (
|
||||
WARM_LAMP_CRITICAL_ENERGY
|
||||
if alert_level >= DiveAlertLevel.Value.CRITICAL
|
||||
else WARM_LAMP_ENERGY
|
||||
)
|
||||
|
||||
|
||||
func practical_light_count() -> int:
|
||||
return _warm_lights.size()
|
||||
|
||||
|
||||
func _add_caged_lamp(x: float, roll_degrees: float) -> void:
|
||||
func _add_caged_lamp(side: float) -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "CagedLamp"
|
||||
root.position = Vector3(x, 0.88, -1.16)
|
||||
root.rotation_degrees.z = roll_degrees
|
||||
root.position = Vector3(
|
||||
side * LAMP_MOUNT_OFFSET.x,
|
||||
LAMP_MOUNT_OFFSET.y,
|
||||
LAMP_MOUNT_OFFSET.z
|
||||
)
|
||||
add_child(root)
|
||||
CockpitMeshFactory.add_box(
|
||||
"LampHousing",
|
||||
root,
|
||||
Vector3(0.0, 0.0, -0.025),
|
||||
Vector3(0.14, 0.11, 0.055),
|
||||
_materials.dark_steel
|
||||
var model := CAGE_LAMP_SCENE.instantiate() as Node3D
|
||||
model.name = "CageLampModel"
|
||||
root.add_child(model)
|
||||
# The light sits inside the cage, 3 cm from its bars: casting, the lamp's
|
||||
# own body would swallow it.
|
||||
CockpitMeshFactory.disable_shadow_casting(model)
|
||||
var glass := CockpitMeshFactory.own_surface_material(
|
||||
model.find_child("CageLamp_Glass", true, false) as MeshInstance3D
|
||||
)
|
||||
CockpitMeshFactory.add_sphere(
|
||||
"WarmBulb",
|
||||
root,
|
||||
Vector3(0.0, 0.0, 0.04),
|
||||
0.038,
|
||||
_materials.warm_glass
|
||||
)
|
||||
for x_offset: float in [-0.048, -0.024, 0.0, 0.024, 0.048]:
|
||||
CockpitMeshFactory.add_box(
|
||||
"LampGuard",
|
||||
root,
|
||||
Vector3(x_offset, 0.0, 0.095),
|
||||
Vector3(0.007, 0.10, 0.007),
|
||||
_materials.brushed_steel
|
||||
)
|
||||
for y_offset: float in [-0.04, 0.04]:
|
||||
CockpitMeshFactory.add_box(
|
||||
"LampGuardRail",
|
||||
root,
|
||||
Vector3(0.0, y_offset, 0.095),
|
||||
Vector3(0.115, 0.007, 0.007),
|
||||
_materials.brushed_steel
|
||||
)
|
||||
if glass != null:
|
||||
glass.emission_energy_multiplier = LAMP_GLASS_ENERGY
|
||||
var light := OmniLight3D.new()
|
||||
light.name = "WarmPracticalLight"
|
||||
light.position = Vector3(0.0, -0.03, 0.16)
|
||||
light.light_color = Color(1.0, 0.55, 0.26)
|
||||
light.light_energy = 0.48
|
||||
light.omni_range = 1.75
|
||||
light.shadow_enabled = false
|
||||
light.position = LAMP_GLOBE_CENTRE
|
||||
light.light_color = WARM_LAMP_COLOR
|
||||
light.light_energy = WARM_LAMP_ENERGY
|
||||
light.omni_range = WARM_LAMP_RANGE_M
|
||||
light.omni_attenuation = WARM_LAMP_ATTENUATION
|
||||
light.shadow_enabled = true
|
||||
light.shadow_bias = WARM_LAMP_SHADOW_BIAS
|
||||
root.add_child(light)
|
||||
_warm_lights.append(light)
|
||||
|
||||
|
||||
func _add_fill_light() -> void:
|
||||
var cool_fill := OmniLight3D.new()
|
||||
cool_fill.name = "CoolRearFill"
|
||||
cool_fill.position = Vector3(0.0, 0.42, 0.35)
|
||||
cool_fill.light_color = Color(0.24, 0.46, 0.47)
|
||||
cool_fill.light_energy = 0.36
|
||||
cool_fill.omni_range = 3.0
|
||||
cool_fill.shadow_enabled = false
|
||||
add_child(cool_fill)
|
||||
# The only cool light in the cabin: sea light entering through the porthole,
|
||||
# placed just inside the glass so it rims the ring, its bolts and the first
|
||||
# band of wall, and reaches the dashboard only from behind.
|
||||
var porthole_fill := OmniLight3D.new()
|
||||
porthole_fill.name = "PortholeFill"
|
||||
porthole_fill.position = Vector3(0.0, 0.47, -1.2)
|
||||
porthole_fill.light_color = Color(0.30, 0.56, 0.78)
|
||||
porthole_fill.light_energy = 1.0
|
||||
porthole_fill.omni_range = 1.1
|
||||
porthole_fill.shadow_enabled = false
|
||||
add_child(porthole_fill)
|
||||
|
||||
|
||||
func _add_emergency_light() -> void:
|
||||
_emergency_light = OmniLight3D.new()
|
||||
_emergency_light.name = "EmergencyLight"
|
||||
_emergency_light.position = Vector3(-0.58, 0.44, -0.83)
|
||||
# High and central, so no single dial sits right under it and washes out.
|
||||
_emergency_light.position = Vector3(0.0, 1.1, -0.95)
|
||||
_emergency_light.light_color = Color(1.0, 0.04, 0.015)
|
||||
_emergency_light.light_energy = 1.4
|
||||
_emergency_light.omni_range = 2.4
|
||||
|
||||
@@ -1,192 +1,57 @@
|
||||
class_name CockpitPressureBulkhead
|
||||
extends Node3D
|
||||
|
||||
const CockpitMaterialPalette = preload(
|
||||
"res://game/presentation/cockpit/cockpit_material_palette.gd"
|
||||
)
|
||||
const CockpitMeshFactory = preload(
|
||||
"res://game/presentation/cockpit/cockpit_mesh_factory.gd"
|
||||
)
|
||||
## Imported pressure hull (end cap, knuckle and barrel in one shell) and the
|
||||
## porthole bolted into it. Both models are instanced by `cockpit_interior.tscn`.
|
||||
|
||||
const PANEL_COUNT := 1
|
||||
const FASTENER_COUNT := 16
|
||||
const ANGULAR_STEPS := 64
|
||||
const RADIAL_STEPS := 4
|
||||
const INNER_RADIUS := 0.72
|
||||
const OUTER_RADIUS := 1.11
|
||||
const WHITEBOX_NAMES: Array[String] = [
|
||||
"LeftBulkhead",
|
||||
"RightBulkhead",
|
||||
"TopBulkhead",
|
||||
"BottomBulkhead",
|
||||
]
|
||||
|
||||
var _materials: CockpitMaterialPalette
|
||||
var _panel_count := 0
|
||||
var _hull_shells: Array[Node] = []
|
||||
var _porthole_frame: MeshInstance3D
|
||||
var _porthole_glass: MeshInstance3D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_materials = CockpitMaterialPalette.new()
|
||||
_hide_rectangular_whitebox()
|
||||
_style_existing_frame()
|
||||
_build_curved_panels()
|
||||
_build_outer_rib()
|
||||
_build_fasteners()
|
||||
_build_clamp_plates()
|
||||
var hull := get_node_or_null("HullWall")
|
||||
if hull != null:
|
||||
_hull_shells = hull.find_children("*", "MeshInstance3D", true, false)
|
||||
_porthole_frame = _find_mesh("Porthole")
|
||||
_porthole_glass = _find_mesh("PortholeGlass")
|
||||
if _hull_shells.is_empty() or _porthole_frame == null or _porthole_glass == null:
|
||||
push_error("Pressure hull or porthole model is missing a node")
|
||||
return
|
||||
_porthole_glass.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
|
||||
|
||||
func panel_count() -> int:
|
||||
return _panel_count
|
||||
return _hull_shells.size()
|
||||
|
||||
|
||||
func hull_shell() -> MeshInstance3D:
|
||||
return null if _hull_shells.is_empty() else _hull_shells[0] as MeshInstance3D
|
||||
|
||||
|
||||
func porthole_frame() -> MeshInstance3D:
|
||||
return _porthole_frame
|
||||
|
||||
|
||||
func porthole_glass() -> MeshInstance3D:
|
||||
return _porthole_glass
|
||||
|
||||
|
||||
func rectangular_whitebox_is_hidden() -> bool:
|
||||
for node_name: String in ["LeftBulkhead", "RightBulkhead", "TopBulkhead", "BottomBulkhead"]:
|
||||
for node_name: String in WHITEBOX_NAMES:
|
||||
var whitebox := get_parent().get_node_or_null(node_name) as MeshInstance3D
|
||||
if whitebox != null and whitebox.visible:
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _hide_rectangular_whitebox() -> void:
|
||||
for node_name: String in ["LeftBulkhead", "RightBulkhead", "TopBulkhead", "BottomBulkhead"]:
|
||||
var whitebox := get_parent().get_node_or_null(node_name) as MeshInstance3D
|
||||
if whitebox != null:
|
||||
whitebox.visible = false
|
||||
|
||||
|
||||
func _style_existing_frame() -> void:
|
||||
var frame := get_parent().get_node_or_null("WindowFrame") as MeshInstance3D
|
||||
if frame != null:
|
||||
frame.material_override = _materials.brushed_steel
|
||||
for child: Node in get_parent().get_children():
|
||||
if child is MeshInstance3D and child.name.begins_with("Bolt"):
|
||||
(child as MeshInstance3D).material_override = _materials.brass
|
||||
|
||||
|
||||
func _build_curved_panels() -> void:
|
||||
var panel_angle := TAU / float(PANEL_COUNT)
|
||||
for panel_index in PANEL_COUNT:
|
||||
var start_angle := panel_index * panel_angle
|
||||
var end_angle := (panel_index + 1) * panel_angle
|
||||
var panel := MeshInstance3D.new()
|
||||
panel.name = "PressurePanel%02d" % (panel_index + 1)
|
||||
panel.mesh = _create_curved_panel_mesh(start_angle, end_angle)
|
||||
panel.material_override = _materials.overhead_steel
|
||||
panel.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
||||
add_child(panel)
|
||||
_panel_count += 1
|
||||
|
||||
|
||||
func _create_curved_panel_mesh(start_angle: float, end_angle: float) -> ArrayMesh:
|
||||
var surface := SurfaceTool.new()
|
||||
surface.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
for angular_index in ANGULAR_STEPS:
|
||||
var angle_ratio_0 := float(angular_index) / float(ANGULAR_STEPS)
|
||||
var angle_ratio_1 := float(angular_index + 1) / float(ANGULAR_STEPS)
|
||||
var angle_0 := lerpf(start_angle, end_angle, angle_ratio_0)
|
||||
var angle_1 := lerpf(start_angle, end_angle, angle_ratio_1)
|
||||
for radial_index in RADIAL_STEPS:
|
||||
var radial_ratio_0 := float(radial_index) / float(RADIAL_STEPS)
|
||||
var radial_ratio_1 := float(radial_index + 1) / float(RADIAL_STEPS)
|
||||
_add_panel_quad(
|
||||
surface,
|
||||
angle_0,
|
||||
angle_1,
|
||||
angle_ratio_0,
|
||||
angle_ratio_1,
|
||||
radial_ratio_0,
|
||||
radial_ratio_1
|
||||
)
|
||||
surface.generate_normals()
|
||||
return surface.commit()
|
||||
|
||||
|
||||
func _add_panel_quad(
|
||||
surface: SurfaceTool,
|
||||
angle_0: float,
|
||||
angle_1: float,
|
||||
angle_ratio_0: float,
|
||||
angle_ratio_1: float,
|
||||
radial_ratio_0: float,
|
||||
radial_ratio_1: float
|
||||
) -> void:
|
||||
_add_panel_vertex(surface, angle_0, angle_ratio_0, radial_ratio_0)
|
||||
_add_panel_vertex(surface, angle_1, angle_ratio_1, radial_ratio_0)
|
||||
_add_panel_vertex(surface, angle_0, angle_ratio_0, radial_ratio_1)
|
||||
_add_panel_vertex(surface, angle_0, angle_ratio_0, radial_ratio_1)
|
||||
_add_panel_vertex(surface, angle_1, angle_ratio_1, radial_ratio_0)
|
||||
_add_panel_vertex(surface, angle_1, angle_ratio_1, radial_ratio_1)
|
||||
|
||||
|
||||
func _add_panel_vertex(
|
||||
surface: SurfaceTool,
|
||||
angle: float,
|
||||
angle_ratio: float,
|
||||
radial_ratio: float
|
||||
) -> void:
|
||||
var radius := lerpf(INNER_RADIUS, OUTER_RADIUS, radial_ratio)
|
||||
var convex_depth := sin(radial_ratio * PI) * 0.075
|
||||
surface.set_uv(Vector2(angle_ratio, radial_ratio))
|
||||
surface.add_vertex(
|
||||
Vector3(cos(angle) * radius, sin(angle) * radius, 0.045 + convex_depth)
|
||||
)
|
||||
|
||||
|
||||
func _build_outer_rib() -> void:
|
||||
var rib := MeshInstance3D.new()
|
||||
rib.name = "OuterPressureRib"
|
||||
var torus := TorusMesh.new()
|
||||
torus.inner_radius = 1.055
|
||||
torus.outer_radius = 1.125
|
||||
torus.rings = 64
|
||||
torus.ring_segments = 12
|
||||
rib.mesh = torus
|
||||
rib.material_override = _materials.dark_steel
|
||||
rib.rotation_degrees.x = 90.0
|
||||
rib.position.z = 0.025
|
||||
add_child(rib)
|
||||
var inner_rib := MeshInstance3D.new()
|
||||
inner_rib.name = "InnerPressureRib"
|
||||
var inner_torus := TorusMesh.new()
|
||||
inner_torus.inner_radius = 0.695
|
||||
inner_torus.outer_radius = 0.755
|
||||
inner_torus.rings = 64
|
||||
inner_torus.ring_segments = 12
|
||||
inner_rib.mesh = inner_torus
|
||||
inner_rib.material_override = _materials.brushed_steel
|
||||
inner_rib.rotation_degrees.x = 90.0
|
||||
inner_rib.position.z = 0.075
|
||||
add_child(inner_rib)
|
||||
|
||||
|
||||
func _build_fasteners() -> void:
|
||||
for fastener_index in FASTENER_COUNT:
|
||||
var angle := (fastener_index + 0.5) * TAU / float(FASTENER_COUNT)
|
||||
var fastener_position := Vector3(
|
||||
cos(angle) * 0.96,
|
||||
sin(angle) * 0.96,
|
||||
0.12
|
||||
)
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"OuterFastener",
|
||||
self,
|
||||
fastener_position,
|
||||
0.020,
|
||||
0.018,
|
||||
_materials.brass,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
CockpitMeshFactory.add_sphere(
|
||||
"FastenerCap",
|
||||
self,
|
||||
fastener_position + Vector3(0.0, 0.0, 0.012),
|
||||
0.017,
|
||||
_materials.brushed_steel
|
||||
)
|
||||
|
||||
|
||||
func _build_clamp_plates() -> void:
|
||||
for plate_index in 12:
|
||||
var angle := (plate_index + 0.5) * TAU / 12.0
|
||||
var plate := CockpitMeshFactory.add_box(
|
||||
"PressureClamp",
|
||||
self,
|
||||
Vector3(cos(angle) * 0.88, sin(angle) * 0.88, 0.105),
|
||||
Vector3(0.11, 0.055, 0.028),
|
||||
_materials.brushed_steel
|
||||
)
|
||||
plate.rotation_degrees.z = rad_to_deg(angle) + 90.0
|
||||
func _find_mesh(mesh_name: String) -> MeshInstance3D:
|
||||
var matches := find_children(mesh_name, "MeshInstance3D", true, false)
|
||||
return null if matches.is_empty() else matches[0] as MeshInstance3D
|
||||
@@ -9,7 +9,6 @@ const CockpitMeshFactory = preload(
|
||||
)
|
||||
|
||||
var _materials: CockpitMaterialPalette
|
||||
var _tank_count := 0
|
||||
var _junction_box_count := 0
|
||||
var _control_count := 0
|
||||
|
||||
@@ -17,14 +16,7 @@ var _control_count := 0
|
||||
func _init(materials: CockpitMaterialPalette) -> void:
|
||||
_materials = materials
|
||||
name = "SideEquipment"
|
||||
_build_left_life_support_bank()
|
||||
_build_right_electrical_bank()
|
||||
_build_cable_looms()
|
||||
_build_control_rails()
|
||||
|
||||
|
||||
func tank_count() -> int:
|
||||
return _tank_count
|
||||
|
||||
|
||||
func junction_box_count() -> int:
|
||||
@@ -35,87 +27,11 @@ func control_count() -> int:
|
||||
return _control_count
|
||||
|
||||
|
||||
func _build_left_life_support_bank() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "LifeSupportBank"
|
||||
add_child(root)
|
||||
for tank_data: Vector3 in [
|
||||
Vector3(-0.78, 0.55, -1.17),
|
||||
Vector3(-0.68, 0.47, -1.19),
|
||||
]:
|
||||
_add_tank(root, tank_data)
|
||||
|
||||
_add_service_box(root, Vector3(-0.61, 0.42, -1.14), "O₂", -10.0)
|
||||
_add_indicator_column(root, Vector3(-0.55, 0.39, -1.055))
|
||||
_add_valve_wheel(root, Vector3(-0.66, 0.12, -1.08))
|
||||
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"ManifoldPipe",
|
||||
root,
|
||||
Vector3(-0.78, 0.24, -1.13),
|
||||
Vector3(-0.67, 0.09, -1.11),
|
||||
0.018,
|
||||
_materials.copper
|
||||
)
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"ManifoldReturn",
|
||||
root,
|
||||
Vector3(-0.68, 0.25, -1.15),
|
||||
Vector3(-0.59, 0.02, -1.10),
|
||||
0.014,
|
||||
_materials.brass
|
||||
)
|
||||
|
||||
|
||||
func _add_tank(root: Node3D, tank_position: Vector3) -> void:
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"OxygenTank",
|
||||
root,
|
||||
tank_position,
|
||||
0.055,
|
||||
0.43,
|
||||
_materials.safety_yellow
|
||||
)
|
||||
CockpitMeshFactory.add_sphere(
|
||||
"TankTop",
|
||||
root,
|
||||
tank_position + Vector3(0.0, 0.215, 0.0),
|
||||
0.055,
|
||||
_materials.safety_yellow
|
||||
)
|
||||
CockpitMeshFactory.add_sphere(
|
||||
"TankBottom",
|
||||
root,
|
||||
tank_position - Vector3(0.0, 0.215, 0.0),
|
||||
0.055,
|
||||
_materials.safety_yellow
|
||||
)
|
||||
for offset_y: float in [-0.13, 0.13]:
|
||||
var strap := MeshInstance3D.new()
|
||||
strap.name = "TankStrap"
|
||||
var torus := TorusMesh.new()
|
||||
torus.inner_radius = 0.054
|
||||
torus.outer_radius = 0.066
|
||||
torus.rings = 20
|
||||
torus.ring_segments = 6
|
||||
strap.mesh = torus
|
||||
strap.material_override = _materials.dark_steel
|
||||
strap.position = tank_position + Vector3(0.0, offset_y, 0.0)
|
||||
root.add_child(strap)
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"TankValve",
|
||||
root,
|
||||
tank_position + Vector3(0.0, 0.29, 0.0),
|
||||
0.017,
|
||||
0.075,
|
||||
_materials.brass
|
||||
)
|
||||
_tank_count += 1
|
||||
|
||||
|
||||
func _build_right_electrical_bank() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "ElectricalBank"
|
||||
# Seated on the flat end cap of the pressure hull, behind the sonar.
|
||||
root.position = Vector3(0.14, -0.18, -0.055)
|
||||
add_child(root)
|
||||
_add_service_box(root, Vector3(0.72, 0.65, -1.17), "ELEC", 11.0)
|
||||
_add_service_box(root, Vector3(0.73, 0.36, -1.16), "POMPES", 8.0)
|
||||
@@ -123,17 +39,6 @@ func _build_right_electrical_bank() -> void:
|
||||
_add_vent(root, Vector3(0.715, 0.40, -1.085))
|
||||
_add_knob(root, Vector3(0.66, 0.55, -1.08))
|
||||
_add_knob(root, Vector3(0.68, 0.25, -1.07))
|
||||
_add_indicator_column(root, Vector3(0.795, 0.49, -1.075))
|
||||
|
||||
for pipe_x: float in [0.64, 0.82]:
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"ElectricalConduit",
|
||||
root,
|
||||
Vector3(pipe_x, 0.70, -1.20),
|
||||
0.017,
|
||||
0.58,
|
||||
_materials.copper
|
||||
)
|
||||
|
||||
|
||||
func _add_service_box(
|
||||
@@ -198,123 +103,3 @@ func _add_knob(root: Node3D, knob_position: Vector3) -> void:
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
_control_count += 1
|
||||
|
||||
|
||||
func _add_indicator_column(root: Node3D, center: Vector3) -> void:
|
||||
var indicator_materials: Array[StandardMaterial3D] = [
|
||||
_materials.indicator_green,
|
||||
_materials.indicator_amber,
|
||||
_materials.indicator_red,
|
||||
]
|
||||
for index in 3:
|
||||
CockpitMeshFactory.add_box(
|
||||
"PilotLightBezel",
|
||||
root,
|
||||
center + Vector3(0.0, (1 - index) * 0.055, -0.004),
|
||||
Vector3(0.038, 0.038, 0.018),
|
||||
_materials.dark_steel
|
||||
)
|
||||
CockpitMeshFactory.add_sphere(
|
||||
"PilotLight",
|
||||
root,
|
||||
center + Vector3(0.0, (1 - index) * 0.055, 0.010),
|
||||
0.013,
|
||||
indicator_materials[index]
|
||||
)
|
||||
_control_count += 1
|
||||
|
||||
|
||||
func _add_valve_wheel(root: Node3D, wheel_position: Vector3) -> void:
|
||||
var wheel := MeshInstance3D.new()
|
||||
wheel.name = "ValveWheel"
|
||||
var torus := TorusMesh.new()
|
||||
torus.inner_radius = 0.034
|
||||
torus.outer_radius = 0.046
|
||||
torus.rings = 20
|
||||
torus.ring_segments = 7
|
||||
wheel.mesh = torus
|
||||
wheel.material_override = _materials.signal_red
|
||||
wheel.position = wheel_position
|
||||
wheel.rotation_degrees.x = 90.0
|
||||
root.add_child(wheel)
|
||||
for angle_degrees: float in [0.0, 45.0, 90.0, 135.0]:
|
||||
CockpitMeshFactory.add_box(
|
||||
"ValveSpoke",
|
||||
root,
|
||||
wheel_position + Vector3(0.0, 0.0, 0.002),
|
||||
Vector3(0.075, 0.007, 0.008),
|
||||
_materials.signal_red,
|
||||
Vector3(0.0, 0.0, angle_degrees)
|
||||
)
|
||||
_control_count += 1
|
||||
|
||||
|
||||
func _build_cable_looms() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "CableLooms"
|
||||
add_child(root)
|
||||
var left_points: Array[Vector3] = [
|
||||
Vector3(-0.82, 0.93, -1.15),
|
||||
Vector3(-0.72, 0.87, -1.16),
|
||||
Vector3(-0.64, 0.73, -1.14),
|
||||
Vector3(-0.61, 0.58, -1.12),
|
||||
]
|
||||
var right_points: Array[Vector3] = [
|
||||
Vector3(0.84, 0.91, -1.15),
|
||||
Vector3(0.75, 0.86, -1.16),
|
||||
Vector3(0.68, 0.73, -1.14),
|
||||
Vector3(0.65, 0.59, -1.12),
|
||||
]
|
||||
_add_cable_path(root, left_points)
|
||||
_add_cable_path(root, right_points)
|
||||
|
||||
|
||||
func _add_cable_path(root: Node3D, points: Array[Vector3]) -> void:
|
||||
for point_index in points.size() - 1:
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"ArmouredCable",
|
||||
root,
|
||||
points[point_index],
|
||||
points[point_index + 1],
|
||||
0.012,
|
||||
_materials.rubber
|
||||
)
|
||||
|
||||
|
||||
func _build_control_rails() -> void:
|
||||
var root := Node3D.new()
|
||||
root.name = "ControlRails"
|
||||
add_child(root)
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"LowerSteelRail",
|
||||
root,
|
||||
Vector3(-0.82, -0.34, -1.12),
|
||||
Vector3(0.82, -0.34, -1.12),
|
||||
0.026,
|
||||
_materials.brushed_steel
|
||||
)
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"LeftGrip",
|
||||
root,
|
||||
Vector3(-0.58, -0.34, -1.11),
|
||||
Vector3(-0.18, -0.34, -1.11),
|
||||
0.034,
|
||||
_materials.rubber
|
||||
)
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"RightGrip",
|
||||
root,
|
||||
Vector3(0.18, -0.34, -1.11),
|
||||
Vector3(0.58, -0.34, -1.11),
|
||||
0.034,
|
||||
_materials.rubber
|
||||
)
|
||||
for x: float in [-0.82, 0.82]:
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"RailSupport",
|
||||
root,
|
||||
Vector3(x, -0.34, -1.12),
|
||||
Vector3(x, -0.20, -1.08),
|
||||
0.022,
|
||||
_materials.brushed_steel
|
||||
)
|
||||
@@ -1,145 +1,36 @@
|
||||
class_name CockpitSonarConsole
|
||||
extends Node3D
|
||||
|
||||
const CockpitMaterialPalette = preload(
|
||||
"res://game/presentation/cockpit/cockpit_material_palette.gd"
|
||||
)
|
||||
const CockpitMeshFactory = preload(
|
||||
"res://game/presentation/cockpit/cockpit_mesh_factory.gd"
|
||||
)
|
||||
## Imported sonar monitor. Exposes its glass as the cockpit's `%SonarScreen` so
|
||||
## the instrument panel can put the shared sonar texture on it.
|
||||
|
||||
var _materials: CockpitMaterialPalette
|
||||
var _control_count := 0
|
||||
## Lamps and knobs are modelled into the housing mesh, not separate nodes:
|
||||
## three amber push-buttons and two knurled knobs (model contract).
|
||||
const HOUSING_CONTROL_COUNT := 5
|
||||
|
||||
var _housing: MeshInstance3D
|
||||
var _screen: MeshInstance3D
|
||||
|
||||
|
||||
func _init(materials: CockpitMaterialPalette) -> void:
|
||||
_materials = materials
|
||||
name = "SonarConsoleDetailing"
|
||||
_build_bezel()
|
||||
_build_grid()
|
||||
_build_control_strip()
|
||||
_build_mounting_hardware()
|
||||
func _ready() -> void:
|
||||
_housing = find_child("Sonar", true, false) as MeshInstance3D
|
||||
_screen = find_child("SonarScreen", true, false) as MeshInstance3D
|
||||
if _housing == null or _screen == null:
|
||||
push_error("Sonar model is missing its Sonar or SonarScreen node")
|
||||
return
|
||||
# The glass belongs to the imported scene; adopt it so the cockpit scene
|
||||
# can address it by unique name like any other instrument.
|
||||
_screen.owner = owner
|
||||
_screen.unique_name_in_owner = true
|
||||
|
||||
|
||||
func housing() -> MeshInstance3D:
|
||||
return _housing
|
||||
|
||||
|
||||
func screen() -> MeshInstance3D:
|
||||
return _screen
|
||||
|
||||
|
||||
func control_count() -> int:
|
||||
return _control_count
|
||||
|
||||
|
||||
func _build_bezel() -> void:
|
||||
CockpitMeshFactory.add_box(
|
||||
"UpperBezel",
|
||||
self,
|
||||
Vector3(-0.025, 0.151, 0.073),
|
||||
Vector3(0.41, 0.034, 0.035),
|
||||
_materials.brushed_steel
|
||||
)
|
||||
CockpitMeshFactory.add_box(
|
||||
"LowerBezel",
|
||||
self,
|
||||
Vector3(-0.025, -0.126, 0.073),
|
||||
Vector3(0.41, 0.034, 0.035),
|
||||
_materials.brushed_steel
|
||||
)
|
||||
for x: float in [-0.225, 0.175]:
|
||||
CockpitMeshFactory.add_box(
|
||||
"SideBezel",
|
||||
self,
|
||||
Vector3(x, 0.012, 0.073),
|
||||
Vector3(0.035, 0.31, 0.035),
|
||||
_materials.brushed_steel
|
||||
)
|
||||
var label := Label3D.new()
|
||||
label.name = "SonarPlate"
|
||||
label.text = "SONAR · BATHYMÉTRIE"
|
||||
label.font_size = 12
|
||||
label.pixel_size = 0.00062
|
||||
label.modulate = Color(0.7, 0.73, 0.64)
|
||||
label.position = Vector3(-0.04, 0.156, 0.097)
|
||||
add_child(label)
|
||||
|
||||
|
||||
func _build_control_strip() -> void:
|
||||
CockpitMeshFactory.add_box(
|
||||
"ControlStrip",
|
||||
self,
|
||||
Vector3(0.235, 0.012, 0.072),
|
||||
Vector3(0.075, 0.31, 0.04),
|
||||
_materials.dark_steel
|
||||
)
|
||||
var lamps: Array[StandardMaterial3D] = [
|
||||
_materials.indicator_amber,
|
||||
_materials.indicator_amber,
|
||||
_materials.indicator_green,
|
||||
]
|
||||
for index in 3:
|
||||
var y := 0.095 - index * 0.073
|
||||
CockpitMeshFactory.add_box(
|
||||
"SonarLampBezel",
|
||||
self,
|
||||
Vector3(0.235, y, 0.101),
|
||||
Vector3(0.044, 0.044, 0.018),
|
||||
_materials.brass
|
||||
)
|
||||
CockpitMeshFactory.add_sphere(
|
||||
"SonarLamp",
|
||||
self,
|
||||
Vector3(0.235, y, 0.116),
|
||||
0.013,
|
||||
lamps[index]
|
||||
)
|
||||
_control_count += 1
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"SonarGainKnob",
|
||||
self,
|
||||
Vector3(0.235, -0.102, 0.108),
|
||||
0.024,
|
||||
0.026,
|
||||
_materials.rubber,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
_control_count += 1
|
||||
|
||||
|
||||
func _build_grid() -> void:
|
||||
for x: float in [-0.145, -0.085, -0.025, 0.035, 0.095]:
|
||||
CockpitMeshFactory.add_box(
|
||||
"SonarGridVertical",
|
||||
self,
|
||||
Vector3(x, 0.012, 0.076),
|
||||
Vector3(0.0015, 0.215, 0.002),
|
||||
_materials.sonar_grid
|
||||
)
|
||||
for y: float in [-0.075, -0.025, 0.025, 0.075]:
|
||||
CockpitMeshFactory.add_box(
|
||||
"SonarGridHorizontal",
|
||||
self,
|
||||
Vector3(-0.025, y, 0.076),
|
||||
Vector3(0.33, 0.0015, 0.002),
|
||||
_materials.sonar_grid
|
||||
)
|
||||
|
||||
|
||||
func _build_mounting_hardware() -> void:
|
||||
for corner: Vector2 in [
|
||||
Vector2(-0.205, -0.105),
|
||||
Vector2(-0.205, 0.13),
|
||||
Vector2(0.195, -0.105),
|
||||
Vector2(0.195, 0.13),
|
||||
]:
|
||||
CockpitMeshFactory.add_cylinder(
|
||||
"SonarFastener",
|
||||
self,
|
||||
Vector3(corner.x, corner.y, 0.103),
|
||||
0.008,
|
||||
0.012,
|
||||
_materials.brass,
|
||||
Vector3(90.0, 0.0, 0.0)
|
||||
)
|
||||
CockpitMeshFactory.add_cylinder_between(
|
||||
"LeftSonarHandle",
|
||||
self,
|
||||
Vector3(-0.255, -0.09, 0.06),
|
||||
Vector3(-0.255, 0.11, 0.06),
|
||||
0.013,
|
||||
_materials.brass
|
||||
)
|
||||
return 0 if _housing == null else HOUSING_CONTROL_COUNT
|
||||
@@ -0,0 +1,26 @@
|
||||
class_name CockpitSwitchPanel
|
||||
extends Node3D
|
||||
|
||||
## Imported switch panel. Decorative in this slice: every control stays in its
|
||||
## exported rest pose and no input reaches it.
|
||||
|
||||
const CONTROL_PREFIXES: Array[String] = ["Toggle_", "Rocker_", "Cover_", "Lever_"]
|
||||
|
||||
var _controls: Array[Node3D] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for prefix: String in CONTROL_PREFIXES:
|
||||
for control: Node in find_children("%s*" % prefix, "Node3D", true, false):
|
||||
_controls.append(control as Node3D)
|
||||
|
||||
|
||||
func control_count() -> int:
|
||||
return _controls.size()
|
||||
|
||||
|
||||
func controls_are_at_rest() -> bool:
|
||||
for control: Node3D in _controls:
|
||||
if not control.rotation.is_zero_approx():
|
||||
return false
|
||||
return true
|
||||
@@ -61,6 +61,10 @@ func built_grid_segment_count() -> int:
|
||||
return _renderer.built_grid_segment_count()
|
||||
|
||||
|
||||
func depth_scale_texts() -> PackedStringArray:
|
||||
return _renderer.depth_scale_texts()
|
||||
|
||||
|
||||
func idle_prompt_visible() -> bool:
|
||||
return _renderer.idle_prompt_visible()
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ script = ExtResource("1_display")
|
||||
|
||||
[node name="SonarReliefRenderer" type="SubViewport" parent="."]
|
||||
unique_name_in_owner = true
|
||||
size = Vector2i(960, 540)
|
||||
size = Vector2i(960, 604)
|
||||
render_target_update_mode = 4
|
||||
script = ExtResource("2_renderer")
|
||||
|
||||
@@ -77,4 +77,4 @@ text = "SONAR PRÊT · F POUR ÉMETTRE"
|
||||
[node name="Legend" type="Label" parent="ExpandedPanel/Layout"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.58, 0.7, 0.71, 1)
|
||||
text = "BLEU PROFOND · VERT/JAUNE HAUTEUR · ORANGE ANOMALIE · NOIR INCONNU · TAB FERMER"
|
||||
text = "TURQUOISE SOMBRE PROFOND · CYAN CLAIR HAUTEUR · ORANGE ANOMALIE · NOIR INCONNU · TAB FERMER"
|
||||
@@ -5,53 +5,59 @@ 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")
|
||||
|
||||
const COLOR_DEEP := Color("173f91")
|
||||
const COLOR_LOW := Color("168fc1")
|
||||
const COLOR_MIDDLE := Color("36b979")
|
||||
const COLOR_HIGH := Color("d7d93d")
|
||||
const COLOR_PEAK := Color("ee5734")
|
||||
const COLOR_METAL := Color("ff6b22")
|
||||
## 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: StandardMaterial3D
|
||||
) -> ArrayMesh:
|
||||
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: StandardMaterial3D
|
||||
material: Material
|
||||
) -> int:
|
||||
var vertices := PackedVector3Array()
|
||||
var normals := PackedVector3Array()
|
||||
var colors := PackedColorArray()
|
||||
var uvs := PackedVector2Array()
|
||||
for column in range(scan.samples.columns - 1):
|
||||
var top_left: SonarSample = scan.samples.sample_at(row - 1, column)
|
||||
var top_right: SonarSample = scan.samples.sample_at(row - 1, column + 1)
|
||||
var bottom_left: SonarSample = scan.samples.sample_at(row, column)
|
||||
var bottom_right: SonarSample = scan.samples.sample_at(row, column + 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 (
|
||||
top_left.observed
|
||||
and top_right.observed
|
||||
and bottom_left.observed
|
||||
and bottom_right.observed
|
||||
corners[0].observed
|
||||
and corners[1].observed
|
||||
and corners[2].observed
|
||||
and corners[3].observed
|
||||
):
|
||||
continue
|
||||
_append_triangle(
|
||||
vertices, normals, colors, scan,
|
||||
top_left, bottom_left, top_right
|
||||
)
|
||||
_append_triangle(
|
||||
vertices, normals, colors, scan,
|
||||
top_right, bottom_left, bottom_right
|
||||
)
|
||||
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 = []
|
||||
@@ -59,93 +65,21 @@ static func add_row_band(
|
||||
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 add_grid_band(
|
||||
mesh: ArrayMesh,
|
||||
scan: SonarScan,
|
||||
row: int,
|
||||
material: StandardMaterial3D
|
||||
) -> int:
|
||||
var vertices := PackedVector3Array()
|
||||
for column in scan.samples.columns:
|
||||
if column % 6 != 0 and column != scan.samples.columns - 1:
|
||||
continue
|
||||
_append_grid_segment(
|
||||
vertices,
|
||||
scan.samples.sample_at(row - 1, column),
|
||||
scan.samples.sample_at(row, column)
|
||||
)
|
||||
if row == 1 or row % 4 == 0 or row == scan.samples.rows - 1:
|
||||
for column in range(scan.samples.columns - 1):
|
||||
_append_grid_segment(
|
||||
vertices,
|
||||
scan.samples.sample_at(row, column),
|
||||
scan.samples.sample_at(row, column + 1)
|
||||
)
|
||||
if vertices.is_empty():
|
||||
return 0
|
||||
var arrays: Array = []
|
||||
arrays.resize(Mesh.ARRAY_MAX)
|
||||
arrays[Mesh.ARRAY_VERTEX] = vertices
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_LINES, arrays)
|
||||
mesh.surface_set_material(mesh.get_surface_count() - 1, material)
|
||||
return vertices.size() / 2
|
||||
|
||||
|
||||
static func _append_grid_segment(
|
||||
vertices: PackedVector3Array,
|
||||
a: SonarSample,
|
||||
b: SonarSample
|
||||
) -> void:
|
||||
if not a.observed or not b.observed:
|
||||
return
|
||||
vertices.append(a.position_m + a.normal * 0.08)
|
||||
vertices.append(b.position_m + b.normal * 0.08)
|
||||
|
||||
|
||||
static func _append_triangle(
|
||||
vertices: PackedVector3Array,
|
||||
normals: PackedVector3Array,
|
||||
colors: PackedColorArray,
|
||||
scan: SonarScan,
|
||||
a: SonarSample,
|
||||
b: SonarSample,
|
||||
c: SonarSample
|
||||
) -> void:
|
||||
for sample: SonarSample in [a, b, c]:
|
||||
vertices.append(sample.position_m)
|
||||
normals.append(sample.normal)
|
||||
colors.append(_sample_color(scan, sample))
|
||||
|
||||
|
||||
static func _sample_color(scan: SonarScan, sample: SonarSample) -> Color:
|
||||
if sample.material == SonarMaterial.Value.METAL:
|
||||
return COLOR_METAL
|
||||
var height := inverse_lerp(
|
||||
scan.minimum_elevation_m,
|
||||
scan.maximum_elevation_m,
|
||||
sample.elevation_m
|
||||
)
|
||||
var color: Color
|
||||
if height <= 0.28:
|
||||
color = COLOR_DEEP.lerp(COLOR_LOW, height / 0.28)
|
||||
elif height <= 0.52:
|
||||
color = COLOR_LOW.lerp(COLOR_MIDDLE, (height - 0.28) / 0.24)
|
||||
elif height <= 0.76:
|
||||
color = COLOR_MIDDLE.lerp(COLOR_HIGH, (height - 0.52) / 0.24)
|
||||
else:
|
||||
color = COLOR_HIGH.lerp(COLOR_PEAK, (height - 0.76) / 0.24)
|
||||
var light_direction := Vector3(-0.35, 0.82, -0.45).normalized()
|
||||
var slope_light := clampf(sample.normal.dot(light_direction), 0.0, 1.0)
|
||||
var shade := lerpf(0.68, 1.0, slope_light)
|
||||
var lit_color := color.lerp(Color.WHITE, sample.intensity * 0.08)
|
||||
return Color(
|
||||
lit_color.r * shade,
|
||||
lit_color.g * shade,
|
||||
lit_color.b * shade,
|
||||
lit_color.a
|
||||
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))
|
||||
@@ -5,15 +5,32 @@ const SonarScan = preload("res://game/domain/sonar/sonar_scan.gd")
|
||||
const SonarReliefMeshBuilder = preload(
|
||||
"res://game/presentation/sonar/sonar_relief_mesh_builder.gd"
|
||||
)
|
||||
const SonarReliefStyle = preload("res://game/presentation/sonar/sonar_relief_style.gd")
|
||||
|
||||
## Matches the glass of the cockpit sonar monitor (0.690 × 0.434, ratio 1.59).
|
||||
const RENDER_SIZE := Vector2i(960, 604)
|
||||
## Ground width framed by the orthographic camera, independent of the ratio.
|
||||
const VIEW_WIDTH_M := 178.0
|
||||
## Table-like oblique view from behind the vehicle. The aim point sits right
|
||||
## of the scan axis so the relief leaves the right edge to the height scale.
|
||||
const CAMERA_AIM_POINT := Vector3(14.0, -46.0, 50.0)
|
||||
const CAMERA_ELEVATION_DEGREES := 44.0
|
||||
const CAMERA_DISTANCE_M := 120.0
|
||||
const IDLE_PROMPT_SIZE := Vector2(600.0, 64.0)
|
||||
## Height scale drawn at the right edge of the screen, in viewport pixels.
|
||||
const SCALE_BAR_RECT := Rect2(920.0, 70.0, 12.0, 464.0)
|
||||
const SCALE_TICK_COUNT := 3
|
||||
const SCALE_LABEL_SIZE := Vector2(112.0, 40.0)
|
||||
const SCALE_FONT_SIZE := 34
|
||||
|
||||
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_material: ShaderMaterial
|
||||
var _previous_material: ShaderMaterial
|
||||
var _depth_scale: TextureRect
|
||||
var _scale_labels: Array[Label] = []
|
||||
var _current_scan_id: StringName = &""
|
||||
var _previous_scan_id: StringName = &""
|
||||
var _built_current_rows := 0
|
||||
@@ -22,9 +39,8 @@ var _built_current_grid_segment_count := 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
size = Vector2i(960, 540)
|
||||
size = RENDER_SIZE
|
||||
render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||
positional_shadow_atlas_size = 1024
|
||||
own_world_3d = true
|
||||
_build_render_world()
|
||||
|
||||
@@ -41,11 +57,11 @@ func present(
|
||||
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)
|
||||
_update_depth_scale(current_scan)
|
||||
_build_current_rows(current_scan, clampi(visible_rows, 0, current_scan.samples.rows))
|
||||
_update_contact_visibility(current_scan, visible_rows)
|
||||
|
||||
@@ -61,7 +77,7 @@ func present(
|
||||
previous_scan,
|
||||
_previous_material
|
||||
)
|
||||
_previous_material.albedo_color.a = clampf(previous_opacity, 0.0, 1.0)
|
||||
SonarReliefStyle.set_opacity(_previous_material, previous_opacity)
|
||||
|
||||
|
||||
func render_texture() -> Texture2D:
|
||||
@@ -84,6 +100,16 @@ func idle_prompt_visible() -> bool:
|
||||
return _idle_prompt != null and _idle_prompt.visible
|
||||
|
||||
|
||||
## Elevation labels of the height scale, top (highest) to bottom; empty until
|
||||
## a scan has been presented.
|
||||
func depth_scale_texts() -> PackedStringArray:
|
||||
var texts := PackedStringArray()
|
||||
if _depth_scale != null and _depth_scale.visible:
|
||||
for label: Label in _scale_labels:
|
||||
texts.append(label.text)
|
||||
return texts
|
||||
|
||||
|
||||
func visible_contact_count() -> int:
|
||||
var count := 0
|
||||
for child: Node in _contact_root.get_children():
|
||||
@@ -96,24 +122,17 @@ 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.background_color = SonarReliefStyle.COLOR_BACKGROUND
|
||||
environment.ambient_light_source = Environment.AMBIENT_SOURCE_DISABLED
|
||||
environment_node.environment = environment
|
||||
add_child(environment_node)
|
||||
|
||||
_current_material = _relief_material(false)
|
||||
_grid_material = _surface_grid_material()
|
||||
_previous_material = _relief_material(true)
|
||||
_current_material = SonarReliefStyle.relief_material(false)
|
||||
_previous_material = SonarReliefStyle.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()
|
||||
@@ -128,72 +147,88 @@ func _build_render_world() -> void:
|
||||
var camera := Camera3D.new()
|
||||
camera.name = "SonarCamera"
|
||||
camera.projection = Camera3D.PROJECTION_ORTHOGONAL
|
||||
camera.size = 116.0
|
||||
camera.keep_aspect = Camera3D.KEEP_WIDTH
|
||||
camera.size = VIEW_WIDTH_M
|
||||
camera.far = 240.0
|
||||
camera.position = Vector3(0.0, 65.0, -30.0)
|
||||
var elevation := deg_to_rad(CAMERA_ELEVATION_DEGREES)
|
||||
camera.position = CAMERA_AIM_POINT + CAMERA_DISTANCE_M * Vector3(
|
||||
0.0, sin(elevation), -cos(elevation)
|
||||
)
|
||||
add_child(camera)
|
||||
camera.look_at(Vector3(0.0, -38.0, 58.0), Vector3.UP)
|
||||
camera.look_at(CAMERA_AIM_POINT, 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_depth_scale()
|
||||
_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.position = (Vector2(RENDER_SIZE) - IDLE_PROMPT_SIZE) * 0.5
|
||||
_idle_prompt.size = IDLE_PROMPT_SIZE
|
||||
_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_color", SonarReliefStyle.COLOR_MIDDLE)
|
||||
_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
|
||||
## Height legend: the relief palette is relative to the scan, so the bar shows
|
||||
## the real elevations of its two ends and of the steps in between.
|
||||
func _build_depth_scale() -> void:
|
||||
var gradient := GradientTexture2D.new()
|
||||
gradient.gradient = SonarReliefStyle.height_gradient()
|
||||
gradient.fill_from = Vector2(0.0, 1.0)
|
||||
gradient.fill_to = Vector2(0.0, 0.0)
|
||||
gradient.width = int(SCALE_BAR_RECT.size.x)
|
||||
gradient.height = int(SCALE_BAR_RECT.size.y)
|
||||
_depth_scale = TextureRect.new()
|
||||
_depth_scale.name = "DepthScale"
|
||||
_depth_scale.texture = gradient
|
||||
_depth_scale.position = SCALE_BAR_RECT.position
|
||||
_depth_scale.size = SCALE_BAR_RECT.size
|
||||
_depth_scale.visible = false
|
||||
add_child(_depth_scale)
|
||||
for tick in SCALE_TICK_COUNT:
|
||||
var label := Label.new()
|
||||
label.size = SCALE_LABEL_SIZE
|
||||
label.position = Vector2(
|
||||
-label.size.x - 8.0,
|
||||
SCALE_BAR_RECT.size.y * tick / float(SCALE_TICK_COUNT - 1) - label.size.y * 0.5
|
||||
)
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
label.add_theme_font_size_override("font_size", SCALE_FONT_SIZE)
|
||||
label.add_theme_color_override("font_color", SonarReliefStyle.COLOR_MIDDLE)
|
||||
_depth_scale.add_child(label)
|
||||
_scale_labels.append(label)
|
||||
|
||||
|
||||
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 _update_depth_scale(scan: SonarScan) -> void:
|
||||
for tick in _scale_labels.size():
|
||||
var elevation := lerpf(
|
||||
scan.maximum_elevation_m,
|
||||
scan.minimum_elevation_m,
|
||||
tick / float(_scale_labels.size() - 1)
|
||||
)
|
||||
_scale_labels[tick].text = "%.0f m" % elevation
|
||||
_depth_scale.visible = true
|
||||
|
||||
|
||||
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(
|
||||
var triangles := 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_triangle_count += triangles
|
||||
_built_current_grid_segment_count += (
|
||||
triangles / 2 * SonarReliefMeshBuilder.GRID_SEGMENTS_PER_CELL
|
||||
)
|
||||
_built_current_rows += 1
|
||||
|
||||
@@ -210,13 +245,9 @@ func _rebuild_contacts(scan: SonarScan) -> void:
|
||||
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
|
||||
marker.material_override = SonarReliefStyle.marker_material(
|
||||
SonarReliefStyle.COLOR_METAL
|
||||
)
|
||||
_contact_root.add_child(marker)
|
||||
|
||||
|
||||
@@ -244,11 +275,7 @@ func _add_submarine_marker() -> void:
|
||||
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
|
||||
marker.material_override = SonarReliefStyle.marker_material(
|
||||
SonarReliefStyle.COLOR_VEHICLE
|
||||
)
|
||||
add_child(marker)
|
||||
@@ -0,0 +1,157 @@
|
||||
class_name SonarReliefStyle
|
||||
extends RefCounted
|
||||
|
||||
## Single owner of the holographic look of the sonar relief: palette, grid
|
||||
## spacing, line widths and intensities. Display only: nothing here is a
|
||||
## gameplay value.
|
||||
|
||||
## Near-black table the hologram floats on. Black also means "unknown".
|
||||
const COLOR_BACKGROUND := Color("010507")
|
||||
## Height ramp, relative to the scan: dark teal low, cyan mid, pale on peaks.
|
||||
const COLOR_DEEP := Color("06404a")
|
||||
const COLOR_MIDDLE := Color("17b8c4")
|
||||
const COLOR_PEAK := Color("d8fbff")
|
||||
## Metal returns keep one warm accent so an anomaly never reads as terrain.
|
||||
const COLOR_METAL := Color("ff7a2a")
|
||||
const COLOR_VEHICLE := Color("f2ffff")
|
||||
|
||||
## Grid lines follow the sample grid: one minor line every N cells.
|
||||
const GRID_MINOR_CELLS := 2.0
|
||||
const GRID_MAJOR_CELLS := 6.0
|
||||
const GRID_MINOR_WIDTH_PX := 1.2
|
||||
const GRID_MAJOR_WIDTH_PX := 2.2
|
||||
const GRID_MINOR_INTENSITY := 0.30
|
||||
const GRID_MAJOR_INTENSITY := 0.80
|
||||
## Iso-height contours, in fractions of the scan's height span.
|
||||
const CONTOUR_STEP := 0.125
|
||||
const CONTOUR_WIDTH_PX := 1.0
|
||||
const CONTOUR_INTENSITY := 0.20
|
||||
## Translucent surface under the grid and the rim that outlines ridges.
|
||||
const SURFACE_INTENSITY := 0.50
|
||||
const RIM_INTENSITY := 0.35
|
||||
const RIM_POWER := 2.5
|
||||
## How much brighter the peaks' lines are than the deep ones.
|
||||
const PEAK_LINE_BOOST := 0.55
|
||||
|
||||
const _SHADER_CODE := """
|
||||
shader_type spatial;
|
||||
render_mode unshaded, cull_disabled, depth_draw_always;
|
||||
|
||||
uniform vec3 background : source_color;
|
||||
uniform vec3 color_deep : source_color;
|
||||
uniform vec3 color_middle : source_color;
|
||||
uniform vec3 color_peak : source_color;
|
||||
uniform vec3 color_metal : source_color;
|
||||
uniform float grid_minor_cells;
|
||||
uniform float grid_major_cells;
|
||||
uniform float grid_minor_width_px;
|
||||
uniform float grid_major_width_px;
|
||||
uniform float grid_minor_intensity;
|
||||
uniform float grid_major_intensity;
|
||||
uniform float contour_step;
|
||||
uniform float contour_width_px;
|
||||
uniform float contour_intensity;
|
||||
uniform float surface_intensity;
|
||||
uniform float rim_intensity;
|
||||
uniform float rim_power;
|
||||
uniform float peak_line_boost;
|
||||
uniform float opacity = 1.0;
|
||||
|
||||
// Anti-aliased line on every multiple of `spacing`, `width_px` wide on screen.
|
||||
// Fades out when lines would be closer than a few pixels, so it never shimmers.
|
||||
float grid_line(float coordinate, float spacing, float width_px) {
|
||||
float scaled = coordinate / spacing;
|
||||
float pixel = max(fwidth(scaled), 1e-5);
|
||||
float distance_px = abs(fract(scaled - 0.5) - 0.5) / pixel;
|
||||
float line = 1.0 - smoothstep(width_px * 0.5 - 0.5, width_px * 0.5 + 0.5, distance_px);
|
||||
return line * clamp((1.0 / pixel - 2.0) / 3.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
// UV carries (column, row) of the sample grid; COLOR.r the relative height,
|
||||
// COLOR.g the metal flag, COLOR.b the echo intensity.
|
||||
float height = clamp(COLOR.r, 0.0, 1.0);
|
||||
vec3 ramp = mix(color_deep, color_middle, smoothstep(0.0, 0.55, height));
|
||||
ramp = mix(ramp, color_peak, smoothstep(0.55, 1.0, height));
|
||||
// The translucent surface stays in the teal-to-cyan range so peaks do not
|
||||
// wash out to grey; only the lines reach the pale peak colour.
|
||||
vec3 fill = mix(color_deep, color_middle, smoothstep(0.0, 0.85, height));
|
||||
float metal = step(0.5, COLOR.g);
|
||||
ramp = mix(ramp, color_metal, metal);
|
||||
fill = mix(fill, color_metal, metal);
|
||||
|
||||
float minor = max(
|
||||
grid_line(UV.x, grid_minor_cells, grid_minor_width_px),
|
||||
grid_line(UV.y, grid_minor_cells, grid_minor_width_px)
|
||||
);
|
||||
float major = max(
|
||||
grid_line(UV.x, grid_major_cells, grid_major_width_px),
|
||||
grid_line(UV.y, grid_major_cells, grid_major_width_px)
|
||||
);
|
||||
float contour = grid_line(height, contour_step, contour_width_px);
|
||||
float rim = pow(1.0 - abs(dot(normalize(NORMAL), normalize(VIEW))), rim_power);
|
||||
|
||||
float lines = max(minor * grid_minor_intensity, major * grid_major_intensity);
|
||||
lines = max(lines, contour * contour_intensity);
|
||||
lines *= 1.0 + peak_line_boost * height;
|
||||
float surface = surface_intensity * (0.22 + 0.78 * height) * (0.8 + 0.2 * COLOR.b);
|
||||
vec3 glow = fill * (surface + rim * rim_intensity) + ramp * lines;
|
||||
|
||||
ALBEDO = background + glow;
|
||||
//FADE ALPHA = opacity;
|
||||
}
|
||||
"""
|
||||
|
||||
static var _opaque_shader: Shader
|
||||
static var _fading_shader: Shader
|
||||
|
||||
|
||||
## The current scan is opaque: depth-tested, so ridges hide what lies behind
|
||||
## them and nothing depends on draw order. The previous scan fades out with the
|
||||
## same shader, alpha-blended and still writing depth.
|
||||
static func relief_material(fading: bool) -> ShaderMaterial:
|
||||
if _opaque_shader == null:
|
||||
_opaque_shader = Shader.new()
|
||||
_opaque_shader.code = _SHADER_CODE
|
||||
_fading_shader = Shader.new()
|
||||
_fading_shader.code = _SHADER_CODE.replace("//FADE ", "")
|
||||
var material := ShaderMaterial.new()
|
||||
material.shader = _fading_shader if fading else _opaque_shader
|
||||
material.set_shader_parameter("background", COLOR_BACKGROUND)
|
||||
material.set_shader_parameter("color_deep", COLOR_DEEP)
|
||||
material.set_shader_parameter("color_middle", COLOR_MIDDLE)
|
||||
material.set_shader_parameter("color_peak", COLOR_PEAK)
|
||||
material.set_shader_parameter("color_metal", COLOR_METAL)
|
||||
material.set_shader_parameter("grid_minor_cells", GRID_MINOR_CELLS)
|
||||
material.set_shader_parameter("grid_major_cells", GRID_MAJOR_CELLS)
|
||||
material.set_shader_parameter("grid_minor_width_px", GRID_MINOR_WIDTH_PX)
|
||||
material.set_shader_parameter("grid_major_width_px", GRID_MAJOR_WIDTH_PX)
|
||||
material.set_shader_parameter("grid_minor_intensity", GRID_MINOR_INTENSITY)
|
||||
material.set_shader_parameter("grid_major_intensity", GRID_MAJOR_INTENSITY)
|
||||
material.set_shader_parameter("contour_step", CONTOUR_STEP)
|
||||
material.set_shader_parameter("contour_width_px", CONTOUR_WIDTH_PX)
|
||||
material.set_shader_parameter("contour_intensity", CONTOUR_INTENSITY)
|
||||
material.set_shader_parameter("surface_intensity", SURFACE_INTENSITY)
|
||||
material.set_shader_parameter("rim_intensity", RIM_INTENSITY)
|
||||
material.set_shader_parameter("rim_power", RIM_POWER)
|
||||
material.set_shader_parameter("peak_line_boost", PEAK_LINE_BOOST)
|
||||
return material
|
||||
|
||||
|
||||
static func set_opacity(material: ShaderMaterial, opacity: float) -> void:
|
||||
material.set_shader_parameter("opacity", clampf(opacity, 0.0, 1.0))
|
||||
|
||||
|
||||
static func marker_material(color: Color) -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
material.albedo_color = color
|
||||
return material
|
||||
|
||||
|
||||
## Colour of a relative height (0 low, 1 peak), for the depth scale legend.
|
||||
static func height_gradient() -> Gradient:
|
||||
var gradient := Gradient.new()
|
||||
gradient.offsets = PackedFloat32Array([0.0, 0.55, 1.0])
|
||||
gradient.colors = PackedColorArray([COLOR_DEEP, COLOR_MIDDLE, COLOR_PEAK])
|
||||
return gradient
|
||||
Reference in new issue
Block a user