rapier3d_meshloader/lib.rs
1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3
4pub use mesh_loader;
5use mesh_loader::{Material, Mesh};
6use rapier3d::geometry::{MeshConverter, SharedShape};
7use rapier3d::math::{Pose, Vector};
8use rapier3d::prelude::MeshConverterError;
9use std::path::Path;
10
11/// The result of loading a shape.
12pub struct LoadedShape {
13 /// The shape loaded from the file and converted by the [`MeshConverter`].
14 pub shape: SharedShape,
15 /// The shape's pose.
16 pub pose: Pose,
17 /// The raw mesh read from the file without any modification.
18 pub raw_mesh: Mesh,
19 /// The material declared in the source asset for this mesh. For an
20 /// OBJ this is the `.mtl` material referenced by the mesh's
21 /// `usemtl` group (loaded by `mesh-loader` when the `.mtl` sits
22 /// alongside the `.obj`). STL files don't carry materials, so this
23 /// is the default-constructed `Material`.
24 pub material: Material,
25}
26
27/// Error while loading an STL file.
28#[derive(thiserror::Error, Debug)]
29pub enum MeshLoaderError {
30 /// An error triggered by rapier’s [`MeshConverter`].
31 #[error(transparent)]
32 MeshConverter(#[from] MeshConverterError),
33 /// A generic IO error.
34 #[error(transparent)]
35 Io(#[from] std::io::Error),
36}
37
38/// Loads parry shapes from a file.
39///
40/// # Parameters
41/// - `path`: the file’s path.
42/// - `converter`: controls how the shapes are computed from the content. In particular, it lets
43/// you specify if the computed [`SharedShape`] is a triangle mesh, its convex hull,
44/// bounding box, etc.
45/// - `scale`: the scaling factor applied to the geometry input to the `converter`. This scale will
46/// affect at the geometric level the [`LoadedShape::shape`]. Note that raw mesh value stored
47/// in [`LoadedShape::raw_mesh`] remains unscaled.
48pub fn load_from_path(
49 path: impl AsRef<Path>,
50 converter: &MeshConverter,
51 scale: Vector,
52) -> Result<Vec<Result<LoadedShape, MeshConverterError>>, MeshLoaderError> {
53 let loader = mesh_loader::Loader::default();
54 let mut colliders = vec![];
55 let scene = loader.load(path)?;
56 // mesh-loader's OBJ backend aligns `scene.materials[i]` with
57 // `scene.meshes[i]` (it expands per-mesh from the material_index),
58 // so zipping is correct here. STL produces empty materials; pair
59 // each mesh with a default material in that case.
60 let mut materials = scene.materials.into_iter();
61 for raw_mesh in scene.meshes.into_iter() {
62 let material = materials.next().unwrap_or_default();
63 let shape = load_from_raw_mesh(&raw_mesh, converter, scale);
64
65 colliders.push(shape.map(|(shape, pose)| LoadedShape {
66 shape,
67 pose,
68 raw_mesh,
69 material,
70 }));
71 }
72 Ok(colliders)
73}
74
75/// Loads an file as a shape from a preloaded raw [`mesh_loader::Mesh`].
76///
77/// # Parameters
78/// - `raw_mesh`: the raw mesh.
79/// - `converter`: controls how the shape is computed from the STL content. In particular, it lets
80/// you specify if the computed [`SharedShape`] is a triangle mesh, its convex hull,
81/// bounding box, etc.
82/// - `scale`: the scaling factor applied to the geometry input to the `converter`. This scale will
83/// affect at the geometric level the [`LoadedShape::shape`]. Note that raw mesh value stored
84/// in [`LoadedShape::raw_mesh`] remains unscaled.
85pub fn load_from_raw_mesh(
86 raw_mesh: &Mesh,
87 converter: &MeshConverter,
88 scale: Vector,
89) -> Result<(SharedShape, Pose), MeshConverterError> {
90 let vertices: Vec<_> = raw_mesh
91 .vertices
92 .iter()
93 .map(|xyz| Vector::new(xyz[0], xyz[1], xyz[2]) * scale)
94 .collect();
95 let indices: Vec<_> = raw_mesh.faces.clone();
96 converter.convert(vertices, indices)
97}