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
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_emissive_material_default() {
109        let material = EmissiveMaterial::default();
110
111        assert_eq!(material.emissive_color, LinearRgba::new(1.0, 1.0, 1.0, 1.0));
112        assert_eq!(material.intensity, 1.0);
113        assert_eq!(material.alpha_mode, AlphaMode::Opaque);
114        // assert!(material.emissive_texture.is_none());
115    }
116
117    #[test]
118    fn test_emissive_material_custom_color() {
119        let material = EmissiveMaterial {
120            emissive_color: LinearRgba::new(1.0, 0.0, 0.0, 1.0),
121            ..Default::default()
122        };
123
124        assert_eq!(material.emissive_color, LinearRgba::new(1.0, 0.0, 0.0, 1.0));
125    }
126
127    #[test]
128    fn test_emissive_material_hdr_intensity() {
129        // Test various intensity ranges
130        let subtle = EmissiveMaterial {
131            intensity: 0.5,
132            ..Default::default()
133        };
134        assert_eq!(subtle.intensity, 0.5);
135
136        let normal = EmissiveMaterial {
137            intensity: 1.0,
138            ..Default::default()
139        };
140        assert_eq!(normal.intensity, 1.0);
141
142        let bright = EmissiveMaterial {
143            intensity: 3.0,
144            ..Default::default()
145        };
146        assert_eq!(bright.intensity, 3.0);
147
148        let very_bright = EmissiveMaterial {
149            intensity: 10.0,
150            ..Default::default()
151        };
152        assert_eq!(very_bright.intensity, 10.0);
153    }
154
155    #[test]
156    fn test_emissive_material_alpha_modes() {
157        let opaque = EmissiveMaterial {
158            alpha_mode: AlphaMode::Opaque,
159            ..Default::default()
160        };
161        assert_eq!(opaque.alpha_mode, AlphaMode::Opaque);
162
163        let masked = EmissiveMaterial {
164            alpha_mode: AlphaMode::Mask(0.5),
165            ..Default::default()
166        };
167        assert_eq!(masked.alpha_mode, AlphaMode::Mask(0.5));
168
169        let blend = EmissiveMaterial {
170            alpha_mode: AlphaMode::Blend,
171            ..Default::default()
172        };
173        assert_eq!(blend.alpha_mode, AlphaMode::Blend);
174    }
175
176    #[test]
177    fn test_emissive_material_clone() {
178        let original = EmissiveMaterial {
179            emissive_color: LinearRgba::new(0.5, 0.7, 1.0, 1.0),
180            intensity: 2.5,
181            ..Default::default()
182        };
183
184        let cloned = original.clone();
185        assert_eq!(cloned.emissive_color, original.emissive_color);
186        assert_eq!(cloned.intensity, original.intensity);
187    }
188
189    #[test]
190    fn test_emissive_material_neon_sign_example() {
191        // Realistic example: blue neon sign
192        let neon = EmissiveMaterial {
193            emissive_color: LinearRgba::new(0.2, 0.5, 1.0, 1.0),
194            intensity: 2.0,
195            alpha_mode: AlphaMode::Opaque,
196            // emissive_texture: None,
197        };
198
199        assert_eq!(neon.intensity, 2.0);
200        assert_eq!(neon.alpha_mode, AlphaMode::Opaque);
201    }
202}