diff options
Diffstat (limited to 'engine/src/model.rs')
-rw-r--r-- | engine/src/model.rs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/engine/src/model.rs b/engine/src/model.rs new file mode 100644 index 0000000..9f5840c --- /dev/null +++ b/engine/src/model.rs @@ -0,0 +1,176 @@ +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<Data>, +} + +impl Model +{ + pub fn new(asset_handle: AssetHandle<Data>) -> Self + { + Self { asset_handle } + } +} + +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct Data +{ + pub mesh: Mesh, + pub materials: HashMap<String, Material>, +} + +impl Data +{ + pub fn builder() -> DataBuilder + { + DataBuilder::default() + } +} + +#[derive(Debug, Default, Clone)] +pub struct DataBuilder +{ + mesh: Mesh, + materials: HashMap<String, Material>, +} + +impl DataBuilder +{ + pub fn mesh(mut self, mesh: Mesh) -> Self + { + self.mesh = mesh; + + self + } + + pub fn material<'name>( + mut self, + name: impl Into<Cow<'name, str>>, + 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::<String, Material>::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<impl Iterator<Item = (String, Material)> + '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()) + })) +} |