Refine cockpit porthole and indicators
This commit is contained in:
1 parent
53288b48c8
commit
c2b3ac42a3
8 files changed
+298
-10
No files matched your search
@@ -18,9 +18,10 @@ production sous `game/` n'en dépend.
|
|||||||
- `DiveUpdate` cohérent composé d'un snapshot et d'événements typés.
|
- `DiveUpdate` cohérent composé d'un snapshot et d'événements typés.
|
||||||
- Acteur sous-marin de production derrière des contrats de commande et de
|
- Acteur sous-marin de production derrière des contrats de commande et de
|
||||||
télémétrie typés.
|
télémétrie typés.
|
||||||
- Habitacle 3D solidaire de la coque : hublot occultant, consoles, commandes,
|
- Habitacle 3D solidaire de la coque : hublot à cloison annulaire bombée,
|
||||||
éclairage, structure de coque renforcée, conduites, jauges analogiques et
|
consoles, commandes, éclairage, structure de coque renforcée, conduites,
|
||||||
tableau d'instruments structuré, et emplacement réservé au sonar.
|
jauges analogiques graduées, tableau d'instruments structuré et emplacement
|
||||||
|
réservé au sonar.
|
||||||
- Instruments physiques à valeurs réelles ; le HUD reste réservé aux alertes,
|
- Instruments physiques à valeurs réelles ; le HUD reste réservé aux alertes,
|
||||||
à l'éjection et aux états terminaux.
|
à l'éjection et aux états terminaux.
|
||||||
- Pause qui suspend les ticks, confirmations de sortie et premier remappage avec
|
- Pause qui suspend les ticks, confirmations de sortie et premier remappage avec
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Statut
|
## Statut
|
||||||
|
|
||||||
- Date : 10 août 2026.
|
- Date : 10 août 2026.
|
||||||
- Dernière révision d'implémentation : 3 octobre 2026.
|
- Dernière révision d'implémentation : 4 octobre 2026.
|
||||||
- État : cadrage approuvé, tranches 0, 1 et 1.5 implémentées ; tranche 2 en cours.
|
- État : cadrage approuvé, tranches 0, 1 et 1.5 implémentées ; tranche 2 en cours.
|
||||||
- Jalon : verticale jouable, après validation des POC A et B.
|
- Jalon : verticale jouable, après validation des POC A et B.
|
||||||
- Durée directrice du plan général : quatre à six mois.
|
- Durée directrice du plan général : quatre à six mois.
|
||||||
@@ -815,6 +815,12 @@ Les jauges de charge batterie et de pression coque sont regroupées dans un
|
|||||||
binnacle solidaire du tableau de bord, hors du hublot. La façade du tableau est
|
binnacle solidaire du tableau de bord, hors du hublot. La façade du tableau est
|
||||||
structurée par des rails, séparateurs, vis et banques de commutateurs physiques.
|
structurée par des rails, séparateurs, vis et banques de commutateurs physiques.
|
||||||
|
|
||||||
|
Passe du 4 octobre 2026 : les quatre volumes rectangulaires autour du hublot
|
||||||
|
sont remplacés par douze panneaux annulaires bombés, séparés par des joints fins
|
||||||
|
et ceinturés par un raidisseur. Les cadrans utilisent des graduations physiques
|
||||||
|
et les témoins colorés provisoires sont remplacés par trois voyants encastrés ;
|
||||||
|
un seul voyant reflète l'état d'alerte courant.
|
||||||
|
|
||||||
- Construire une coque intérieure 3D fixe par rapport au véhicule, indépendante
|
- Construire une coque intérieure 3D fixe par rapport au véhicule, indépendante
|
||||||
du regard de la caméra.
|
du regard de la caméra.
|
||||||
- Remplacer le cercle Canvas par un hublot 3D occultant avec cadre, joint, verre,
|
- Remplacer le cercle Canvas par un hublot 3D occultant avec cadre, joint, verre,
|
||||||
|
|||||||
@@ -3,16 +3,20 @@ extends Node3D
|
|||||||
|
|
||||||
const CockpitMaterialPalette = preload("res://game/presentation/cockpit/cockpit_material_palette.gd")
|
const CockpitMaterialPalette = preload("res://game/presentation/cockpit/cockpit_material_palette.gd")
|
||||||
const CockpitMeshFactory = preload("res://game/presentation/cockpit/cockpit_mesh_factory.gd")
|
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 _materials: CockpitMaterialPalette
|
||||||
var _battery_needle: Node3D
|
var _battery_needle: Node3D
|
||||||
var _pressure_needle: Node3D
|
var _pressure_needle: Node3D
|
||||||
|
var _indicator_materials: Array[StandardMaterial3D] = []
|
||||||
|
var _active_indicator_count := 0
|
||||||
|
|
||||||
|
|
||||||
func _init(materials: CockpitMaterialPalette) -> void:
|
func _init(materials: CockpitMaterialPalette) -> void:
|
||||||
_materials = materials
|
_materials = materials
|
||||||
name = "AnalogGauges"
|
name = "AnalogGauges"
|
||||||
_build_binnacle()
|
_build_binnacle()
|
||||||
|
_build_indicator_bank()
|
||||||
_battery_needle = _add_gauge(
|
_battery_needle = _add_gauge(
|
||||||
"BatteryGauge",
|
"BatteryGauge",
|
||||||
Vector3(-0.095, 0.315, 0.145),
|
Vector3(-0.095, 0.315, 0.145),
|
||||||
@@ -27,15 +31,28 @@ func _init(materials: CockpitMaterialPalette) -> void:
|
|||||||
_add_binnacle_label("PRESSION COQUE", Vector3(0.095, 0.235, 0.160))
|
_add_binnacle_label("PRESSION COQUE", Vector3(0.095, 0.235, 0.160))
|
||||||
|
|
||||||
|
|
||||||
func present(battery_ratio: float, pressure_ratio: float) -> void:
|
func present(
|
||||||
|
battery_ratio: float,
|
||||||
|
pressure_ratio: float,
|
||||||
|
alert_level: DiveAlertLevel.Value
|
||||||
|
) -> void:
|
||||||
_set_needle_ratio(_battery_needle, battery_ratio)
|
_set_needle_ratio(_battery_needle, battery_ratio)
|
||||||
_set_needle_ratio(_pressure_needle, pressure_ratio)
|
_set_needle_ratio(_pressure_needle, pressure_ratio)
|
||||||
|
_update_indicator_bank(alert_level)
|
||||||
|
|
||||||
|
|
||||||
func battery_needle_degrees() -> float:
|
func battery_needle_degrees() -> float:
|
||||||
return 0.0 if _battery_needle == null else _battery_needle.rotation_degrees.z
|
return 0.0 if _battery_needle == null else _battery_needle.rotation_degrees.z
|
||||||
|
|
||||||
|
|
||||||
|
func indicator_count() -> int:
|
||||||
|
return _indicator_materials.size()
|
||||||
|
|
||||||
|
|
||||||
|
func active_indicator_count() -> int:
|
||||||
|
return _active_indicator_count
|
||||||
|
|
||||||
|
|
||||||
func _build_binnacle() -> void:
|
func _build_binnacle() -> void:
|
||||||
CockpitMeshFactory.add_box(
|
CockpitMeshFactory.add_box(
|
||||||
"BinnacleHousing",
|
"BinnacleHousing",
|
||||||
@@ -53,6 +70,72 @@ func _build_binnacle() -> void:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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 x := -0.055 + indicator_index * 0.055
|
||||||
|
CockpitMeshFactory.add_cylinder(
|
||||||
|
"%sBezel" % captions[indicator_index],
|
||||||
|
root,
|
||||||
|
Vector3(x, 0.198, 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(x, 0.198, 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(x, 0.177, 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 _add_binnacle_label(caption: String, position: Vector3) -> void:
|
func _add_binnacle_label(caption: String, position: Vector3) -> void:
|
||||||
var label := Label3D.new()
|
var label := Label3D.new()
|
||||||
label.name = "%sLabel" % caption.to_pascal_case().replace(" ", "")
|
label.name = "%sLabel" % caption.to_pascal_case().replace(" ", "")
|
||||||
@@ -113,6 +196,7 @@ func _add_gauge(gauge_name: String, position: Vector3, caption: String) -> Node3
|
|||||||
face.rotation_degrees.x = 90.0
|
face.rotation_degrees.x = 90.0
|
||||||
face.position.z = 0.003
|
face.position.z = 0.003
|
||||||
gauge.add_child(face)
|
gauge.add_child(face)
|
||||||
|
_add_dial_ticks(gauge)
|
||||||
_add_scale_label(gauge, "0", Vector3(-0.052, -0.018, 0.028))
|
_add_scale_label(gauge, "0", Vector3(-0.052, -0.018, 0.028))
|
||||||
_add_scale_label(gauge, "50", Vector3(0.0, 0.052, 0.028))
|
_add_scale_label(gauge, "50", Vector3(0.0, 0.052, 0.028))
|
||||||
_add_scale_label(gauge, "100", Vector3(0.052, -0.018, 0.028))
|
_add_scale_label(gauge, "100", Vector3(0.052, -0.018, 0.028))
|
||||||
@@ -160,12 +244,30 @@ func _add_gauge(gauge_name: String, position: Vector3, caption: String) -> Node3
|
|||||||
return needle_root
|
return needle_root
|
||||||
|
|
||||||
|
|
||||||
|
func _add_dial_ticks(gauge: Node3D) -> void:
|
||||||
|
for tick_index in 11:
|
||||||
|
var ratio := float(tick_index) / 10.0
|
||||||
|
var angle_degrees := lerpf(125.0, -125.0, ratio)
|
||||||
|
var angle_radians := deg_to_rad(angle_degrees)
|
||||||
|
var direction := Vector2(-sin(angle_radians), cos(angle_radians))
|
||||||
|
var is_major := tick_index % 5 == 0
|
||||||
|
var tick_length := 0.015 if is_major else 0.009
|
||||||
|
var tick := CockpitMeshFactory.add_box(
|
||||||
|
"MajorTick" if is_major else "MinorTick",
|
||||||
|
gauge,
|
||||||
|
Vector3(direction.x * 0.064, direction.y * 0.064, 0.029),
|
||||||
|
Vector3(0.004 if is_major else 0.0025, tick_length, 0.003),
|
||||||
|
_materials.dark_steel
|
||||||
|
)
|
||||||
|
tick.rotation_degrees.z = angle_degrees
|
||||||
|
|
||||||
|
|
||||||
func _add_scale_label(gauge: Node3D, text: String, position: Vector3) -> void:
|
func _add_scale_label(gauge: Node3D, text: String, position: Vector3) -> void:
|
||||||
var label := Label3D.new()
|
var label := Label3D.new()
|
||||||
label.name = "Scale%s" % text
|
label.name = "Scale%s" % text
|
||||||
label.text = text
|
label.text = text
|
||||||
label.font_size = 9
|
label.font_size = 11
|
||||||
label.pixel_size = 0.00048
|
label.pixel_size = 0.00052
|
||||||
label.modulate = Color(0.09, 0.085, 0.065)
|
label.modulate = Color(0.09, 0.085, 0.065)
|
||||||
label.position = position
|
label.position = position
|
||||||
gauge.add_child(label)
|
gauge.add_child(label)
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ func apply_screen_materials(dashboard: Node3D) -> void:
|
|||||||
screen.material_override = _materials.instrument_screen
|
screen.material_override = _materials.instrument_screen
|
||||||
|
|
||||||
|
|
||||||
|
func hide_placeholder_indicators(cockpit: Node3D) -> void:
|
||||||
|
for indicator_name: String in ["CyanIndicator", "AmberIndicator", "RedIndicator"]:
|
||||||
|
var indicator := cockpit.get_node_or_null(indicator_name) as MeshInstance3D
|
||||||
|
if indicator != null:
|
||||||
|
indicator.visible = false
|
||||||
|
|
||||||
|
|
||||||
func _build_structural_frame() -> void:
|
func _build_structural_frame() -> void:
|
||||||
CockpitMeshFactory.add_box(
|
CockpitMeshFactory.add_box(
|
||||||
"TopRail",
|
"TopRail",
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ func present(update: DiveUpdate, telemetry: VehicleTelemetry) -> void:
|
|||||||
/ maxf(snapshot.hull.certified_pressure_bar, 0.001),
|
/ maxf(snapshot.hull.certified_pressure_bar, 0.001),
|
||||||
0.0,
|
0.0,
|
||||||
1.0
|
1.0
|
||||||
)
|
),
|
||||||
|
snapshot.alerts.maximum
|
||||||
)
|
)
|
||||||
var alert_level: DiveAlertLevel.Value = snapshot.alerts.maximum
|
var alert_level: DiveAlertLevel.Value = snapshot.alerts.maximum
|
||||||
_emergency_light.visible = alert_level >= DiveAlertLevel.Value.CRITICAL
|
_emergency_light.visible = alert_level >= DiveAlertLevel.Value.CRITICAL
|
||||||
@@ -315,6 +316,7 @@ func _build_dashboard_instrumentation() -> void:
|
|||||||
_dashboard_detailing = CockpitDashboardDetailing.new(_materials)
|
_dashboard_detailing = CockpitDashboardDetailing.new(_materials)
|
||||||
dashboard.add_child(_dashboard_detailing)
|
dashboard.add_child(_dashboard_detailing)
|
||||||
_dashboard_detailing.apply_screen_materials(dashboard)
|
_dashboard_detailing.apply_screen_materials(dashboard)
|
||||||
|
_dashboard_detailing.hide_placeholder_indicators(get_parent())
|
||||||
_analog_gauges = CockpitAnalogGauges.new(_materials)
|
_analog_gauges = CockpitAnalogGauges.new(_materials)
|
||||||
dashboard.add_child(_analog_gauges)
|
dashboard.add_child(_analog_gauges)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
[gd_scene load_steps=20 format=3]
|
[gd_scene load_steps=21 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_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_detailing.gd" id="2_detailing"]
|
||||||
|
[ext_resource type="Script" path="res://game/presentation/cockpit/cockpit_pressure_bulkhead.gd" id="3_bulkhead"]
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="SteelMaterial"]
|
[sub_resource type="StandardMaterial3D" id="SteelMaterial"]
|
||||||
albedo_color = Color(0.055, 0.07, 0.07, 1)
|
albedo_color = Color(0.055, 0.07, 0.07, 1)
|
||||||
@@ -160,6 +161,10 @@ material_override = SubResource("BrassMaterial")
|
|||||||
[node name="PortholeAssembly" type="Node3D" parent="."]
|
[node name="PortholeAssembly" type="Node3D" parent="."]
|
||||||
position = Vector3(0, 0.47, -1.32)
|
position = Vector3(0, 0.47, -1.32)
|
||||||
|
|
||||||
|
[node name="CurvedPressureBulkhead" type="Node3D" parent="PortholeAssembly"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
script = ExtResource("3_bulkhead")
|
||||||
|
|
||||||
[node name="LeftBulkhead" type="MeshInstance3D" parent="PortholeAssembly"]
|
[node name="LeftBulkhead" type="MeshInstance3D" parent="PortholeAssembly"]
|
||||||
position = Vector3(-0.94, 0, 0)
|
position = Vector3(-0.94, 0, 0)
|
||||||
scale = Vector3(0.6, 1.82, 0.2)
|
scale = Vector3(0.6, 1.82, 0.2)
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PANEL_COUNT := 12
|
||||||
|
const ANGULAR_STEPS := 4
|
||||||
|
const RADIAL_STEPS := 3
|
||||||
|
const INNER_RADIUS := 0.72
|
||||||
|
const OUTER_RADIUS := 1.11
|
||||||
|
|
||||||
|
var _materials: CockpitMaterialPalette
|
||||||
|
var _panel_count := 0
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_materials = CockpitMaterialPalette.new()
|
||||||
|
_hide_rectangular_whitebox()
|
||||||
|
_build_curved_panels()
|
||||||
|
_build_outer_rib()
|
||||||
|
_build_fasteners()
|
||||||
|
|
||||||
|
|
||||||
|
func panel_count() -> int:
|
||||||
|
return _panel_count
|
||||||
|
|
||||||
|
|
||||||
|
func rectangular_whitebox_is_hidden() -> bool:
|
||||||
|
for node_name: String in ["LeftBulkhead", "RightBulkhead", "TopBulkhead", "BottomBulkhead"]:
|
||||||
|
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 _build_curved_panels() -> void:
|
||||||
|
var panel_angle := TAU / float(PANEL_COUNT)
|
||||||
|
var seam_half_width := deg_to_rad(0.22)
|
||||||
|
for panel_index in PANEL_COUNT:
|
||||||
|
var start_angle := panel_index * panel_angle + seam_half_width
|
||||||
|
var end_angle := (panel_index + 1) * panel_angle - seam_half_width
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
func _build_fasteners() -> void:
|
||||||
|
for fastener_index in PANEL_COUNT:
|
||||||
|
var angle := (fastener_index + 0.5) * TAU / float(PANEL_COUNT)
|
||||||
|
CockpitMeshFactory.add_cylinder(
|
||||||
|
"OuterFastener",
|
||||||
|
self,
|
||||||
|
Vector3(cos(angle) * 0.96, sin(angle) * 0.96, 0.105),
|
||||||
|
0.014,
|
||||||
|
0.018,
|
||||||
|
_materials.brass,
|
||||||
|
Vector3(90.0, 0.0, 0.0)
|
||||||
|
)
|
||||||
@@ -8,6 +8,9 @@ const CockpitHud = preload("res://game/presentation/cockpit/cockpit_hud.gd")
|
|||||||
const CockpitDetailing = preload(
|
const CockpitDetailing = preload(
|
||||||
"res://game/presentation/cockpit/cockpit_detailing.gd"
|
"res://game/presentation/cockpit/cockpit_detailing.gd"
|
||||||
)
|
)
|
||||||
|
const CockpitPressureBulkhead = preload(
|
||||||
|
"res://game/presentation/cockpit/cockpit_pressure_bulkhead.gd"
|
||||||
|
)
|
||||||
const PauseMenu = preload("res://game/presentation/pause/pause_menu.gd")
|
const PauseMenu = preload("res://game/presentation/pause/pause_menu.gd")
|
||||||
const ControlsSettingsScreen = preload(
|
const ControlsSettingsScreen = preload(
|
||||||
"res://game/presentation/settings/controls_settings_screen.gd"
|
"res://game/presentation/settings/controls_settings_screen.gd"
|
||||||
@@ -16,7 +19,7 @@ const ProductionInputAdapter = preload(
|
|||||||
"res://game/infrastructure/input/production_input_adapter.gd"
|
"res://game/infrastructure/input/production_input_adapter.gd"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TEST_COUNT := 58
|
const TEST_COUNT := 64
|
||||||
|
|
||||||
var _failures: Array[String] = []
|
var _failures: Array[String] = []
|
||||||
|
|
||||||
@@ -82,6 +85,16 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
|
|||||||
var detailing: CockpitDetailing = (
|
var detailing: CockpitDetailing = (
|
||||||
instruments.detailing() if instruments != null else null
|
instruments.detailing() if instruments != null else null
|
||||||
)
|
)
|
||||||
|
var pressure_bulkhead := (
|
||||||
|
instruments.get_node_or_null("%CurvedPressureBulkhead") as CockpitPressureBulkhead
|
||||||
|
if instruments != null
|
||||||
|
else null
|
||||||
|
)
|
||||||
|
var analog_gauges := (
|
||||||
|
instruments.get_node_or_null("Dashboard/AnalogGauges")
|
||||||
|
if instruments != null
|
||||||
|
else null
|
||||||
|
)
|
||||||
var adapter := composition.input_adapter() as ProductionInputAdapter
|
var adapter := composition.input_adapter() as ProductionInputAdapter
|
||||||
var hud := composition.active_screen() as CockpitHud
|
var hud := composition.active_screen() as CockpitHud
|
||||||
var sonar_service = composition.sonar_service()
|
var sonar_service = composition.sonar_service()
|
||||||
@@ -91,6 +104,8 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
|
|||||||
_expect(camera != null, "cockpit camera is attached to the submarine")
|
_expect(camera != null, "cockpit camera is attached to the submarine")
|
||||||
_expect(instruments != null, "physical cockpit instruments are assembled")
|
_expect(instruments != null, "physical cockpit instruments are assembled")
|
||||||
_expect(detailing != null, "cockpit structural detailing is assembled")
|
_expect(detailing != null, "cockpit structural detailing is assembled")
|
||||||
|
_expect(pressure_bulkhead != null, "curved pressure bulkhead is assembled")
|
||||||
|
_expect(analog_gauges != null, "dashboard analog instruments are assembled")
|
||||||
_expect(hud != null, "critical HUD overlay is assembled")
|
_expect(hud != null, "critical HUD overlay is assembled")
|
||||||
_expect(sonar_service != null, "production sonar service is assembled")
|
_expect(sonar_service != null, "production sonar service is assembled")
|
||||||
_expect(sonar_display != null, "shared sonar presentation is assembled")
|
_expect(sonar_display != null, "shared sonar presentation is assembled")
|
||||||
@@ -100,6 +115,8 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
|
|||||||
or camera == null
|
or camera == null
|
||||||
or instruments == null
|
or instruments == null
|
||||||
or detailing == null
|
or detailing == null
|
||||||
|
or pressure_bulkhead == null
|
||||||
|
or analog_gauges == null
|
||||||
or adapter == null
|
or adapter == null
|
||||||
or hud == null
|
or hud == null
|
||||||
):
|
):
|
||||||
@@ -114,6 +131,10 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
|
|||||||
_expect(detailing.textures_are_loaded(), "cockpit loads its overhead and analog gauge textures")
|
_expect(detailing.textures_are_loaded(), "cockpit loads its overhead and analog gauge textures")
|
||||||
_expect(detailing.gauges_are_dashboard_mounted(), "analog gauges are mounted on the dashboard")
|
_expect(detailing.gauges_are_dashboard_mounted(), "analog gauges are mounted on the dashboard")
|
||||||
_expect(detailing.dashboard_control_count() >= 8, "dashboard exposes physical auxiliary controls")
|
_expect(detailing.dashboard_control_count() >= 8, "dashboard exposes physical auxiliary controls")
|
||||||
|
_expect(pressure_bulkhead.panel_count() == 12, "porthole bulkhead uses twelve curved pressure panels")
|
||||||
|
_expect(pressure_bulkhead.rectangular_whitebox_is_hidden(), "porthole rectangular whitebox is hidden")
|
||||||
|
_expect(analog_gauges.indicator_count() == 3, "instrument binnacle exposes three inset pilot lights")
|
||||||
|
_expect(analog_gauges.active_indicator_count() == 1, "exactly one pilot light reflects the alert state")
|
||||||
_expect("kWh" in instruments.resources_text() and "kW" in instruments.resources_text(), "battery instrument has exact units")
|
_expect("kWh" in instruments.resources_text() and "kW" in instruments.resources_text(), "battery instrument has exact units")
|
||||||
_expect("L/min" in instruments.resources_text(), "oxygen instrument has volume and consumption")
|
_expect("L/min" in instruments.resources_text(), "oxygen instrument has volume and consumption")
|
||||||
_expect("bar" in instruments.integrity_text(), "hull instrument has current and certified pressure")
|
_expect("bar" in instruments.integrity_text(), "hull instrument has current and certified pressure")
|
||||||
|
|||||||
Reference in new issue
Block a user