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}