Skip to main content

rustial_engine/visualization/
grid_extrusion_layer.rs

1//! Extruded georeferenced grid overlay layer.
2
3use std::any::Any;
4
5use crate::layer::{Layer, LayerId, LayerKind};
6use super::{ColorRamp, GeoGrid, ScalarField2D};
7use super::overlay::ExtrusionParams;
8
9/// An extruded georeferenced grid overlay.
10///
11/// Each grid cell is rendered as an extruded prism (box) with height
12/// proportional to the scalar value multiplied by
13/// [`ExtrusionParams::height_scale`].
14#[derive(Debug, Clone)]
15pub struct GridExtrusionLayer {
16    id: LayerId,
17    name: String,
18    visible: bool,
19    opacity: f32,
20    z_index: i32,
21    /// Grid geometry.
22    pub grid: GeoGrid,
23    /// Scalar field values driving extrusion height.
24    pub field: ScalarField2D,
25    /// Colour transfer function.
26    pub ramp: ColorRamp,
27    /// Extrusion parameters.
28    pub params: ExtrusionParams,
29}
30
31impl GridExtrusionLayer {
32    /// Create a new grid extrusion layer.
33    pub fn new(
34        name: impl Into<String>,
35        grid: GeoGrid,
36        field: ScalarField2D,
37        ramp: ColorRamp,
38    ) -> Self {
39        Self {
40            id: LayerId::next(),
41            name: name.into(),
42            visible: true,
43            opacity: 1.0,
44            z_index: 0,
45            grid,
46            field,
47            ramp,
48            params: ExtrusionParams::default(),
49        }
50    }
51
52    /// Set extrusion parameters.
53    pub fn with_params(mut self, params: ExtrusionParams) -> Self {
54        self.params = params;
55        self
56    }
57
58    /// Replace the scalar field values (value-only update).
59    pub fn update_field(&mut self, data: Vec<f32>) {
60        self.field.update_values(data);
61    }
62}
63
64impl Layer for GridExtrusionLayer {
65    fn id(&self) -> LayerId {
66        self.id
67    }
68
69    fn name(&self) -> &str {
70        &self.name
71    }
72
73    fn kind(&self) -> LayerKind {
74        LayerKind::Visualization
75    }
76
77    fn visible(&self) -> bool {
78        self.visible
79    }
80
81    fn set_visible(&mut self, visible: bool) {
82        self.visible = visible;
83    }
84
85    fn opacity(&self) -> f32 {
86        self.opacity
87    }
88
89    fn set_opacity(&mut self, opacity: f32) {
90        self.opacity = opacity.clamp(0.0, 1.0);
91    }
92
93    fn z_index(&self) -> i32 {
94        self.z_index
95    }
96
97    fn as_any(&self) -> &dyn Any {
98        self
99    }
100
101    fn as_any_mut(&mut self) -> &mut dyn Any {
102        self
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::visualization::ColorStop;
110    use rustial_math::GeoCoord;
111
112    fn test_ramp() -> ColorRamp {
113        ColorRamp::new(vec![
114            ColorStop { value: 0.0, color: [0.0, 0.0, 1.0, 1.0] },
115            ColorStop { value: 1.0, color: [1.0, 0.0, 0.0, 1.0] },
116        ])
117    }
118
119    #[test]
120    fn grid_extrusion_layer_basics() {
121        let grid = GeoGrid::new(GeoCoord::from_lat_lon(0.0, 0.0), 4, 4, 100.0, 100.0);
122        let field = ScalarField2D::from_data(4, 4, vec![0.0; 16]);
123        let layer = GridExtrusionLayer::new("extrusion", grid, field, test_ramp());
124        assert_eq!(layer.name(), "extrusion");
125        assert!((layer.params.height_scale - 1.0).abs() < 1e-9);
126        assert!((layer.params.base_meters - 0.0).abs() < 1e-9);
127    }
128
129    #[test]
130    fn grid_extrusion_with_params() {
131        let grid = GeoGrid::new(GeoCoord::from_lat_lon(0.0, 0.0), 2, 2, 50.0, 50.0);
132        let field = ScalarField2D::from_data(2, 2, vec![1.0; 4]);
133        let layer = GridExtrusionLayer::new("ext", grid, field, test_ramp())
134            .with_params(ExtrusionParams {
135                height_scale: 10.0,
136                base_meters: 5.0,
137            });
138        assert!((layer.params.height_scale - 10.0).abs() < 1e-9);
139        assert!((layer.params.base_meters - 5.0).abs() < 1e-9);
140    }
141}