khora_core/asset/materials/
emissive.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Defines emissive materials for self-illuminating surfaces.
16
17use crate::{
18    asset::{Asset, Material},
19    math::LinearRgba,
20};
21
22use super::AlphaMode;
23
24/// A material that emits light without being affected by scene lighting.
25///
26/// Emissive materials are perfect for objects that should glow or appear self-illuminated,
27/// such as neon signs, magical effects, UI elements, LED displays, or light sources.
28/// They render at full brightness regardless of lighting conditions.
29///
30/// # HDR Support
31///
32/// The `intensity` parameter allows values greater than 1.0, enabling High Dynamic Range
33/// (HDR) emissive effects. This is particularly useful for bloom post-processing effects
34/// where bright emissive surfaces can "bleed" light into surrounding areas.
35///
36/// # Examples
37///
38/// ```
39/// use khora_core::asset::EmissiveMaterial;
40/// use khora_core::math::LinearRgba;
41///
42/// // Create a bright red glowing sign
43/// let neon_sign = EmissiveMaterial {
44///     emissive_color: LinearRgba::new(1.0, 0.0, 0.0, 1.0),
45///     intensity: 2.0,  // HDR intensity for bloom
46///     ..Default::default()
47/// };
48///
49/// // Create a subtle blue glow
50/// let subtle_glow = EmissiveMaterial {
51///     emissive_color: LinearRgba::new(0.3, 0.5, 1.0, 1.0),
52///     intensity: 0.5,
53///     ..Default::default()
54/// };
55/// ```
56#[derive(Clone, Debug)]
57pub struct EmissiveMaterial {
58    /// The emissive color of the material.
59    ///
60    /// This color is directly output to the framebuffer without any lighting calculations.
61    /// The RGB values represent the color of the emitted light.
62    pub emissive_color: LinearRgba,
63
64    /// Optional texture for emissive color.
65    ///
66    /// If present, the texture's RGB values are multiplied with `emissive_color`.
67    /// The alpha channel can be used for transparency when combined with appropriate `alpha_mode`.
68    ///
69    /// **Future work**: Texture asset system integration pending.
70    // pub emissive_texture: Option<AssetHandle<TextureId>>,
71
72    /// Intensity multiplier for the emissive color.
73    ///
74    /// Values greater than 1.0 enable HDR effects and are particularly useful for
75    /// bloom post-processing. A value of 1.0 produces standard emissive output.
76    ///
77    /// **Recommended ranges:**
78    /// - 0.0-1.0: Subtle emission
79    /// - 1.0-3.0: Strong emission with bloom
80    /// - 3.0+: Very bright, suitable for light sources
81    pub intensity: f32,
82
83    /// The alpha blending mode for this material.
84    ///
85    /// Determines how transparency is handled. See [`AlphaMode`] for details.
86    pub alpha_mode: AlphaMode,
87}
88
89impl Default for EmissiveMaterial {
90    fn default() -> Self {
91        Self {
92            emissive_color: LinearRgba::new(1.0, 1.0, 1.0, 1.0), // White
93            // emissive_texture: None,
94            intensity: 1.0,
95            alpha_mode: AlphaMode::Opaque,
96        }
97    }
98}
99
100impl Asset for EmissiveMaterial {}
101impl Material for EmissiveMaterial {
102    fn base_color(&self) -> crate::math::LinearRgba {
103        crate::math::LinearRgba::BLACK
104    }
105
106    fn emissive_color(&self) -> crate::math::LinearRgba {
107        self.emissive_color
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_emissive_material_default() {
117        let material = EmissiveMaterial::default();
118
119        assert_eq!(material.emissive_color, LinearRgba::new(1.0, 1.0, 1.0, 1.0));
120        assert_eq!(material.intensity, 1.0);
121        assert_eq!(material.alpha_mode, AlphaMode::Opaque);
122        // assert!(material.emissive_texture.is_none());
123    }
124
125    #[test]
126    fn test_emissive_material_custom_color() {
127        let material = EmissiveMaterial {
128            emissive_color: LinearRgba::new(1.0, 0.0, 0.0, 1.0),
129            ..Default::default()
130        };
131
132        assert_eq!(material.emissive_color, LinearRgba::new(1.0, 0.0, 0.0, 1.0));
133    }
134
135    #[test]
136    fn test_emissive_material_hdr_intensity() {
137        // Test various intensity ranges
138        let subtle = EmissiveMaterial {
139            intensity: 0.5,
140            ..Default::default()
141        };
142        assert_eq!(subtle.intensity, 0.5);
143
144        let normal = EmissiveMaterial {
145            intensity: 1.0,
146            ..Default::default()
147        };
148        assert_eq!(normal.intensity, 1.0);
149
150        let bright = EmissiveMaterial {
151            intensity: 3.0,
152            ..Default::default()
153        };
154        assert_eq!(bright.intensity, 3.0);
155
156        let very_bright = EmissiveMaterial {
157            intensity: 10.0,
158            ..Default::default()
159        };
160        assert_eq!(very_bright.intensity, 10.0);
161    }
162
163    #[test]
164    fn test_emissive_material_alpha_modes() {
165        let opaque = EmissiveMaterial {
166            alpha_mode: AlphaMode::Opaque,
167            ..Default::default()
168        };
169        assert_eq!(opaque.alpha_mode, AlphaMode::Opaque);
170
171        let masked = EmissiveMaterial {
172            alpha_mode: AlphaMode::Mask(0.5),
173            ..Default::default()
174        };
175        assert_eq!(masked.alpha_mode, AlphaMode::Mask(0.5));
176
177        let blend = EmissiveMaterial {
178            alpha_mode: AlphaMode::Blend,
179            ..Default::default()
180        };
181        assert_eq!(blend.alpha_mode, AlphaMode::Blend);
182    }
183
184    #[test]
185    fn test_emissive_material_clone() {
186        let original = EmissiveMaterial {
187            emissive_color: LinearRgba::new(0.5, 0.7, 1.0, 1.0),
188            intensity: 2.5,
189            ..Default::default()
190        };
191
192        let cloned = original.clone();
193        assert_eq!(cloned.emissive_color, original.emissive_color);
194        assert_eq!(cloned.intensity, original.intensity);
195    }
196
197    #[test]
198    fn test_emissive_material_neon_sign_example() {
199        // Realistic example: blue neon sign
200        let neon = EmissiveMaterial {
201            emissive_color: LinearRgba::new(0.2, 0.5, 1.0, 1.0),
202            intensity: 2.0,
203            alpha_mode: AlphaMode::Opaque,
204            // emissive_texture: None,
205        };
206
207        assert_eq!(neon.intensity, 2.0);
208        assert_eq!(neon.alpha_mode, AlphaMode::Opaque);
209    }
210}