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}