Skip to main content

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/// ![example](https://imgur.com/LEjedUE.png)
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}