linear-sim 0.8.0

Minimal linear 3D simulation library
Documentation
//! Components for use by entities.

use std;
use enumflags2;
use derive_more::From;
#[cfg(feature = "derive_serdes")]
use serde::{Deserialize, Serialize};

use crate::{force, geometry, math};

#[expect(clippy::excessive_precision)]
#[expect(clippy::unreadable_literal)]
pub const MATERIAL_WOOD    : Material  = Material {
  restitution: math::Normalized::noisy_f64 (0.6),
  friction:    math::NonNegative::noisy_f64 (0.63245553203367586639)
  //restitution: 0.6, friction: 0.4
};
#[expect(clippy::excessive_precision)]
#[expect(clippy::unreadable_literal)]
pub const MATERIAL_STONE   : Material = Material {
  restitution: math::Normalized::noisy_f64 (0.8),
  friction:    math::NonNegative::noisy_f64 (0.77459666924148337703)
  //restitution: 0.8, friction: 0.6
};
#[expect(clippy::excessive_precision)]
#[expect(clippy::unreadable_literal)]
pub const MATERIAL_COPPER  : Material = Material {
  restitution: math::Normalized::noisy_f64 (0.6),
  friction:    math::NonNegative::noisy_f64 (0.63245553203367586639)
  //restitution: 0.15, friction: 0.4
};
#[expect(clippy::excessive_precision)]
#[expect(clippy::unreadable_literal)]
pub const MATERIAL_BRONZE  : Material = Material {
  restitution: math::Normalized::noisy_f64 (0.6),
  friction:    math::NonNegative::noisy_f64 (0.54772255750516611345)
  //restitution: 0.6, friction: 0.3
};
pub const MATERIAL_LEATHER : Material = Material {
  restitution: math::Normalized::noisy_f64 (0.1),
  friction:    math::NonNegative::noisy_f64 (std::f64::consts::FRAC_1_SQRT_2)
  //restitution: 0.1, friction: 0.5
};

/// A point in 3D space
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, From)]
pub struct Position (pub math::Point3 <f64>);

/// First (velocity) and second (acceleration) time derivatives of position
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Derivatives {
  /// First time derivative of position
  pub velocity     : math::Vector3 <f64>,
  /// Second time derivative of position
  pub acceleration : math::Vector3 <f64>,
  /// First time derivative of momentum.
  ///
  /// This vector is intended to be cleared and then accumulated each step.
  pub force        : math::Vector3 <f64>,
  pub force_flags  : enumflags2::BitFlags <force::Flag>
}

/// A drag coefficient for dynamic objects
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Drag (pub f64);

/// A bounding volume
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, PartialEq, From)]
pub struct Bound (pub geometry::shape::Variant <f64>);

/// A point mass; does *not* currently enforce that mass is nonzero or positive
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Mass {
  mass            : f64,
  mass_reciprocal : f64
}

/// Surface material properties
#[cfg_attr(feature = "derive_serdes", derive(Deserialize, Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Material {
  /// A generic restitution is in [0.0,1.0] with 1.0 being perfectly elastic and
  /// 0.0 perfectly inelastic.
  ///
  /// When a pair of material objects collide, their respective restitution
  /// coefficients are multiplied to give the restitution coefficient for that
  /// collision.
  pub restitution : math::Normalized <f64>,
  /// A generic friction coefficient is in [0.0,\infty] with 0.0 being perfectly
  /// frictionless and 1.0 being about equivalent to concrete on rubber.
  ///
  /// When a pair of material objects have contact, the average of their
  /// respective friction coefficients is computed to give the friction
  /// coefficient for that contact.
  pub friction    : math::NonNegative <f64>
}

impl Position {
  #[inline]
  pub fn origin() -> Self {
    Position (math::Point::origin())
  }
}

// NOTE: calling the macro with non snake-case param causes a non_snake_case warning;
// the `expect` attribute cannot be applied at the call site so we created this private
// module to apply it to the module
#[expect(non_snake_case)]
mod bound {
  use super::*;
  macro impl_from_try_into {
    ($shape:ident, $variant:ident) => {
      impl From <geometry::shape::$shape <f64>> for Bound {
        fn from ($shape : geometry::shape::$shape <f64>) -> Self {
          Bound (geometry::shape::Variant::$variant ($shape.into()))
        }
      }
      impl TryInto <geometry::shape::$shape <f64>> for Bound {
        type Error = Self;
        fn try_into (self) -> Result <geometry::shape::$shape <f64>, Self> {
          geometry::shape::$variant::try_from (self.0).map_err (|err| Bound (err.input))
            .and_then (|variant| geometry::shape::$shape::try_from (variant)
              .map_err (|err| Bound (geometry::shape::Variant::$variant (err.input))))
        }
      }
    }
  }
  impl_from_try_into!{Sphere, Bounded}
  impl_from_try_into!{Capsule, Bounded}
  impl_from_try_into!{Cylinder, Bounded}
  impl_from_try_into!{Cone, Bounded}
  impl_from_try_into!{Cube, Bounded}
  impl_from_try_into!{Cuboid, Bounded}
  impl_from_try_into!{Hull, Bounded}
  impl_from_try_into!{Halfspace, Unbounded}
  //impl_from_try_into!{Orthant, Unbounded}
  // Orthant is not generic
  impl From <geometry::shape::Orthant> for Bound {
    fn from (Orthant : geometry::shape::Orthant) -> Self {
      Bound (geometry::shape::Variant::Unbounded (Orthant.into()))
    }
  }
  impl TryInto <geometry::shape::Orthant> for Bound {
    type Error = Self;
    fn try_into (self) -> Result <geometry::shape::Orthant, Self> {
      geometry::shape::Unbounded::try_from (self.0).map_err (|err| Bound (err.input))
        .and_then (|variant| geometry::shape::Orthant::try_from (variant)
          .map_err (|err| Bound (geometry::shape::Variant::Unbounded (err.input))))
    }
  }
}

impl Mass {
  #[inline]
  pub fn new (mass : f64) -> Self {
    let mass_reciprocal = 1.0 / mass;
    Self { mass, mass_reciprocal }
  }
  #[inline]
  pub const fn mass (&self) -> f64 {
    self.mass
  }
  #[inline]
  pub const fn mass_reciprocal (&self) -> f64 {
    self.mass_reciprocal
  }
  #[inline]
  pub fn set_mass (&mut self, mass : f64) {
    self.mass = mass;
    self.mass_reciprocal = 1.0 / self.mass;
  }
}
impl std::ops::Add <Self> for Mass {
  type Output = Self;
  fn add (self, rhs : Self) -> Self {
    let mass = self.mass + rhs.mass;
    let mass_reciprocal = 1.0 / mass;
    Mass { mass, mass_reciprocal }
  }
}

impl Derivatives {
  #[inline]
  pub fn zero() -> Self {
    let velocity     = math::Vector3::zero();
    let acceleration = math::Vector3::zero();
    let force        = math::Vector3::zero();
    let force_flags  = enumflags2::BitFlags::all();
    Derivatives { velocity, acceleration, force, force_flags }
  }
}

impl Drag {
  #[inline]
  pub const fn zero() -> Self {
    Drag (0.0)
  }
}