rustial-engine 0.0.1

Framework-agnostic 2.5D map engine for rustial
Documentation
//! Instanced column descriptors.

use crate::models::AltitudeMode;
use rustial_math::GeoCoord;

/// A single column instance anchored to a geographic position.
#[derive(Debug, Clone)]
pub struct ColumnInstance {
    /// Geographic anchor position.
    pub position: GeoCoord,
    /// Column height in meters.
    pub height: f64,
    /// Base elevation offset in meters (relative to altitude mode).
    pub base: f64,
    /// Column width (footprint diameter / side length) in meters.
    pub width: f64,
    /// Optional per-instance RGBA colour override.
    pub color: Option<[f32; 4]>,
    /// Stable pick identifier returned by the picking system.
    pub pick_id: u64,
    /// Altitude mode for this column.
    pub altitude_mode: AltitudeMode,
}

impl ColumnInstance {
    /// Create a column with the given position, height, and width.
    pub fn new(position: GeoCoord, height: f64, width: f64) -> Self {
        Self {
            position,
            height,
            base: 0.0,
            width,
            color: None,
            pick_id: 0,
            altitude_mode: AltitudeMode::ClampToGround,
        }
    }

    /// Set the pick id.
    pub fn with_pick_id(mut self, id: u64) -> Self {
        self.pick_id = id;
        self
    }

    /// Set an RGBA colour override.
    pub fn with_color(mut self, color: [f32; 4]) -> Self {
        self.color = Some(color);
        self
    }

    /// Set the base elevation offset.
    pub fn with_base(mut self, base: f64) -> Self {
        self.base = base;
        self
    }
}

/// A collection of [`ColumnInstance`]s with a generation counter.
#[derive(Debug, Clone)]
pub struct ColumnInstanceSet {
    /// The column instances.
    pub columns: Vec<ColumnInstance>,
    /// Structural generation counter. Bump when the number or identity
    /// of columns changes.
    pub generation: u64,
}

impl ColumnInstanceSet {
    /// Create a new set from existing columns.
    pub fn new(columns: Vec<ColumnInstance>) -> Self {
        Self {
            columns,
            generation: 0,
        }
    }

    /// Number of columns.
    #[inline]
    pub fn len(&self) -> usize {
        self.columns.len()
    }

    /// Whether the set is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.columns.is_empty()
    }
}

impl Default for ColumnInstanceSet {
    fn default() -> Self {
        Self::new(Vec::new())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn column_instance_defaults() {
        let col = ColumnInstance::new(GeoCoord::from_lat_lon(0.0, 0.0), 100.0, 10.0);
        assert!((col.height - 100.0).abs() < 1e-9);
        assert!((col.width - 10.0).abs() < 1e-9);
        assert!((col.base - 0.0).abs() < 1e-9);
        assert!(col.color.is_none());
        assert_eq!(col.pick_id, 0);
    }

    #[test]
    fn column_instance_builder() {
        let col = ColumnInstance::new(GeoCoord::from_lat_lon(0.0, 0.0), 50.0, 5.0)
            .with_pick_id(42)
            .with_color([1.0, 0.0, 0.0, 1.0])
            .with_base(10.0);
        assert_eq!(col.pick_id, 42);
        assert_eq!(col.color, Some([1.0, 0.0, 0.0, 1.0]));
        assert!((col.base - 10.0).abs() < 1e-9);
    }

    #[test]
    fn column_instance_set_len() {
        let set = ColumnInstanceSet::new(vec![
            ColumnInstance::new(GeoCoord::from_lat_lon(0.0, 0.0), 10.0, 5.0),
            ColumnInstance::new(GeoCoord::from_lat_lon(1.0, 1.0), 20.0, 5.0),
        ]);
        assert_eq!(set.len(), 2);
        assert!(!set.is_empty());
    }

    #[test]
    fn column_instance_set_empty() {
        let set = ColumnInstanceSet::default();
        assert!(set.is_empty());
        assert_eq!(set.generation, 0);
    }
}