Skip to main content

rustial_engine/visualization/
column.rs

1//! Instanced column descriptors.
2
3use crate::models::AltitudeMode;
4use rustial_math::GeoCoord;
5
6/// A single column instance anchored to a geographic position.
7#[derive(Debug, Clone)]
8pub struct ColumnInstance {
9    /// Geographic anchor position.
10    pub position: GeoCoord,
11    /// Column height in meters.
12    pub height: f64,
13    /// Base elevation offset in meters (relative to altitude mode).
14    pub base: f64,
15    /// Column width (footprint diameter / side length) in meters.
16    pub width: f64,
17    /// Optional per-instance RGBA colour override.
18    pub color: Option<[f32; 4]>,
19    /// Stable pick identifier returned by the picking system.
20    pub pick_id: u64,
21    /// Altitude mode for this column.
22    pub altitude_mode: AltitudeMode,
23}
24
25impl ColumnInstance {
26    /// Create a column with the given position, height, and width.
27    pub fn new(position: GeoCoord, height: f64, width: f64) -> Self {
28        Self {
29            position,
30            height,
31            base: 0.0,
32            width,
33            color: None,
34            pick_id: 0,
35            altitude_mode: AltitudeMode::ClampToGround,
36        }
37    }
38
39    /// Set the pick id.
40    pub fn with_pick_id(mut self, id: u64) -> Self {
41        self.pick_id = id;
42        self
43    }
44
45    /// Set an RGBA colour override.
46    pub fn with_color(mut self, color: [f32; 4]) -> Self {
47        self.color = Some(color);
48        self
49    }
50
51    /// Set the base elevation offset.
52    pub fn with_base(mut self, base: f64) -> Self {
53        self.base = base;
54        self
55    }
56}
57
58/// A collection of [`ColumnInstance`]s with a generation counter.
59#[derive(Debug, Clone)]
60pub struct ColumnInstanceSet {
61    /// The column instances.
62    pub columns: Vec<ColumnInstance>,
63    /// Structural generation counter. Bump when the number or identity
64    /// of columns changes.
65    pub generation: u64,
66}
67
68impl ColumnInstanceSet {
69    /// Create a new set from existing columns.
70    pub fn new(columns: Vec<ColumnInstance>) -> Self {
71        Self {
72            columns,
73            generation: 0,
74        }
75    }
76
77    /// Number of columns.
78    #[inline]
79    pub fn len(&self) -> usize {
80        self.columns.len()
81    }
82
83    /// Whether the set is empty.
84    #[inline]
85    pub fn is_empty(&self) -> bool {
86        self.columns.is_empty()
87    }
88}
89
90impl Default for ColumnInstanceSet {
91    fn default() -> Self {
92        Self::new(Vec::new())
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn column_instance_defaults() {
102        let col = ColumnInstance::new(GeoCoord::from_lat_lon(0.0, 0.0), 100.0, 10.0);
103        assert!((col.height - 100.0).abs() < 1e-9);
104        assert!((col.width - 10.0).abs() < 1e-9);
105        assert!((col.base - 0.0).abs() < 1e-9);
106        assert!(col.color.is_none());
107        assert_eq!(col.pick_id, 0);
108    }
109
110    #[test]
111    fn column_instance_builder() {
112        let col = ColumnInstance::new(GeoCoord::from_lat_lon(0.0, 0.0), 50.0, 5.0)
113            .with_pick_id(42)
114            .with_color([1.0, 0.0, 0.0, 1.0])
115            .with_base(10.0);
116        assert_eq!(col.pick_id, 42);
117        assert_eq!(col.color, Some([1.0, 0.0, 0.0, 1.0]));
118        assert!((col.base - 10.0).abs() < 1e-9);
119    }
120
121    #[test]
122    fn column_instance_set_len() {
123        let set = ColumnInstanceSet::new(vec![
124            ColumnInstance::new(GeoCoord::from_lat_lon(0.0, 0.0), 10.0, 5.0),
125            ColumnInstance::new(GeoCoord::from_lat_lon(1.0, 1.0), 20.0, 5.0),
126        ]);
127        assert_eq!(set.len(), 2);
128        assert!(!set.is_empty());
129    }
130
131    #[test]
132    fn column_instance_set_empty() {
133        let set = ColumnInstanceSet::default();
134        assert!(set.is_empty());
135        assert_eq!(set.generation, 0);
136    }
137}