Skip to main content

nightshade/ecs/light/
components.rs

1//! Light component definitions.
2
3use serde::{Deserialize, Serialize};
4
5/// Type of light source. Directional, point, and spot follow glTF
6/// KHR_lights_punctual. Area is an emitter with spatial extent shaded with
7/// linearly transformed cosines.
8#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize, enum2schema::Schema)]
9#[schema(string_enum)]
10pub enum LightType {
11    /// Infinitely distant light (like the sun). Uses entity's -Z direction.
12    #[default]
13    Directional,
14    /// Omnidirectional point light. Uses entity's position.
15    Point,
16    /// Cone-shaped spotlight. Uses entity's position and -Z direction.
17    Spot,
18    /// Emitter with spatial extent (rectangle, disk, sphere, or tube). The
19    /// extent and shape come from the area fields on [`Light`].
20    Area,
21}
22
23/// Geometric shape of an area light's emitter surface.
24#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize, enum2schema::Schema)]
25#[schema(string_enum)]
26pub enum AreaLightShape {
27    /// Flat rectangle oriented in the entity's local XY plane, emitting along
28    /// -Z. Sized by `area_width` and `area_height`. Supports an emissive texture.
29    #[default]
30    Rectangle,
31    /// Flat disk oriented in the entity's local XY plane, emitting along -Z.
32    /// Sized by `area_radius`.
33    Disk,
34    /// Spherical emitter. Sized by `area_radius`. Always faces the shaded point.
35    Sphere,
36    /// Capsule-like tube along the entity's local X axis. `area_width` is the
37    /// length and `area_radius` is the tube radius.
38    Tube,
39}
40
41/// Light source component.
42///
43/// Attach to an entity with a transform to illuminate the scene.
44/// The transform's -Z axis defines the light direction (for directional and spot lights).
45#[derive(Debug, Clone, Serialize, Deserialize, enum2schema::Schema)]
46pub struct Light {
47    /// The type of light (directional, point, spot, or area).
48    pub light_type: LightType,
49    /// Light color as linear RGB.
50    #[schema(type = "array", items = "number", len = 3)]
51    pub color: nalgebra_glm::Vec3,
52    /// Light intensity (lumens for point/spot, lux for directional).
53    pub intensity: f32,
54    /// Maximum range for point/spot lights (0 = unlimited).
55    pub range: f32,
56    /// Inner cone angle in radians for spot lights (full intensity).
57    pub inner_cone_angle: f32,
58    /// Outer cone angle in radians for spot lights (zero intensity).
59    pub outer_cone_angle: f32,
60    /// Whether this light casts shadows.
61    pub cast_shadows: bool,
62    /// Depth bias to reduce shadow acne artifacts.
63    pub shadow_bias: f32,
64    /// Per-light shadow map resolution. 0 falls back to the renderer default.
65    #[serde(default)]
66    pub shadow_resolution: u32,
67    /// Maximum distance at which this light casts shadows. 0 = unlimited.
68    #[serde(default)]
69    pub shadow_distance: f32,
70    /// Optional cookie texture path (IES profile or projected mask).
71    #[serde(default)]
72    pub cookie_texture: Option<String>,
73    /// Emitter shape for `LightType::Area`. Ignored for other light types.
74    #[serde(default)]
75    pub area_shape: AreaLightShape,
76    /// Width of a rectangular area light, or length of a tube light.
77    #[serde(default = "default_area_extent")]
78    pub area_width: f32,
79    /// Height of a rectangular area light.
80    #[serde(default = "default_area_extent")]
81    pub area_height: f32,
82    /// Radius of a disk, sphere, or tube area light.
83    #[serde(default = "default_area_extent")]
84    pub area_radius: f32,
85    /// Whether a rectangular or disk area light emits from both faces.
86    #[serde(default)]
87    pub area_two_sided: bool,
88    /// Optional emissive texture projected across a rectangular area light.
89    #[serde(default)]
90    pub area_emissive_texture: Option<String>,
91    /// Normal-offset bias in shadow texels, reducing acne on sloped surfaces.
92    #[serde(default = "default_shadow_normal_bias")]
93    pub shadow_normal_bias: f32,
94    /// Shadow penumbra softness (PCSS light-size scale). Larger is softer.
95    #[serde(default = "default_shadow_softness")]
96    pub shadow_softness: f32,
97}
98
99fn default_area_extent() -> f32 {
100    1.0
101}
102
103fn default_shadow_normal_bias() -> f32 {
104    1.8
105}
106
107fn default_shadow_softness() -> f32 {
108    1.0
109}
110
111impl Default for Light {
112    fn default() -> Self {
113        Self {
114            light_type: LightType::default(),
115            color: nalgebra_glm::Vec3::new(0.0, 0.0, 0.0),
116            intensity: 0.0,
117            range: 0.0,
118            inner_cone_angle: 0.0,
119            outer_cone_angle: 0.0,
120            cast_shadows: false,
121            shadow_bias: 0.0,
122            shadow_resolution: 0,
123            shadow_distance: 0.0,
124            cookie_texture: None,
125            area_shape: AreaLightShape::Rectangle,
126            area_width: default_area_extent(),
127            area_height: default_area_extent(),
128            area_radius: default_area_extent(),
129            area_two_sided: false,
130            area_emissive_texture: None,
131            shadow_normal_bias: default_shadow_normal_bias(),
132            shadow_softness: default_shadow_softness(),
133        }
134    }
135}
136
137impl Light {
138    /// Enables shadow casting with the specified depth bias.
139    pub fn with_shadows(mut self, bias: f32) -> Self {
140        self.cast_shadows = true;
141        self.shadow_bias = bias;
142        self
143    }
144}