Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
5.6 kB · 145 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146//! Mesh contract types: the renderer-facing CPU model and its provenance.//!//! [`MeshFormat`], [`LengthUnit`], and [`Units`] are re-exported from//! [`polymodel_renderer_protocol`] so the app, worker, and mesh crate share a single//! definition without the mesh crate pulling `three-d-asset` into the app.
pub use polymodel_renderer_protocol::{LengthUnit, MeshFormat, Units};
// These `three-d-asset` types are the owned CPU-side contract shared with the renderer// worker. They do not include GPU/runtime state.pub use three_d_asset::{AxisAlignedBoundingBox, Geometry, Model, PointCloud, TriMesh};
/// Camera framing hint derived from a mesh's axis-aligned bounding box: the centre the/// camera should orbit and a bounding-radius estimate to frame it.#[derive(Debug, Clone, Copy, PartialEq)]pub struct CameraFit { /// AABB centre. pub center: [f32; 3], /// Half the largest AABB extent. pub radius: f32,}
/// A Polymodel model: owned CPU geometry/material data plus format provenance and units.////// glTF/OBJ keep the per-primitive geometry/material structure exposed by/// `three-d-asset`; STL is represented as a one-part triangle model. Triangle normals are/// geometry-derived during validation. Point-cloud geometry is preserved even though the/// current worker renders triangles first.#[derive(Debug)]pub struct ModelMesh { /// The owned CPU model, including primitive boundaries, transforms, materials, and /// decoded textures. pub model: Model, /// The format this mesh was loaded from. pub format: MeshFormat, /// Unit provenance and assumption. pub units: Units,}
impl ModelMesh { /// Construct a model contract from an owned CPU model. pub fn new(model: Model, format: MeshFormat, units: Units) -> Self { Self { model, format, units, } }
/// Number of CPU primitives/parts preserved from the source model. pub fn part_count(&self) -> usize { self.model.geometries.len() }
/// The axis-aligned bounding box over all preserved primitive positions in world space. pub fn aabb(&self) -> AxisAlignedBoundingBox { let mut aabb = AxisAlignedBoundingBox::EMPTY; for primitive in &self.model.geometries { match &primitive.geometry { Geometry::Triangles(mesh) => { aabb.expand_with_aabb(AxisAlignedBoundingBox::new_with_transformed_positions( &mesh.positions.to_f32(), primitive.transformation, )); } Geometry::Points(points) => { aabb.expand_with_aabb(AxisAlignedBoundingBox::new_with_transformed_positions( &points.positions.to_f32(), primitive.transformation, )); } } } aabb }
/// Camera framing derived from [`Self::aabb`]: `center` is the box centre and /// `radius` is half the largest box extent. pub fn camera_fit(&self) -> CameraFit { let aabb = self.aabb(); let center = aabb.center(); let size = aabb.size(); CameraFit { center: [center.x, center.y, center.z], radius: size.x.max(size.y).max(size.z) / 2.0, } }}
/// Errors that can occur loading a mesh.#[derive(Debug, thiserror::Error)]pub enum MeshLoadError { /// The input was empty (zero bytes). #[error("mesh input is empty")] Empty, /// The bytes could not be parsed as a valid mesh of the expected format. #[error("failed to parse STL: {0}")] Parse(#[from] std::io::Error), /// A format parsed by `three-d-asset` (OBJ/glTF) failed to decode, including a /// missing or undecodable companion resource (buffer/texture). #[error("mesh decode failed: {0}")] Decode(String), /// A glTF companion URI is unsafe to fetch or load. #[error("unsafe companion URI: {0}")] UnsafeCompanionUri(String), /// The mesh parsed but is degenerate: no triangles, an out-of-range index, a /// non-finite position, or a point-sized bounding box. #[error("mesh is degenerate")] Degenerate, /// The format is not supported by the active parser. Returned by the /// [`RemoteMeshParser`](crate::parser::RemoteMeshParser) seam when no conversion is /// wired in (reserved for future server-side formats like STEP). #[error("unsupported mesh format")] UnsupportedFormat,}
/// Lightweight stats for reporting back to the main thread.impl ModelMesh { /// Number of source positions across all preserved parts. /// /// This is a sum of part-local triangle vertices and point-cloud points, not a global /// deduplicated vertex count. pub fn vertex_count(&self) -> u32 { self.model .geometries .iter() .map(|primitive| match &primitive.geometry { Geometry::Triangles(mesh) => mesh.positions.len() as u32, Geometry::Points(points) => points.positions.len() as u32, }) .sum() }
/// Number of triangles across triangle parts. Point-cloud parts contribute zero. pub fn triangle_count(&self) -> u32 { self.model .geometries .iter() .map(|primitive| match &primitive.geometry { Geometry::Triangles(mesh) => mesh.triangle_count() as u32, Geometry::Points(_) => 0, }) .sum() }}