summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2025-11-06 19:08:01 +0100
committerHampusM <hampus@hampusmat.com>2025-11-06 19:08:01 +0100
commitc71a7048243148003f9eb09a03ab32dfcf249f9e (patch)
tree20d7d7854f353a1f02b65cb517e543e31c37afc1 /engine
parent3e19baab36762b4816301dab591405d3f1561287 (diff)
refactor(engine): make models import as multiple asserts
Diffstat (limited to 'engine')
-rw-r--r--engine/src/image.rs6
-rw-r--r--engine/src/lib.rs5
-rw-r--r--engine/src/material.rs4
-rw-r--r--engine/src/material/asset.rs90
-rw-r--r--engine/src/model.rs202
-rw-r--r--engine/src/model/asset.rs82
-rw-r--r--engine/src/renderer/opengl.rs133
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(
- &current_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(&current_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(
&current_graphics_context,
- &material,
+ model_material,
&material_flags,
&global_light,
shader_program,
@@ -846,7 +885,7 @@ fn render(
match create_bind_material_textures(
&current_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) => {