Skip to main content

rustial_engine/visualization/
point_cloud.rs

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