semantic-scene 0.1.0

Rust parser for Habitat-Sim `SemanticScene` descriptors.
Documentation
//! Semantic category types and mapping selectors.

use std::fmt;

/// Category mapping namespace.
///
/// Habitat-Sim categories expose `index(mapping)` and `name(mapping)`. This
/// enum provides the same choice without stringly typed mapping names.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CategoryMapping {
    /// Loader-specific default mapping.
    Default,
    /// Raw dataset category mapping.
    Raw,
    /// `Matterport3D` `mpcat40` mapping.
    MpCat40,
    /// Region category mapping.
    Category,
}

/// Semantic object category.
///
/// For MP3D, the default mapping is `mpcat40`, and `Raw` returns the original
/// dataset category mapping.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Category {
    source_index: i32,
    raw_index: i32,
    raw_name: String,
    mpcat40_index: i32,
    mpcat40_name: String,
}

impl Category {
    #[must_use]
    pub(crate) const fn new(
        source_index: i32,
        raw_index: i32,
        raw_name: String,
        mpcat40_index: i32,
        mpcat40_name: String,
    ) -> Self {
        Self {
            source_index,
            raw_index,
            raw_name,
            mpcat40_index,
            mpcat40_name,
        }
    }

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

    /// Returns the category index under the requested mapping.
    ///
    /// Mirrors Habitat-Sim's `SemanticCategory::index(mapping)` behavior.
    #[must_use]
    pub const fn index(&self, mapping: CategoryMapping) -> Option<i32> {
        match mapping {
            CategoryMapping::Default | CategoryMapping::MpCat40 => Some(self.mpcat40_index),
            CategoryMapping::Raw => Some(self.raw_index),
            CategoryMapping::Category => None,
        }
    }

    /// Returns the category name under the requested mapping.
    ///
    /// Mirrors Habitat-Sim's `SemanticCategory::name(mapping)` behavior.
    #[must_use]
    pub fn name(&self, mapping: CategoryMapping) -> Option<&str> {
        match mapping {
            CategoryMapping::Default | CategoryMapping::MpCat40 => Some(&self.mpcat40_name),
            CategoryMapping::Raw => Some(&self.raw_name),
            CategoryMapping::Category => None,
        }
    }
}

impl fmt::Display for Category {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "mpcat40={}({}), raw={}({})",
            self.mpcat40_name, self.mpcat40_index, self.raw_name, self.raw_index
        )
    }
}

/// Semantic region category.
///
/// MP3D region categories are encoded as a single character in each `R` record
/// and mapped to Habitat-Sim's room category names.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegionCategory {
    code: char,
}

impl RegionCategory {
    #[must_use]
    pub(crate) const fn new(code: char) -> Self {
        Self { code }
    }

    /// Returns the MP3D region category code.
    #[must_use]
    pub const fn code(self) -> char {
        self.code
    }

    /// Returns this region category's ordinal in Habitat-Sim's MP3D category map.
    #[must_use]
    pub fn index(self, _mapping: CategoryMapping) -> Option<i32> {
        region_category_entries()
            .iter()
            .position(|(code, _)| *code == self.code)
            .and_then(|index| i32::try_from(index).ok())
    }

    /// Returns this region category's Habitat-Sim MP3D category name.
    #[must_use]
    pub fn name(self, _mapping: CategoryMapping) -> Option<&'static str> {
        region_category_entries()
            .iter()
            .find_map(|(code, name)| (*code == self.code).then_some(*name))
    }
}

impl fmt::Display for RegionCategory {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = self.name(CategoryMapping::Default).unwrap_or("unknown");
        write!(formatter, "{name}({})", self.code)
    }
}

const fn region_category_entries() -> &'static [(char, &'static str)] {
    &[
        ('a', "bathroom"),
        ('b', "bedroom"),
        ('c', "closet"),
        ('d', "dining room"),
        ('e', "entryway/foyer/lobby"),
        ('f', "familyroom/lounge"),
        ('g', "garage"),
        ('h', "hallway"),
        ('i', "library"),
        ('j', "laundryroom/mudroom"),
        ('k', "kitchen"),
        ('l', "living room"),
        ('m', "meetingroom/conferenceroom"),
        ('n', "lounge"),
        ('o', "office"),
        ('p', "porch/terrace/deck"),
        ('r', "rec/game"),
        ('s', "stairs"),
        ('t', "toilet"),
        ('u', "utilityroom/toolroom"),
        ('v', "tv"),
        ('w', "workout/gym/exercise"),
        ('x', "outdoor"),
        ('y', "balcony"),
        ('z', "other room"),
        ('B', "bar"),
        ('C', "classroom"),
        ('D', "dining booth"),
        ('S', "spa/sauna"),
        ('Z', "junk"),
    ]
}