plotlars_core/components/lighting.rs
1/// A structure describing the lighting model.
2///
3/// # Example
4///
5/// ```rust
6/// use ndarray::Array;
7/// use plotlars::{ColorBar, Lighting, Palette, Plot, SurfacePlot, Text};
8/// use polars::prelude::*;
9/// use std::iter;
10///
11/// let n: usize = 100;
12/// let x_base: Vec<f64> = Array::linspace(-10.0, 10.0, n).into_raw_vec();
13/// let y_base: Vec<f64> = Array::linspace(-10.0, 10.0, n).into_raw_vec();
14///
15/// let x = x_base
16/// .iter()
17/// .flat_map(|&xi| iter::repeat(xi).take(n))
18/// .collect::<Vec<_>>();
19///
20/// let y = y_base
21/// .iter()
22/// .cycle()
23/// .take(n * n)
24/// .cloned()
25/// .collect::<Vec<_>>();
26///
27/// let z = x_base
28/// .iter()
29/// .map(|i| {
30/// y_base
31/// .iter()
32/// .map(|j| 1.0 / (j * j + 5.0) * j.sin() + 1.0 / (i * i + 5.0) * i.cos())
33/// .collect::<Vec<_>>()
34/// })
35/// .flatten()
36/// .collect::<Vec<_>>();
37///
38/// let dataset = df![
39/// "x" => &x,
40/// "y" => &y,
41/// "z" => &z,
42/// ]
43/// .unwrap();
44///
45/// SurfacePlot::builder()
46/// .data(&dataset)
47/// .x("x")
48/// .y("y")
49/// .z("z")
50/// .plot_title(
51/// Text::from("Surface Plot")
52/// .font("Arial")
53/// .size(18),
54/// )
55/// .color_bar(
56/// &ColorBar::new()
57/// .border_width(1),
58/// )
59/// .color_scale(Palette::Cividis)
60/// .reverse_scale(true)
61/// .lighting(
62/// &Lighting::new()
63/// .position(1, 0, 0)
64/// .ambient(1.0)
65/// .diffuse(1.0)
66/// .fresnel(1.0)
67/// .roughness(1.0)
68/// .specular(1.0),
69/// )
70/// .opacity(0.5)
71/// .build()
72/// .plot();
73/// ```
74///
75/// 
76#[derive(Default, Clone)]
77pub struct Lighting {
78 pub position: Option<[i32; 3]>,
79 pub ambient: Option<f64>,
80 pub diffuse: Option<f64>,
81 pub fresnel: Option<f64>,
82 pub roughness: Option<f64>,
83 pub specular: Option<f64>,
84}
85
86impl Lighting {
87 /// Creates a new `Lighting` instance with default values.
88 pub fn new() -> Self {
89 Self::default()
90 }
91
92 /// Sets the position of the virtual light source.
93 ///
94 /// # Arguments
95 ///
96 /// * `x` – An `i32` value representing the *x*‑coordinate of the light.
97 /// * `y` – An `i32` value representing the *y*‑coordinate of the light.
98 /// * `z` – An `i32` value representing the *z*‑coordinate of the light (positive z points toward the viewer).
99 pub fn position(mut self, x: i32, y: i32, z: i32) -> Self {
100 self.position = Some([x, y, z]);
101 self
102 }
103
104 /// Sets the ambient light component.
105 ///
106 /// # Arguments
107 ///
108 /// * `value` – A `f64` value in the range 0.0 – 1.0 specifying the uniform tint strength.
109 pub fn ambient(mut self, value: f64) -> Self {
110 self.ambient = Some(value);
111 self
112 }
113
114 /// Sets the diffuse light component.
115 ///
116 /// # Arguments
117 ///
118 /// * `value` – A `f64` value in the range 0.0 – 1.0 specifying the Lambertian reflection strength.
119 pub fn diffuse(mut self, value: f64) -> Self {
120 self.diffuse = Some(value);
121 self
122 }
123
124 /// Sets the Fresnel (edge brightness) component.
125 ///
126 /// # Arguments
127 ///
128 /// * `value` – A `f64` value in the range 0.0 – 1.0 specifying the rim‑light intensity.
129 pub fn fresnel(mut self, value: f64) -> Self {
130 self.fresnel = Some(value);
131 self
132 }
133
134 /// Sets the roughness of the material.
135 ///
136 /// # Arguments
137 ///
138 /// * `value` – A `f64` value in the range 0.0 – 1.0 that controls highlight width (0.0 = glossy, 1.0 = matte).
139 pub fn roughness(mut self, value: f64) -> Self {
140 self.roughness = Some(value);
141 self
142 }
143
144 /// Sets the specular highlight intensity.
145 ///
146 /// # Arguments
147 ///
148 /// * `value` – A `f64` value in the range 0.0 – 1.0 specifying the mirror‑like highlight strength.
149 pub fn specular(mut self, value: f64) -> Self {
150 self.specular = Some(value);
151 self
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_default() {
161 let light = Lighting::new();
162 assert!(light.position.is_none());
163 assert!(light.ambient.is_none());
164 assert!(light.diffuse.is_none());
165 assert!(light.fresnel.is_none());
166 assert!(light.roughness.is_none());
167 assert!(light.specular.is_none());
168 }
169
170 #[test]
171 fn test_ambient() {
172 let light = Lighting::new().ambient(0.5);
173 assert!((light.ambient.unwrap() - 0.5).abs() < 1e-6);
174 }
175
176 #[test]
177 fn test_diffuse() {
178 let light = Lighting::new().diffuse(0.8);
179 assert!((light.diffuse.unwrap() - 0.8).abs() < 1e-6);
180 }
181
182 #[test]
183 fn test_specular() {
184 let light = Lighting::new().specular(0.3);
185 assert!((light.specular.unwrap() - 0.3).abs() < 1e-6);
186 }
187
188 #[test]
189 fn test_builder_chaining() {
190 let light = Lighting::new()
191 .position(1, 2, 3)
192 .ambient(0.4)
193 .diffuse(0.6)
194 .fresnel(0.2)
195 .roughness(0.9)
196 .specular(0.7);
197
198 assert_eq!(light.position, Some([1, 2, 3]));
199 assert!((light.ambient.unwrap() - 0.4).abs() < 1e-6);
200 assert!((light.diffuse.unwrap() - 0.6).abs() < 1e-6);
201 assert!((light.fresnel.unwrap() - 0.2).abs() < 1e-6);
202 assert!((light.roughness.unwrap() - 0.9).abs() < 1e-6);
203 assert!((light.specular.unwrap() - 0.7).abs() < 1e-6);
204 }
205}