use std::borrow::Cow; use std::collections::HashMap; use std::fs::read_to_string; use std::path::Path; use ecs::Component; use crate::asset::{Assets, Handle as AssetHandle, Submitter as AssetSubmitter}; use crate::material::Material; use crate::mesh::Mesh; use crate::texture::Texture; #[derive(Debug, Clone, Component)] #[non_exhaustive] pub struct Model { pub asset_handle: AssetHandle, } impl Model { pub fn new(asset_handle: AssetHandle) -> Self { Self { asset_handle } } } #[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct Data { pub mesh: Mesh, pub materials: HashMap, } impl Data { pub fn builder() -> DataBuilder { DataBuilder::default() } } #[derive(Debug, Default, Clone)] pub struct DataBuilder { mesh: Mesh, materials: HashMap, } impl DataBuilder { pub fn mesh(mut self, mesh: Mesh) -> Self { self.mesh = mesh; self } pub fn material<'name>( mut self, name: impl Into>, material: Material, ) -> Self { self.materials.insert(name.into().into_owned(), material); self } pub fn build(self) -> Data { Data { mesh: self.mesh, materials: self.materials, } } } #[derive(Debug, Clone)] #[non_exhaustive] pub struct Settings {} #[derive(Debug, thiserror::Error)] enum Error { #[error("Failed to read model file")] ReadModelFileFailed(#[source] std::io::Error), #[error("Failed to read material file")] ReadMaterialFileFailed(#[source] std::io::Error), #[error("Failed to parse model file")] ParsingFailed(#[from] ParsingError), } pub fn set_asset_importers(assets: &mut Assets) { assets.set_importer(["obj"], import_wavefront_obj_asset); } #[derive(Debug, thiserror::Error)] enum ParsingError { #[error(transparent)] Obj(#[from] crate::file_format::wavefront::obj::Error), #[error(transparent)] Mtl(#[from] crate::file_format::wavefront::mtl::Error), } fn import_wavefront_obj_asset( asset_submitter: &mut AssetSubmitter<'_>, path: &Path, _settings: Option<&'_ Settings>, ) -> Result<(), Error> { let obj = crate::file_format::wavefront::obj::parse( &read_to_string(path).map_err(Error::ReadModelFileFailed)?, ) .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?; let mesh = obj .to_mesh() .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?; let mut materials = HashMap::::with_capacity(obj.mtl_libs.iter().flatten().count()); for mtl_lib_path in obj.mtl_libs.iter().flatten() { materials.extend(import_mtl(asset_submitter, &mtl_lib_path)?); } asset_submitter.submit_store(Data { mesh, materials }); Ok(()) } fn import_mtl<'a>( asset_submitter: &'a AssetSubmitter<'_>, path: &Path, ) -> Result + 'a, Error> { let named_materials = crate::file_format::wavefront::mtl::parse( &read_to_string(path).map_err(Error::ReadMaterialFileFailed)?, ) .map_err(|err| Error::ParsingFailed(ParsingError::Mtl(err)))?; Ok(named_materials.into_iter().map(|named_material| { let mut material_builder = Material::builder() .ambient(named_material.ambient) .diffuse(named_material.diffuse) .specular(named_material.specular) .shininess(named_material.shininess); if let Some(ambient_map) = named_material.ambient_map { material_builder = material_builder.ambient_map(Texture::new( asset_submitter.submit_load_other(ambient_map.path.as_path()), )); } if let Some(diffuse_map) = named_material.diffuse_map { material_builder = material_builder.diffuse_map(Texture::new( asset_submitter.submit_load_other(diffuse_map.path.as_path()), )); } if let Some(specular_map) = named_material.specular_map { material_builder = material_builder.specular_map(Texture::new( asset_submitter.submit_load_other(specular_map.path.as_path()), )); } (named_material.name, material_builder.build()) })) }