semantic-scene 0.1.0

Rust parser for Habitat-Sim `SemanticScene` descriptors.
Documentation
//! Geometry primitives used by `SemanticScene`.

use std::fmt;

/// Two-dimensional vector.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vec2(pub f32, pub f32);

/// Rotation requested while loading a scene descriptor.
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub enum Rotation3 {
    /// Do not rotate loaded coordinates.
    #[default]
    Identity,
    /// Quaternion represented as `[x, y, z, w]`.
    ///
    /// Present to reserve Habitat-Sim's rotation option shape. MP3D loading
    /// currently rejects this variant with `UnsupportedOption`.
    Quaternion([f32; 4]),
}

/// Three-dimensional vector.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vec3(pub f32, pub f32, pub f32);

impl Vec3 {
    /// Returns the cross product of two vectors.
    #[must_use]
    pub fn cross(self, other: Self) -> Self {
        Self(
            self.1.mul_add(other.2, -(self.2 * other.1)),
            self.2.mul_add(other.0, -(self.0 * other.2)),
            self.0.mul_add(other.1, -(self.1 * other.0)),
        )
    }
}

impl fmt::Display for Vec2 {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "({:.2}, {:.2})", self.0, self.1)
    }
}

impl fmt::Display for Vec3 {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "({:.2}, {:.2}, {:.2})", self.0, self.1, self.2)
    }
}

/// Axis-aligned bounding box.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Aabb {
    /// Minimum corner of the box.
    pub min: Vec3,
    /// Maximum corner of the box.
    pub max: Vec3,
}

impl Aabb {
    /// Returns whether the point lies inside or on the box boundary.
    #[must_use]
    pub fn contains(self, point: Vec3) -> bool {
        point.0 >= self.min.0
            && point.0 <= self.max.0
            && point.1 >= self.min.1
            && point.1 <= self.max.1
            && point.2 >= self.min.2
            && point.2 <= self.max.2
    }
}

impl fmt::Display for Aabb {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "min={}, max={}", self.min, self.max)
    }
}

/// Oriented bounding box.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Obb {
    /// Box center.
    pub center: Vec3,
    /// First local box axis.
    pub axis0: Vec3,
    /// Second local box axis.
    pub axis1: Vec3,
    /// Third local box axis.
    pub axis2: Vec3,
    /// Half of the box dimensions along each local axis.
    pub half_extents: Vec3,
}

impl Obb {
    /// Returns the axis-aligned bounding box enclosing this oriented box.
    #[must_use]
    pub fn to_aabb(self) -> Aabb {
        let ex = extent(self.axis0.0, self.axis1.0, self.axis2.0, self.half_extents);
        let ey = extent(self.axis0.1, self.axis1.1, self.axis2.1, self.half_extents);
        let ez = extent(self.axis0.2, self.axis1.2, self.axis2.2, self.half_extents);

        Aabb {
            min: Vec3(self.center.0 - ex, self.center.1 - ey, self.center.2 - ez),
            max: Vec3(self.center.0 + ex, self.center.1 + ey, self.center.2 + ez),
        }
    }
}

impl fmt::Display for Obb {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "center={}, half_extents={}",
            self.center, self.half_extents
        )
    }
}

fn extent(axis0: f32, axis1: f32, axis2: f32, half_extents: Vec3) -> f32 {
    axis2.abs().mul_add(
        half_extents.2,
        axis0
            .abs()
            .mul_add(half_extents.0, axis1.abs() * half_extents.1),
    )
}