bevy_obj 0.18.2

Wavefront OBJ mesh asset loader plugin for the Bevy engine
Documentation
use crate::ObjSettings;
use bevy::asset::{AssetLoader, LoadContext, io::Reader};
use bevy::mesh::Mesh;
use bevy::reflect::TypePath;
use bevy::tasks::ConditionalSendFuture;

#[derive(Default, TypePath)]
pub struct ObjLoader;

impl AssetLoader for ObjLoader {
    type Error = ObjError;
    type Settings = ObjSettings;
    type Asset = Mesh;

    fn load(
        &self,
        reader: &mut dyn Reader,
        settings: &Self::Settings,
        _: &mut LoadContext,
    ) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>> {
        Box::pin(async move {
            let mut bytes = Vec::new();
            reader.read_to_end(&mut bytes).await?;
            load_obj_as_mesh(&bytes, settings)
        })
    }

    fn extensions(&self) -> &[&str] {
        crate::EXTENSIONS
    }
}

#[derive(thiserror::Error, Debug)]
pub enum ObjError {
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),
    #[error("Invalid OBJ file: {0}")]
    InvalidFile(#[from] wobj::WobjError),
}

pub fn load_obj_as_mesh(bytes: &[u8], settings: &ObjSettings) -> Result<Mesh, ObjError> {
    let obj = wobj::Obj::parse(bytes)?;

    let mut indicies = Vec::new();
    let mut positions = Vec::new();
    let mut normals = Vec::new();
    let mut uvs = Vec::new();

    for mesh in obj.meshes() {
        let trimesh = mesh.triangulate()?;
        let start_index = positions.len();
        indicies.extend(trimesh.0.0.into_iter().map(|i| i + start_index));
        positions.extend(trimesh.1.positions);
        if let Some(v) = trimesh.1.normals {
            normals.extend(v);
        }
        if let Some(v) = trimesh.1.uvs {
            uvs.extend(v);
        }
    }

    let len = positions.len();
    Ok(crate::util::to_bevy_mesh(
        wobj::Indicies(indicies),
        wobj::Vertices {
            positions,
            normals: if normals.len() == len {
                Some(normals)
            } else {
                None
            },
            uvs: if uvs.len() == len { Some(uvs) } else { None },
        },
        settings,
    ))
}