diff options
| author | HampusM <hampus@hampusmat.com> | 2025-11-06 19:08:01 +0100 |
|---|---|---|
| committer | HampusM <hampus@hampusmat.com> | 2025-11-06 19:08:01 +0100 |
| commit | c71a7048243148003f9eb09a03ab32dfcf249f9e (patch) | |
| tree | 20d7d7854f353a1f02b65cb517e543e31c37afc1 /engine | |
| parent | 3e19baab36762b4816301dab591405d3f1561287 (diff) | |
refactor(engine): make models import as multiple asserts
Diffstat (limited to 'engine')
| -rw-r--r-- | engine/src/image.rs | 6 | ||||
| -rw-r--r-- | engine/src/lib.rs | 5 | ||||
| -rw-r--r-- | engine/src/material.rs | 4 | ||||
| -rw-r--r-- | engine/src/material/asset.rs | 90 | ||||
| -rw-r--r-- | engine/src/model.rs | 202 | ||||
| -rw-r--r-- | engine/src/model/asset.rs | 82 | ||||
| -rw-r--r-- | engine/src/renderer/opengl.rs | 133 |
7 files changed, 357 insertions, 165 deletions
diff --git a/engine/src/image.rs b/engine/src/image.rs index 0e04412..9296167 100644 --- a/engine/src/image.rs +++ b/engine/src/image.rs @@ -5,9 +5,9 @@ use std::path::Path; use image_rs::GenericImageView as _; use crate::asset::{Assets, Submitter as AssetSubmitter}; +use crate::builder; use crate::color::Color; use crate::data_types::dimens::Dimens; -use crate::builder; #[derive(Debug)] pub struct Image @@ -36,7 +36,7 @@ impl Image } pub fn from_color(dimens: impl Into<Dimens<u32>>, color: impl Into<Color<u8>>) - -> Self + -> Self { let dimens: Dimens<u32> = dimens.into(); @@ -158,7 +158,7 @@ pub fn set_asset_importers(assets: &mut Assets) #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Failed to read image file")] + #[error("Failed to read file")] ReadFailed(#[source] std::io::Error), #[error("Failed to decode image")] diff --git a/engine/src/lib.rs b/engine/src/lib.rs index d5531c1..75bc921 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -12,7 +12,7 @@ use ecs::uid::Uid; use ecs::{SoleAlreadyExistsError, World}; use crate::asset::{Assets, Extension as AssetExtension}; -use crate::delta_time::{update as update_delta_time, DeltaTime, LastUpdate}; +use crate::delta_time::{DeltaTime, LastUpdate, update as update_delta_time}; mod opengl; mod util; @@ -73,7 +73,8 @@ impl Engine let mut assets = Assets::with_capacity(INITIAL_ASSET_CAPACITY); - crate::model::set_asset_importers(&mut assets); + crate::model::asset::add_importers(&mut assets); + crate::material::asset::add_importers(&mut assets); crate::image::set_asset_importers(&mut assets); world.add_extension(AssetExtension { assets }); diff --git a/engine/src/material.rs b/engine/src/material.rs index 56ff15f..2b9a8ca 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -1,8 +1,10 @@ use ecs::Component; +use crate::builder; use crate::color::Color; use crate::texture::Texture; -use crate::builder; + +pub mod asset; #[derive(Debug, Clone)] #[non_exhaustive] diff --git a/engine/src/material/asset.rs b/engine/src/material/asset.rs new file mode 100644 index 0000000..1f53dad --- /dev/null +++ b/engine/src/material/asset.rs @@ -0,0 +1,90 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::fs::read_to_string; +use std::path::{Path, PathBuf}; + +use crate::asset::{Assets, Handle as AssetHandle, Submitter as AssetSubmitter}; +use crate::material::Material; +use crate::texture::Texture; + +#[derive(Debug, Clone)] +pub struct Map +{ + pub assets: HashMap<Cow<'static, str>, AssetHandle<Material>>, +} + +/// Material asset import settings. +#[derive(Debug)] +#[non_exhaustive] +pub struct Settings {} + +pub fn add_importers(assets: &mut Assets) +{ + assets.set_importer(["mtl"], import_wavefront_mtl_asset); +} + +fn import_wavefront_mtl_asset( + asset_submitter: &mut AssetSubmitter<'_>, + path: &Path, + _settings: Option<&'_ Settings>, +) -> Result<(), Error> +{ + let named_materials = crate::file_format::wavefront::mtl::parse( + &read_to_string(path) + .map_err(|err| Error::ReadFailed(err, path.to_path_buf()))?, + )?; + + let mut mat_asset_map = Map { + assets: HashMap::with_capacity(named_materials.len()), + }; + + for material in named_materials { + let mut material_builder = Material::builder() + .ambient(material.ambient) + .diffuse(material.diffuse) + .specular(material.specular) + .shininess(material.shininess); + + if let Some(ambient_map) = 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) = 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) = material.specular_map { + material_builder = material_builder.specular_map(Texture::new( + asset_submitter.submit_load_other(specular_map.path.as_path()), + )); + } + + let material_name = material.name; + let material = material_builder.build(); + + let material_asset = + asset_submitter.submit_store_named(material_name.clone(), material); + + mat_asset_map + .assets + .insert(material_name.into(), material_asset); + } + + asset_submitter.submit_store(mat_asset_map); + + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +enum Error +{ + #[error("Failed to read file {}", .1.display())] + ReadFailed(#[source] std::io::Error, PathBuf), + + #[error(transparent)] + Other(#[from] crate::file_format::wavefront::mtl::Error), +} diff --git a/engine/src/model.rs b/engine/src/model.rs index 9f5840c..4c88f8f 100644 --- a/engine/src/model.rs +++ b/engine/src/model.rs @@ -1,176 +1,154 @@ 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::asset::Handle as AssetHandle; use crate::material::Material; +use crate::material::asset::Map as MaterialAssetMap; use crate::mesh::Mesh; -use crate::texture::Texture; + +pub mod asset; #[derive(Debug, Clone, Component)] #[non_exhaustive] pub struct Model { - pub asset_handle: AssetHandle<Data>, + pub spec_asset: AssetHandle<Spec>, } impl Model { - pub fn new(asset_handle: AssetHandle<Data>) -> Self + pub fn new(asset_handle: AssetHandle<Spec>) -> Self { - Self { asset_handle } + Self { spec_asset: asset_handle } } } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] #[non_exhaustive] -pub struct Data +pub struct Spec { - pub mesh: Mesh, - pub materials: HashMap<String, Material>, + pub mesh_asset: Option<AssetHandle<Mesh>>, + pub materials: Materials, + pub material_names: Vec<Cow<'static, str>>, } -impl Data +impl Spec { - pub fn builder() -> DataBuilder + pub fn builder() -> SpecBuilder { - DataBuilder::default() + SpecBuilder::default() } } #[derive(Debug, Default, Clone)] -pub struct DataBuilder +pub struct SpecBuilder { - mesh: Mesh, - materials: HashMap<String, Material>, + mesh_asset: Option<AssetHandle<Mesh>>, + materials: Materials, + material_names: Vec<Cow<'static, str>>, } -impl DataBuilder +impl SpecBuilder { - pub fn mesh(mut self, mesh: Mesh) -> Self + pub fn mesh(mut self, asset: AssetHandle<Mesh>) -> Self { - self.mesh = mesh; + self.mesh_asset = Some(asset); self } - pub fn material<'name>( - mut self, - name: impl Into<Cow<'name, str>>, - material: Material, - ) -> Self + pub fn materials(mut self, materials: Materials) -> Self { - self.materials.insert(name.into().into_owned(), material); + self.materials = materials; self } - pub fn build(self) -> Data + pub fn material_name(mut self, material_name: impl Into<Cow<'static, str>>) -> Self { - Data { - mesh: self.mesh, - materials: self.materials, - } + self.material_names.push(material_name.into()); + + self } -} -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct Settings {} + pub fn material_names<MaterialName>( + mut self, + material_names: impl IntoIterator<Item = MaterialName>, + ) -> Self + where + MaterialName: Into<Cow<'static, str>>, + { + self.material_names + .extend(material_names.into_iter().map(|mat_name| mat_name.into())); -#[derive(Debug, thiserror::Error)] -enum Error -{ - #[error("Failed to read model file")] - ReadModelFileFailed(#[source] std::io::Error), + self + } - #[error("Failed to read material file")] - ReadMaterialFileFailed(#[source] std::io::Error), + #[tracing::instrument(skip_all)] + pub fn build(self) -> Spec + { + if !self.materials.is_empty() && self.material_names.is_empty() { + tracing::warn!("Model spec will have materials but no material names"); + } - #[error("Failed to parse model file")] - ParsingFailed(#[from] ParsingError), -} + if self.materials.is_empty() && !self.material_names.is_empty() { + tracing::warn!("Model spec will have material names but no materials"); + } -pub fn set_asset_importers(assets: &mut Assets) -{ - assets.set_importer(["obj"], import_wavefront_obj_asset); + Spec { + mesh_asset: self.mesh_asset, + materials: self.materials, + material_names: self.material_names, + } + } } -#[derive(Debug, thiserror::Error)] -enum ParsingError +#[derive(Debug, Clone)] +pub enum Materials { - #[error(transparent)] - Obj(#[from] crate::file_format::wavefront::obj::Error), - - #[error(transparent)] - Mtl(#[from] crate::file_format::wavefront::mtl::Error), + Direct(HashMap<Cow<'static, str>, AssetHandle<Material>>), + Maps(Vec<AssetHandle<MaterialAssetMap>>), } -fn import_wavefront_obj_asset( - asset_submitter: &mut AssetSubmitter<'_>, - path: &Path, - _settings: Option<&'_ Settings>, -) -> Result<(), Error> +impl Materials { - 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)?); + pub fn direct<MaterialName>( + material_assets: impl IntoIterator<Item = (MaterialName, AssetHandle<Material>)>, + ) -> Self + where + MaterialName: Into<Cow<'static, str>>, + { + Self::Direct( + material_assets + .into_iter() + .map(|(material_name, mat_asset)| (material_name.into(), mat_asset)) + .collect(), + ) } - 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()), - )); + pub fn is_empty(&self) -> bool + { + match self { + Self::Direct(material_assets) => material_assets.is_empty(), + Self::Maps(material_asset_map_assets) => material_asset_map_assets.is_empty(), } + } - 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()), - )); + pub fn len(&self) -> usize + { + match self { + Self::Direct(material_assets) => material_assets.len(), + Self::Maps(material_asset_map_assets) => material_asset_map_assets.len(), } + } +} - (named_material.name, material_builder.build()) - })) +impl Default for Materials +{ + fn default() -> Self + { + Self::Maps(Vec::new()) + } } diff --git a/engine/src/model/asset.rs b/engine/src/model/asset.rs new file mode 100644 index 0000000..070200d --- /dev/null +++ b/engine/src/model/asset.rs @@ -0,0 +1,82 @@ +use std::collections::HashSet; +use std::fs::read_to_string; +use std::path::{Path, PathBuf}; + +use crate::asset::{Assets, Submitter as AssetSubmitter}; +use crate::material::asset::Map as MaterialAssetMap; +use crate::model::{Materials, Spec}; + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Settings {} + +pub fn add_importers(assets: &mut Assets) +{ + assets.set_importer(["obj"], import_wavefront_obj_asset); +} + +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(|err| Error::ReadFailed(err, path.to_path_buf()))?, + )?; + + let mesh = obj.to_mesh()?; + + let mesh_asset = asset_submitter.submit_store_named("mesh", mesh); + + let mut material_asset_map_assets = + Vec::with_capacity(obj.mtl_libs.iter().flatten().count()); + + for mtl_lib_path in obj.mtl_libs.iter().flatten() { + let mtl_lib_asset = + asset_submitter.submit_load_other::<MaterialAssetMap>(mtl_lib_path.as_path()); + + material_asset_map_assets.push(mtl_lib_asset); + } + + let material_names = obj + .faces + .into_iter() + .map(|face| face.material_name) + .flatten() + .fold( + (HashSet::<String>::new(), Vec::<String>::new()), + |(mut pushed_mat_names, mut unique_mat_names), material_name| { + if pushed_mat_names.contains(&material_name) { + return (pushed_mat_names, unique_mat_names); + } + + unique_mat_names.push(material_name.clone()); + pushed_mat_names.insert(material_name); + + (pushed_mat_names, unique_mat_names) + }, + ) + .1; + + asset_submitter.submit_store( + Spec::builder() + .mesh(mesh_asset) + .materials(Materials::Maps(material_asset_map_assets)) + .material_names(material_names) + .build(), + ); + + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +enum Error +{ + #[error("Failed to read file {}", .1.display())] + ReadFailed(#[source] std::io::Error, PathBuf), + + #[error(transparent)] + Other(#[from] crate::file_format::wavefront::obj::Error), +} diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs index fb7dfbe..8f10215 100644 --- a/engine/src/renderer/opengl.rs +++ b/engine/src/renderer/opengl.rs @@ -15,26 +15,26 @@ use ecs::phase::Phase; use ecs::query::term::Without; use ecs::sole::Single; use ecs::system::observer::Observe; -use ecs::{declare_entity, Component, Query}; +use ecs::{Component, Query, declare_entity}; use glutin::display::GetGlDisplay; use glutin::prelude::GlDisplay; use glutin::surface::GlSurface; use opengl_bindings::debug::{ - set_debug_message_callback, - set_debug_message_control, MessageIdsAction, MessageSeverity, MessageSource, MessageType, SetDebugMessageControlError as GlSetDebugMessageControlError, + set_debug_message_callback, + set_debug_message_control, }; use opengl_bindings::misc::{ - clear_buffers, - enable, - set_enabled, BufferClearMask, Capability, SetViewportError as GlSetViewportError, + clear_buffers, + enable, + set_enabled, }; use opengl_bindings::shader::{ Error as GlShaderError, @@ -66,10 +66,10 @@ use crate::image::{ColorType as ImageColorType, Image}; use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; use crate::material::{Flags as MaterialFlags, Material}; use crate::matrix::Matrix; -use crate::model::Model; +use crate::model::{Materials as ModelMaterials, Model, Spec as ModelSpec}; use crate::opengl::glsl::{ - preprocess as glsl_preprocess, PreprocessingError as GlslPreprocessingError, + preprocess as glsl_preprocess, }; use crate::projection::{ClipVolume, Projection}; use crate::renderer::opengl::glutin_compat::{ @@ -86,13 +86,13 @@ use crate::texture::{ use crate::transform::{Scale, WorldPosition}; use crate::util::MapVec; use crate::vector::{Vec2, Vec3}; +use crate::windowing::Context as WindowingContext; use crate::windowing::window::{ Closed as WindowClosed, CreationAttributes as WindowCreationAttributes, CreationReady, Window, }; -use crate::windowing::Context as WindowingContext; mod glutin_compat; mod graphics_mesh; @@ -648,6 +648,43 @@ fn init_window_graphics( } } +enum MaterialSearchResult<'a> +{ + Found(&'a Material), + NotFound, + NoMaterials, +} + +fn find_first_model_material<'assets>( + model_spec: &'assets ModelSpec, + assets: &'assets Assets, +) -> MaterialSearchResult<'assets> +{ + let Some(material_name) = model_spec.material_names.first() else { + return MaterialSearchResult::NoMaterials; + }; + + let Some(material_asset) = (match &model_spec.materials { + ModelMaterials::Maps(material_asset_map_assets) => material_asset_map_assets + .iter() + .find_map(|mat_asset_map_asset| { + let mat_asset_map = assets.get(mat_asset_map_asset)?; + + mat_asset_map.assets.get(material_name) + }), + ModelMaterials::Direct(material_assets) => material_assets.get(material_name), + }) else { + return MaterialSearchResult::NotFound; + }; + + let Some(material) = assets.get(material_asset) else { + tracing::trace!("Missing material asset"); + return MaterialSearchResult::NotFound; + }; + + MaterialSearchResult::Found(material) +} + #[tracing::instrument(skip_all)] #[allow(clippy::too_many_arguments)] fn render( @@ -730,11 +767,31 @@ fn render( ), ) in query.iter_with_euids() { - let Some(model_data) = assets.get(&model.asset_handle) else { - tracing::trace!("Missing model asset"); + let Some(model_spec) = assets.get(&model.spec_asset) else { + tracing::trace!("Missing model spec asset"); + continue; + }; + + let Some(mesh_asset) = &model_spec.mesh_asset else { + tracing::debug!("Model spec mesh asset is None"); continue; }; + let Some(model_mesh) = assets.get(&mesh_asset) else { + tracing::trace!("Missing mesh asset"); + continue; + }; + + debug_assert!(model_spec.material_names.len() <= 1); + + let model_material = match find_first_model_material(model_spec, &assets) { + MaterialSearchResult::Found(model_material) => model_material, + MaterialSearchResult::NotFound => { + continue; + } + MaterialSearchResult::NoMaterials => &Material::default(), + }; + let material_flags = material_flags .map(|material_flags| material_flags.clone()) .unwrap_or_default(); @@ -762,23 +819,21 @@ fn render( data_in_graphics_ctx.graphics_mesh_id } None => { - let graphics_mesh = match GraphicsMesh::new( - ¤t_graphics_context, - &model_data.mesh, - ) { - Ok(graphics_mesh) => graphics_mesh, - Err(err) => { - tracing::error!( - "Failed to create {}: {err}", - type_name::<GraphicsMesh>() - ); - - // This system should not try again - actions.add_components(euid, (NoDraw,)); - - continue; - } - }; + let graphics_mesh = + match GraphicsMesh::new(¤t_graphics_context, model_mesh) { + Ok(graphics_mesh) => graphics_mesh, + Err(err) => { + tracing::error!( + "Failed to create {}: {err}", + type_name::<GraphicsMesh>() + ); + + // This system should not try again + actions.add_components(euid, (NoDraw,)); + + continue; + } + }; let graphics_mesh_id = graphics_mesh_store.insert(graphics_mesh); @@ -813,25 +868,9 @@ fn render( window.inner_size(), ); - if model_data.materials.len() > 1 { - tracing::warn!(concat!( - "Multiple model materials are not supported ", - "so only the first material will be used" - )); - } - - let material = match model_data.materials.values().next() { - Some(material) => material, - None => { - tracing::warn!("Model has no materials. Using default material"); - - &Material::default() - } - }; - apply_light( ¤t_graphics_context, - &material, + model_material, &material_flags, &global_light, shader_program, @@ -846,7 +885,7 @@ fn render( match create_bind_material_textures( ¤t_graphics_context, - &material, + model_material, &assets, textures_objs, default_1x1_texture_obj, @@ -1466,7 +1505,7 @@ fn opengl_debug_message_cb( { use std::backtrace::{Backtrace, BacktraceStatus}; - use tracing::{event, Level}; + use tracing::{Level, event}; macro_rules! create_event { ($level: expr) => { |
