1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/// Type of light source.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LightType {
/// Directional light (infinite distance, parallel rays like the sun).
Directional,
/// Point light (emits in all directions from a position).
Point,
/// Spot light (emits in a cone from a position toward a direction).
Spot {
/// Half-angle of the cone in degrees. Default: 30.0
cone_angle: f64,
/// Exponent controlling falloff from cone center. Default: 1.0
exponent: f64,
},
/// Ambient light (uniform illumination from all directions).
Ambient,
}
/// A light source in the scene.
///
/// Analogous to VTK's `vtkLight`. Supports directional, point, spot, and
/// ambient light types with configurable color and intensity.
#[derive(Debug, Clone)]
pub struct Light {
/// Light type determines how position/direction are interpreted.
pub light_type: LightType,
/// Position of the light (used for Point and Spot lights).
pub position: [f64; 3],
/// Direction the light points toward (used for Directional and Spot lights).
pub direction: [f64; 3],
/// RGB color of the light. Default: white [1, 1, 1].
pub color: [f32; 3],
/// Intensity multiplier. Default: 1.0
pub intensity: f64,
/// Whether the light is enabled. Default: true
pub enabled: bool,
}
impl Default for Light {
fn default() -> Self {
Self {
light_type: LightType::Directional,
position: [0.0, 0.0, 10.0],
direction: [0.0, 0.0, -1.0],
color: [1.0, 1.0, 1.0],
intensity: 1.0,
enabled: true,
}
}
}
impl Light {
/// Create a directional light pointing in the given direction.
pub fn directional(direction: [f64; 3], color: [f32; 3], intensity: f64) -> Self {
Self {
light_type: LightType::Directional,
direction,
color,
intensity,
..Default::default()
}
}
/// Create a point light at the given position.
pub fn point(position: [f64; 3], color: [f32; 3], intensity: f64) -> Self {
Self {
light_type: LightType::Point,
position,
color,
intensity,
..Default::default()
}
}
/// Create a spot light at the given position pointing toward a direction.
pub fn spot(
position: [f64; 3],
direction: [f64; 3],
color: [f32; 3],
intensity: f64,
cone_angle: f64,
) -> Self {
Self {
light_type: LightType::Spot {
cone_angle,
exponent: 1.0,
},
position,
direction,
color,
intensity,
..Default::default()
}
}
/// Create an ambient light with the given color and intensity.
pub fn ambient(color: [f32; 3], intensity: f64) -> Self {
Self {
light_type: LightType::Ambient,
color,
intensity,
..Default::default()
}
}
/// Create a default headlight (directional, white, pointing along -Z).
pub fn headlight() -> Self {
Self::directional([0.0, 0.0, -1.0], [1.0, 1.0, 1.0], 1.0)
}
}
impl std::fmt::Display for Light {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let lt = match self.light_type {
LightType::Directional => "Directional",
LightType::Point => "Point",
LightType::Spot { .. } => "Spot",
LightType::Ambient => "Ambient",
};
write!(f, "Light({}, intensity={:.1})", lt, self.intensity)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_light() {
let l = Light::default();
assert_eq!(l.light_type, LightType::Directional);
assert!(l.enabled);
assert_eq!(l.color, [1.0, 1.0, 1.0]);
}
#[test]
fn directional_light() {
let l = Light::directional([1.0, -1.0, 0.0], [1.0, 0.9, 0.8], 0.8);
assert_eq!(l.light_type, LightType::Directional);
assert_eq!(l.intensity, 0.8);
}
#[test]
fn point_light() {
let l = Light::point([5.0, 5.0, 5.0], [1.0, 0.0, 0.0], 2.0);
assert_eq!(l.light_type, LightType::Point);
assert_eq!(l.position, [5.0, 5.0, 5.0]);
}
#[test]
fn spot_light() {
let l = Light::spot(
[0.0, 10.0, 0.0],
[0.0, -1.0, 0.0],
[1.0, 1.0, 1.0],
1.5,
45.0,
);
match l.light_type {
LightType::Spot { cone_angle, .. } => {
assert_eq!(cone_angle, 45.0);
}
_ => panic!("expected spot light"),
}
}
#[test]
fn ambient_light() {
let l = Light::ambient([0.2, 0.2, 0.2], 0.5);
assert_eq!(l.light_type, LightType::Ambient);
assert_eq!(l.intensity, 0.5);
}
#[test]
fn display() {
let l = Light::directional([0.0, -1.0, 0.0], [1.0, 1.0, 1.0], 0.8);
let s = format!("{l}");
assert!(s.contains("Directional"));
assert!(s.contains("0.8"));
}
}