class_name CockpitAnalogGauges 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 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 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 ) -> 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) 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 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 _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: if needle != null: needle.rotation_degrees.z = lerpf(125.0, -125.0, clampf(ratio, 0.0, 1.0))