#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
use core::{iter::Sum, ops::Add};
const INV6: f64 = 1.0 / 6.0;
const INV120: f64 = 1.0 / 120.0;
#[cfg(feature = "gltf")]
#[inline]
fn cast_point(point: [f32; 3]) -> [f64; 3] {
[point[0] as f64, point[1] as f64, point[2] as f64]
}
#[cfg(feature = "gltf")]
#[derive(Debug)]
pub enum GltfErr {
LoadErr(gltf::Error),
InvalidIndex,
NotTriangleList,
NoVertices,
NoIndices,
ZeroVolume,
}
#[cfg(feature = "gltf")]
impl std::fmt::Display for GltfErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use GltfErr::*;
match self {
LoadErr(_) => f.write_str("Failed to load the gltf/glb file"),
InvalidIndex => f.write_str("Invalid mesh index"),
NotTriangleList => f.write_str("One of the primitives isn't a triangle list"),
NoVertices => f.write_str("One of the primitives doesn't have vertices"),
NoIndices => f.write_str("One of the primitives doesn't have indices"),
ZeroVolume => f.write_str("The calculated volume is zero"),
}
}
}
#[cfg(feature = "gltf")]
impl std::error::Error for GltfErr {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use GltfErr::*;
match self {
LoadErr(e) => Some(e),
InvalidIndex | NotTriangleList | NoVertices | NoIndices | ZeroVolume => None,
}
}
}
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Inertia {
pub xx: f64,
pub yy: f64,
pub zz: f64,
pub xy: f64,
pub xz: f64,
pub yz: f64,
}
impl Inertia {
#[inline]
pub fn to_cols_array_2d(&self) -> [[f64; 3]; 3] {
[
[self.xx, self.xy, self.xz],
[self.xy, self.yy, self.yz],
[self.xz, self.yz, self.zz],
]
}
}
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MassProperties {
density: f64,
pub mass: f64,
pub center_of_mass: [f64; 3],
pub inertia: Inertia,
}
pub struct TriangleContrib {
mass: f64,
com_x: f64,
com_y: f64,
com_z: f64,
xx: f64,
yy: f64,
zz: f64,
xy: f64,
xz: f64,
yz: f64,
}
impl TriangleContrib {
pub const ZERO: Self = TriangleContrib {
mass: 0.0,
com_x: 0.0,
com_y: 0.0,
com_z: 0.0,
xx: 0.0,
yy: 0.0,
zz: 0.0,
xy: 0.0,
xz: 0.0,
yz: 0.0,
};
#[inline]
pub fn new([x1, y1, z1]: [f64; 3], [x2, y2, z2]: [f64; 3], [x3, y3, z3]: [f64; 3]) -> Self {
#[cfg(not(feature = "fma"))]
let v = x1 * (y2 * z3 - y3 * z2) + y1 * (z2 * x3 - x2 * z3) + z1 * (x2 * y3 - x3 * y2);
#[cfg(feature = "fma")]
let v = x1.mul_add(
y2.mul_add(z3, -y3 * z2),
y1.mul_add(z2.mul_add(x3, -x2 * z3), z1 * (x2.mul_add(y3, -x3 * y2))),
);
let x_sum = x1 + x2 + x3;
let y_sum = y1 + y2 + y3;
let z_sum = z1 + z2 + z3;
Self {
mass: v,
com_x: v * x_sum,
com_y: v * y_sum,
com_z: v * z_sum,
#[cfg(not(feature = "fma"))]
xx: v * (x1 * x1 + x2 * x2 + x3 * x3 + x_sum * x_sum),
#[cfg(feature = "fma")]
xx: v * (x1.mul_add(x1, x2.mul_add(x2, x3.mul_add(x3, x_sum * x_sum)))),
#[cfg(not(feature = "fma"))]
yy: v * (y1 * y1 + y2 * y2 + y3 * y3 + y_sum * y_sum),
#[cfg(feature = "fma")]
yy: v * (y1.mul_add(y1, y2.mul_add(y2, y3.mul_add(y3, y_sum * y_sum)))),
#[cfg(not(feature = "fma"))]
zz: v * (z1 * z1 + z2 * z2 + z3 * z3 + z_sum * z_sum),
#[cfg(feature = "fma")]
zz: v * (z1.mul_add(z1, z2.mul_add(z2, z3.mul_add(z3, z_sum * z_sum)))),
#[cfg(not(feature = "fma"))]
xy: v * (y1 * x1 + y2 * x2 + y3 * x3 + y_sum * x_sum),
#[cfg(feature = "fma")]
xy: v * (y1.mul_add(x1, y2.mul_add(x2, y3.mul_add(x3, y_sum * x_sum)))),
#[cfg(not(feature = "fma"))]
xz: v * (z1 * x1 + z2 * x2 + z3 * x3 + z_sum * x_sum),
#[cfg(feature = "fma")]
xz: v * (z1.mul_add(x1, z2.mul_add(x2, z3.mul_add(x3, z_sum * x_sum)))),
#[cfg(not(feature = "fma"))]
yz: v * (z1 * y1 + z2 * y2 + z3 * y3 + z_sum * y_sum),
#[cfg(feature = "fma")]
yz: v * (z1.mul_add(y1, z2.mul_add(y2, z3.mul_add(y3, z_sum * y_sum)))),
}
}
}
impl Add for TriangleContrib {
type Output = Self;
#[inline]
fn add(mut self, rhs: Self) -> Self::Output {
self.mass += rhs.mass;
self.com_x += rhs.com_x;
self.com_y += rhs.com_y;
self.com_z += rhs.com_z;
self.xx += rhs.xx;
self.yy += rhs.yy;
self.zz += rhs.zz;
self.xy += rhs.xy;
self.xz += rhs.xz;
self.yz += rhs.yz;
self
}
}
impl Sum for TriangleContrib {
#[inline]
fn sum<I: Iterator<Item = TriangleContrib>>(iter: I) -> Self {
iter.fold(Self::ZERO, |acc, contrib| acc + contrib)
}
}
impl MassProperties {
pub fn from_contrib_sum(sum: TriangleContrib) -> Option<Self> {
if sum.mass == 0.0 {
return None;
}
let r = 1.0 / (4.0 * sum.mass);
let com_x = sum.com_x * r;
let com_y = sum.com_y * r;
let com_z = sum.com_z * r;
let mass = sum.mass * INV6;
let mass_com_x = mass * com_x;
let mass_com_y = mass * com_y;
#[cfg(not(feature = "fma"))]
let xy = mass_com_x * com_y - sum.xy * INV120;
#[cfg(feature = "fma")]
let xy = mass_com_x.mul_add(com_y, -sum.xy * INV120);
#[cfg(not(feature = "fma"))]
let xz = mass_com_x * com_z - sum.xz * INV120;
#[cfg(feature = "fma")]
let xz = mass_com_x.mul_add(com_z, -sum.xz * INV120);
#[cfg(not(feature = "fma"))]
let yz = mass_com_y * com_z - sum.yz * INV120;
#[cfg(feature = "fma")]
let yz = mass_com_y.mul_add(com_z, -sum.yz * INV120);
#[cfg(not(feature = "fma"))]
let xx = sum.xx * INV120 - mass_com_x * com_x;
#[cfg(feature = "fma")]
let xx = sum.xx.mul_add(INV120, -mass_com_x * com_x);
#[cfg(not(feature = "fma"))]
let yy = sum.yy * INV120 - mass_com_y * com_y;
#[cfg(feature = "fma")]
let yy = sum.yy.mul_add(INV120, -mass_com_y * com_y);
#[cfg(not(feature = "fma"))]
let zz = sum.zz * INV120 - mass * com_z * com_z;
#[cfg(feature = "fma")]
let zz = sum.zz.mul_add(INV120, -mass * com_z * com_z);
Some(Self {
density: 1.0,
mass,
center_of_mass: [com_x, com_y, com_z],
inertia: Inertia {
xx: yy + zz,
yy: zz + xx,
zz: xx + yy,
xy,
yz,
xz,
},
})
}
#[cfg(feature = "gltf")]
pub fn from_gltf<P: AsRef<std::path::Path>>(path: P, mesh: usize) -> Result<Self, GltfErr> {
use gltf::mesh::util::ReadIndices;
let path = path.as_ref();
let mut gltf = gltf::Gltf::open(path).map_err(GltfErr::LoadErr)?;
let blob = gltf.blob.take();
let mesh = gltf.meshes().nth(mesh).ok_or(GltfErr::InvalidIndex)?;
let buffers =
gltf::import_buffers(&gltf.document, path.parent(), blob).map_err(GltfErr::LoadErr)?;
let sum = mesh
.primitives()
.try_fold(TriangleContrib::ZERO, |acc, primitive| {
if primitive.mode() != gltf::mesh::Mode::Triangles {
return Err(GltfErr::NotTriangleList);
}
let reader = primitive
.reader(|buffer| buffers.get(buffer.index()).map(|data| data.0.as_slice()));
let vertices = reader
.read_positions()
.ok_or(GltfErr::NoVertices)?
.collect::<Vec<_>>();
let indices = reader.read_indices().ok_or(GltfErr::NoIndices)?;
macro_rules! props {
($indices:ident) => {
(0..$indices.len() / 3)
.map(|_| {
let i0 = $indices.next().unwrap() as usize;
let i1 = $indices.next().unwrap() as usize;
let i2 = $indices.next().unwrap() as usize;
let p0 = vertices[i0];
let p1 = vertices[i1];
let p2 = vertices[i2];
TriangleContrib::new(cast_point(p0), cast_point(p1), cast_point(p2))
})
.sum()
};
}
let sum = match indices {
ReadIndices::U8(mut indices) => props!(indices),
ReadIndices::U16(mut indices) => props!(indices),
ReadIndices::U32(mut indices) => props!(indices),
};
Ok(acc + sum)
})?;
Self::from_contrib_sum(sum).ok_or(GltfErr::ZeroVolume)
}
#[inline]
pub fn density(&self) -> f64 {
self.density
}
pub fn with_density(mut self, density: f64) -> Self {
debug_assert_ne!(density, 0.0);
let ratio = density / self.density;
self.density = density;
self.mass *= ratio;
self.inertia.xx *= ratio;
self.inertia.yy *= ratio;
self.inertia.zz *= ratio;
self.inertia.xy *= ratio;
self.inertia.xz *= ratio;
self.inertia.yz *= ratio;
self
}
#[inline]
pub fn volume(&self) -> f64 {
self.mass / self.density
}
}