semantic-scene 0.1.1

Rust parser for semantic scene descriptors, currently focused on Habitat-Sim Matterport3D .house files.
Documentation
//! Semantic region model.

use std::fmt;

use crate::{Aabb, Vec2, Vec3};

use super::SemanticObject;

/// Represents a semantic region, typically a room in a level of a house.
///
/// MP3D descriptors provide an AABB but not polyloop geometry. Polyloop-related
/// accessors return empty slices unless a dataset supplies that geometry.
#[derive(Debug, Clone, PartialEq)]
pub struct SemanticRegion<ObjectCategory, RegionCategory> {
    index: i32,
    category: RegionCategory,
    position: Vec3,
    aabb: Aabb,
    objects: Vec<SemanticObject<ObjectCategory>>,
    poly_loop_points: Vec<Vec2>,
    volume_edges: Vec<Vec<Vec3>>,
    floor_height: f64,
    extrusion_height: f64,
    area: f64,
}

impl<ObjectCategory, RegionCategory> SemanticRegion<ObjectCategory, RegionCategory> {
    /// Creates a semantic region.
    ///
    /// `index` is the original descriptor index for this region.
    #[must_use]
    pub fn new(
        index: i32,
        category: RegionCategory,
        position: Vec3,
        aabb: Aabb,
        objects: Vec<SemanticObject<ObjectCategory>>,
    ) -> Self {
        Self {
            index,
            category,
            position,
            aabb,
            objects,
            poly_loop_points: Vec::new(),
            volume_edges: Vec::new(),
            floor_height: f64::from(aabb.min.1),
            extrusion_height: f64::from(aabb.max.1 - aabb.min.1),
            area: f64::from((aabb.max.0 - aabb.min.0).abs() * (aabb.max.2 - aabb.min.2).abs()),
        }
    }

    /// Returns the source descriptor index.
    #[must_use]
    pub const fn index(&self) -> i32 {
        self.index
    }

    /// Returns the semantic category of the region.
    #[must_use]
    pub const fn category(&self) -> &RegionCategory {
        &self.category
    }

    /// Returns the region position from the source descriptor.
    #[must_use]
    pub const fn position(&self) -> Vec3 {
        self.position
    }

    /// Returns the region axis-aligned bounding box.
    #[must_use]
    pub const fn aabb(&self) -> &Aabb {
        &self.aabb
    }

    /// Returns objects contained by this region.
    #[must_use]
    pub fn objects(&self) -> &[SemanticObject<ObjectCategory>] {
        &self.objects
    }

    /// Tests whether this region contains the point.
    ///
    /// For MP3D-loaded scenes this uses the region AABB. Future descriptor
    /// loaders with polyloop geometry can provide tighter containment.
    #[must_use]
    pub fn contains(&self, point: Vec3) -> bool {
        self.aabb.contains(point)
    }

    /// Returns points making up the region's floor-parallel polyloop.
    #[must_use]
    pub fn poly_loop_points(&self) -> &[Vec2] {
        &self.poly_loop_points
    }

    /// Returns bounding volume edges for visualization.
    #[must_use]
    pub fn volume_edges(&self) -> &[Vec<Vec3>] {
        &self.volume_edges
    }

    /// Returns the floor height above the x-z plane.
    #[must_use]
    pub const fn floor_height(&self) -> f64 {
        self.floor_height
    }

    /// Returns the extrusion height above the floor.
    #[must_use]
    pub const fn extrusion_height(&self) -> f64 {
        self.extrusion_height
    }

    /// Returns the area of the region base.
    #[must_use]
    pub const fn area(&self) -> f64 {
        self.area
    }

    /// Returns the volume of the region bounds.
    #[must_use]
    pub const fn volume(&self) -> f64 {
        self.area * self.extrusion_height
    }
}

impl<OC, RC: fmt::Display> fmt::Display for SemanticRegion<OC, RC> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "region {}: category={}, objects={}, bounds=[{}]",
            self.index,
            self.category,
            self.objects.len(),
            self.aabb
        )
    }
}