class_name CockpitMaterialPalette 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 var overhead_steel: StandardMaterial3D 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: painted_steel = _weathered_material( Color(0.045, 0.12, 0.125), Color(0.16, 0.18, 0.17), 0.52, 0.58, 0.13 ) dark_steel = _weathered_material( Color(0.045, 0.055, 0.055), Color(0.22, 0.24, 0.22), 0.78, 0.44, 0.08 ) overhead_steel = _textured_material( _load_texture(NAVAL_STEEL_TEXTURE_PATH), Color(0.88, 0.91, 0.88), 0.64, 0.56, Vector3(1.6, 1.6, 1.6) ) brushed_steel = _plain_material(Color(0.42, 0.44, 0.42), 0.82, 0.30) brass = _weathered_material( Color(0.28, 0.18, 0.045), Color(0.08, 0.16, 0.13), 0.74, 0.38, 0.17 ) copper = _weathered_material( Color(0.34, 0.12, 0.045), Color(0.055, 0.18, 0.15), 0.72, 0.42, 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( base_color: Color, worn_color: Color, metallic: float, roughness: float, wear_amount: float ) -> ShaderMaterial: var shader := Shader.new() shader.code = """ shader_type spatial; render_mode diffuse_burley, specular_schlick_ggx; uniform vec4 base_color : source_color; uniform vec4 worn_color : source_color; uniform float metallic_value : hint_range(0.0, 1.0); uniform float roughness_value : hint_range(0.0, 1.0); uniform float wear_amount : hint_range(0.0, 0.4); float hash21(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); } float value_noise(vec2 p) { vec2 cell = floor(p); vec2 local = fract(p); local = local * local * (3.0 - 2.0 * local); float a = hash21(cell); float b = hash21(cell + vec2(1.0, 0.0)); float c = hash21(cell + vec2(0.0, 1.0)); float d = hash21(cell + vec2(1.0, 1.0)); return mix(mix(a, b, local.x), mix(c, d, local.x), local.y); } void fragment() { float broad = value_noise(UV * 11.0); float grain = value_noise(UV * 73.0 + vec2(4.2, 8.7)); float wear = smoothstep(0.76, 1.0, broad) * wear_amount; ALBEDO = mix(base_color.rgb * mix(0.78, 1.08, broad), worn_color.rgb, wear); METALLIC = metallic_value; ROUGHNESS = clamp(roughness_value + (grain - 0.5) * 0.22, 0.16, 0.96); } """ var material := ShaderMaterial.new() material.shader = shader material.set_shader_parameter("base_color", base_color) material.set_shader_parameter("worn_color", worn_color) material.set_shader_parameter("metallic_value", metallic) material.set_shader_parameter("roughness_value", roughness) material.set_shader_parameter("wear_amount", wear_amount) return material func _plain_material( color: Color, metallic: float, roughness: float ) -> StandardMaterial3D: var material := StandardMaterial3D.new() material.albedo_color = color material.metallic = metallic material.roughness = roughness return material func _textured_material( texture: Texture2D, tint: Color, metallic: float, roughness: float, uv_scale: Vector3 = Vector3.ONE ) -> StandardMaterial3D: var material := StandardMaterial3D.new() material.albedo_color = tint material.albedo_texture = texture material.metallic = metallic material.roughness = roughness material.uv1_scale = uv_scale material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC return material func _load_texture(resource_path: String) -> ImageTexture: var image := Image.load_from_file(resource_path) if image == null or image.is_empty(): push_error("Unable to load cockpit texture: %s" % resource_path) return null return ImageTexture.create_from_image(image) 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 = """ shader_type spatial; render_mode diffuse_burley, specular_schlick_ggx; void fragment() { vec2 centered = UV - vec2(0.5); float vignette = 1.0 - smoothstep(0.28, 0.72, length(centered)); float scanline = 0.94 + 0.06 * sin(UV.y * 720.0); float edge = mix(0.68, 1.0, vignette); vec3 phosphor = vec3(0.025, 0.105, 0.11) * edge * scanline; ALBEDO = phosphor; EMISSION = phosphor * 0.38; METALLIC = 0.08; ROUGHNESS = 0.18; SPECULAR = 0.82; } """ var material := ShaderMaterial.new() material.shader = shader return material func _emissive_material(color: Color, energy: float) -> StandardMaterial3D: var material := StandardMaterial3D.new() material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED material.albedo_color = color material.emission_enabled = true 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