diff --git a/Cargo.lock b/Cargo.lock index 48a1c021b..65d906345 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2329,6 +2329,7 @@ version = "0.2.2" dependencies = [ "glam", "strafesnet_common", + "strafesnet_deferred_loader", "vbsp", "vmdl", ] diff --git a/lib/bsp_loader/Cargo.toml b/lib/bsp_loader/Cargo.toml index a3ef41f9d..1c01e4169 100644 --- a/lib/bsp_loader/Cargo.toml +++ b/lib/bsp_loader/Cargo.toml @@ -12,5 +12,6 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"] [dependencies] glam = "0.29.0" strafesnet_common = { path = "../common", registry = "strafesnet" } +strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader" } vbsp = "0.6.0" vmdl = "0.2.0" diff --git a/lib/bsp_loader/src/bsp.rs b/lib/bsp_loader/src/bsp.rs index 903c611ac..12bd173dd 100644 --- a/lib/bsp_loader/src/bsp.rs +++ b/lib/bsp_loader/src/bsp.rs @@ -1,18 +1,18 @@ -use strafesnet_common::{map,model,integer,gameplay_attributes}; +use std::borrow::Cow; -const VALVE_SCALE:f32=1.0/16.0; -fn valve_transform([x,y,z]:[f32;3])->integer::Planar64Vec3{ - integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap() -} -pub fn convert_bsp<AcquireRenderConfigId,AcquireMeshId>( - bsp:&vbsp::Bsp, - mut acquire_render_config_id:AcquireRenderConfigId, - mut acquire_mesh_id:AcquireMeshId -)->PartialMap1 -where - AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId, - AcquireMeshId:FnMut(&str)->model::MeshId, -{ +use strafesnet_common::{map,model,integer,gameplay_attributes}; +use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader}; +use strafesnet_deferred_loader::mesh::Meshes; +use strafesnet_deferred_loader::texture::{RenderConfigs,Texture}; + +use crate::valve_transform; + +pub fn convert<'a>( + bsp:&'a crate::Bsp, + render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>, + mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>, +)->PartialMap1{ + let bsp=bsp.as_ref(); //figure out real attributes later let mut unique_attributes=Vec::new(); unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration); @@ -22,7 +22,7 @@ where //declare all prop models to Loader let prop_models=bsp.static_props().map(|prop|{ //get or create mesh_id - let mesh_id=acquire_mesh_id(prop.model()); + let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model()); //not the most failsafe code but this is just for the map tool lmao if prop_mesh_count==mesh_id.get(){ prop_mesh_count+=1; @@ -65,7 +65,7 @@ where //this automatically figures out what the texture is trying to do and creates //a render config for it, and then returns the id to that render config - let render_id=acquire_render_config_id(Some(face_texture_data.name())); + let render_id=render_config_deferred_loader.acquire_render_config_id(Some(Cow::Borrowed(face_texture_data.name()))); //normal let normal=face.normal(); @@ -176,26 +176,13 @@ pub struct PartialMap1{ modes:strafesnet_common::gameplay_modes::Modes, } impl PartialMap1{ - pub fn add_prop_meshes<AcquireRenderConfigId>( + pub fn add_prop_meshes<'a>( self, - prop_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::ModelData)>, - mut acquire_render_config_id:AcquireRenderConfigId, - )->PartialMap2 - where - AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId, - { + prop_meshes:Meshes, + )->PartialMap2{ PartialMap2{ attributes:self.attributes, - prop_meshes:prop_meshes.into_iter().filter_map(|(mesh_id,model_data)| - //this will generate new render ids and texture ids - match convert_mesh(model_data,&mut acquire_render_config_id){ - Ok(mesh)=>Some((mesh_id,mesh)), - Err(e)=>{ - println!("error converting mesh: {e}"); - None - } - } - ).collect(), + prop_meshes:prop_meshes.consume().collect(), prop_models:self.prop_models, world_meshes:self.world_meshes, world_models:self.world_models, @@ -214,8 +201,7 @@ pub struct PartialMap2{ impl PartialMap2{ pub fn add_render_configs_and_textures( mut self, - render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>, - textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>, + render_configs:RenderConfigs, )->map::CompleteMap{ //merge mesh and model lists, flatten and remap all ids let mesh_id_offset=self.world_meshes.len(); @@ -234,10 +220,11 @@ impl PartialMap2{ }) )); //let mut models=Vec::new(); + let (textures,render_configs)=render_configs.consume(); let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>) =textures.into_iter() //.filter_map(f) cull unused textures - .enumerate().map(|(new_texture_id,(old_texture_id,texture))|{ + .enumerate().map(|(new_texture_id,(old_texture_id,Texture::ImageDDS(texture)))|{ (texture,(old_texture_id,model::TextureId::new(new_texture_id as u32))) }).unzip(); let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{ @@ -257,77 +244,3 @@ impl PartialMap2{ } } } - -fn convert_mesh<AcquireRenderConfigId>( - model_data:crate::data::ModelData, - acquire_render_config_id:&mut AcquireRenderConfigId, -)->Result<model::Mesh,vmdl::ModelError> -where - AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId, -{ - let model=model_data.read_model()?; - let texture_paths=model.texture_directories(); - if texture_paths.len()!=1{ - println!("WARNING: multiple texture paths"); - } - let skin=model.skin_tables().nth(0).unwrap(); - - let mut spam_pos=Vec::with_capacity(model.vertices().len()); - let mut spam_normal=Vec::with_capacity(model.vertices().len()); - let mut spam_tex=Vec::with_capacity(model.vertices().len()); - let mut spam_vertices=Vec::with_capacity(model.vertices().len()); - for (i,vertex) in model.vertices().iter().enumerate(){ - spam_pos.push(valve_transform(vertex.position.into())); - spam_normal.push(valve_transform(vertex.normal.into())); - spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates)); - spam_vertices.push(model::IndexedVertex{ - pos:model::PositionId::new(i as u32), - tex:model::TextureCoordinateId::new(i as u32), - normal:model::NormalId::new(i as u32), - color:model::ColorId::new(0), - }); - } - let mut graphics_groups=Vec::new(); - let mut physics_groups=Vec::new(); - let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{ - let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32); - - let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){ - let mut path=std::path::PathBuf::from(texture_path.as_str()); - path.push(texture_name); - acquire_render_config_id(path.as_os_str().to_str()) - }else{ - acquire_render_config_id(None) - }; - - graphics_groups.push(model::IndexedGraphicsGroup{ - render:render_id, - groups:vec![polygon_group_id], - }); - physics_groups.push(model::IndexedPhysicsGroup{ - groups:vec![polygon_group_id], - }); - model::PolygonGroup::PolygonList(model::PolygonList::new( - //looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function - mesh.vertex_strip_indices().flat_map(|mut strip| - std::iter::from_fn(move||{ - match (strip.next(),strip.next(),strip.next()){ - (Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3].map(|vertex_id|model::VertexId::new(vertex_id as u32)).to_vec()), - //ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate - _=>None, - } - }) - ).collect() - )) - }).collect(); - Ok(model::Mesh{ - unique_pos:spam_pos, - unique_normal:spam_normal, - unique_tex:spam_tex, - unique_color:vec![glam::Vec4::ONE], - unique_vertices:spam_vertices, - polygon_groups, - graphics_groups, - physics_groups, - }) -} diff --git a/lib/bsp_loader/src/data.rs b/lib/bsp_loader/src/data.rs deleted file mode 100644 index 83ca6782e..000000000 --- a/lib/bsp_loader/src/data.rs +++ /dev/null @@ -1,60 +0,0 @@ -pub struct Bsp(vbsp::Bsp); -impl Bsp{ - pub const fn new(value:vbsp::Bsp)->Self{ - Self(value) - } -} -impl AsRef<vbsp::Bsp> for Bsp{ - fn as_ref(&self)->&vbsp::Bsp{ - &self.0 - } -} - -pub struct MdlData(Vec<u8>); -impl MdlData{ - pub const fn new(value:Vec<u8>)->Self{ - Self(value) - } -} -impl AsRef<[u8]> for MdlData{ - fn as_ref(&self)->&[u8]{ - self.0.as_ref() - } -} -pub struct VtxData(Vec<u8>); -impl VtxData{ - pub const fn new(value:Vec<u8>)->Self{ - Self(value) - } -} -impl AsRef<[u8]> for VtxData{ - fn as_ref(&self)->&[u8]{ - self.0.as_ref() - } -} -pub struct VvdData(Vec<u8>); -impl VvdData{ - pub const fn new(value:Vec<u8>)->Self{ - Self(value) - } -} -impl AsRef<[u8]> for VvdData{ - fn as_ref(&self)->&[u8]{ - self.0.as_ref() - } -} - -pub struct ModelData{ - pub mdl:MdlData, - pub vtx:VtxData, - pub vvd:VvdData, -} -impl ModelData{ - pub fn read_model(&self)->Result<vmdl::Model,vmdl::ModelError>{ - Ok(vmdl::Model::from_parts( - vmdl::mdl::Mdl::read(self.mdl.as_ref())?, - vmdl::vtx::Vtx::read(self.vtx.as_ref())?, - vmdl::vvd::Vvd::read(self.vvd.as_ref())?, - )) - } -} \ No newline at end of file diff --git a/lib/bsp_loader/src/lib.rs b/lib/bsp_loader/src/lib.rs index e8d4d91d1..21d16f3d8 100644 --- a/lib/bsp_loader/src/lib.rs +++ b/lib/bsp_loader/src/lib.rs @@ -1,7 +1,23 @@ mod bsp; -pub mod data; +mod mesh; +pub mod loader; -pub use data::Bsp; +pub struct Bsp(vbsp::Bsp); +impl Bsp{ + pub const fn new(value:vbsp::Bsp)->Self{ + Self(value) + } +} +impl AsRef<vbsp::Bsp> for Bsp{ + fn as_ref(&self)->&vbsp::Bsp{ + &self.0 + } +} + +const VALVE_SCALE:f32=1.0/16.0; +pub(crate) fn valve_transform([x,y,z]:[f32;3])->strafesnet_common::integer::Planar64Vec3{ + strafesnet_common::integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap() +} #[derive(Debug)] pub enum ReadError{ @@ -24,14 +40,4 @@ pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{ vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp) } -pub fn convert<AcquireRenderConfigId,AcquireMeshId>( - bsp:&Bsp, - acquire_render_config_id:AcquireRenderConfigId, - acquire_mesh_id:AcquireMeshId -)->bsp::PartialMap1 -where - AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId, - AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId, -{ - bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id) -} \ No newline at end of file +pub use bsp::convert; diff --git a/lib/bsp_loader/src/loader.rs b/lib/bsp_loader/src/loader.rs new file mode 100644 index 000000000..6f69fe46f --- /dev/null +++ b/lib/bsp_loader/src/loader.rs @@ -0,0 +1,112 @@ +use std::{borrow::Cow, io::Read}; + +use strafesnet_common::model::Mesh; +use strafesnet_deferred_loader::{loader::Loader,texture::Texture}; + +use crate::{mesh::ModelData, Bsp}; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum TextureError{ + Io(std::io::Error), +} +impl std::fmt::Display for TextureError{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for TextureError{} +impl From<std::io::Error> for TextureError{ + fn from(value:std::io::Error)->Self{ + Self::Io(value) + } +} + +pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>); +impl TextureLoader<'_>{ + pub fn new()->Self{ + Self(std::marker::PhantomData) + } +} +impl<'a> Loader for TextureLoader<'a>{ + type Error=TextureError; + type Index=Cow<'a,str>; + type Resource=Texture; + fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{ + let file_name=format!("textures/{}.dds",index); + let mut file=std::fs::File::open(file_name)?; + let mut data=Vec::new(); + file.read_to_end(&mut data)?; + Ok(Texture::ImageDDS(data)) + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub enum MeshError{ + Io(std::io::Error), + VMDL(vmdl::ModelError), + VBSP(vbsp::BspError), + MissingMdl, + MissingVtx, + MissingVvd, +} +impl std::fmt::Display for MeshError{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for MeshError{} +impl From<std::io::Error> for MeshError{ + fn from(value:std::io::Error)->Self{ + Self::Io(value) + } +} +impl From<vmdl::ModelError> for MeshError{ + fn from(value:vmdl::ModelError)->Self{ + Self::VMDL(value) + } +} +impl From<vbsp::BspError> for MeshError{ + fn from(value:vbsp::BspError)->Self{ + Self::VBSP(value) + } +} + +pub struct MeshLoader<'a,'b>{ + bsp:&'a Bsp, + deferred_loader:&'b mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>, +} +impl MeshLoader<'_,'_>{ + pub fn new<'a,'b>( + bsp:&'a Bsp, + deferred_loader:&'b mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>, + )->MeshLoader<'a,'b>{ + MeshLoader{ + bsp, + deferred_loader, + } + } +} +impl<'a> Loader for MeshLoader<'a,'_>{ + type Error=MeshError; + type Index=&'a str; + type Resource=Mesh; + fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{ + let mdl_path_lower=index.to_lowercase(); + //.mdl, .vvd, .dx90.vtx + let path=std::path::PathBuf::from(mdl_path_lower.as_str()); + let mut vvd_path=path.clone(); + let mut vtx_path=path; + vvd_path.set_extension("vvd"); + vtx_path.set_extension("dx90.vtx"); + // TODO: search more packs, possibly using an index of multiple packs + let bsp=self.bsp.as_ref(); + let mdl=bsp.pack.get(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl)?; + let vtx=bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVtx)?; + let vvd=bsp.pack.get(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?; + let model=ModelData{mdl,vtx,vvd}; + let mesh=model.convert_mesh(&mut self.deferred_loader)?; + Ok(mesh) + } +} diff --git a/lib/bsp_loader/src/mesh.rs b/lib/bsp_loader/src/mesh.rs new file mode 100644 index 000000000..d6a3339b4 --- /dev/null +++ b/lib/bsp_loader/src/mesh.rs @@ -0,0 +1,89 @@ +use std::borrow::Cow; + +use strafesnet_common::model; +use strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader; + +use crate::valve_transform; + +pub struct ModelData{ + pub mdl:Vec<u8>, + pub vtx:Vec<u8>, + pub vvd:Vec<u8>, +} +impl ModelData{ + fn read_model(&self)->Result<vmdl::Model,vmdl::ModelError>{ + Ok(vmdl::Model::from_parts( + vmdl::mdl::Mdl::read(self.mdl.as_ref())?, + vmdl::vtx::Vtx::read(self.vtx.as_ref())?, + vmdl::vvd::Vvd::read(self.vvd.as_ref())?, + )) + } + pub fn convert_mesh<'a>(self,deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>)->Result<model::Mesh,vmdl::ModelError>{ + let model=self.read_model()?; + let texture_paths=model.texture_directories(); + if texture_paths.len()!=1{ + println!("WARNING: multiple texture paths"); + } + let skin=model.skin_tables().nth(0).unwrap(); + + let mut spam_pos=Vec::with_capacity(model.vertices().len()); + let mut spam_normal=Vec::with_capacity(model.vertices().len()); + let mut spam_tex=Vec::with_capacity(model.vertices().len()); + let mut spam_vertices=Vec::with_capacity(model.vertices().len()); + for (i,vertex) in model.vertices().iter().enumerate(){ + spam_pos.push(valve_transform(vertex.position.into())); + spam_normal.push(valve_transform(vertex.normal.into())); + spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates)); + spam_vertices.push(model::IndexedVertex{ + pos:model::PositionId::new(i as u32), + tex:model::TextureCoordinateId::new(i as u32), + normal:model::NormalId::new(i as u32), + color:model::ColorId::new(0), + }); + } + let mut graphics_groups=Vec::new(); + let mut physics_groups=Vec::new(); + let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{ + let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32); + + let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){ + let mut path=std::path::PathBuf::from(texture_path.as_str()); + path.push(texture_name); + let index=path.as_os_str().to_str().map(|s|Cow::Owned(s.to_owned())); + deferred_loader.acquire_render_config_id(index) + }else{ + deferred_loader.acquire_render_config_id(None) + }; + + graphics_groups.push(model::IndexedGraphicsGroup{ + render:render_id, + groups:vec![polygon_group_id], + }); + physics_groups.push(model::IndexedPhysicsGroup{ + groups:vec![polygon_group_id], + }); + model::PolygonGroup::PolygonList(model::PolygonList::new( + //looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function + mesh.vertex_strip_indices().flat_map(|mut strip| + std::iter::from_fn(move||{ + match (strip.next(),strip.next(),strip.next()){ + (Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3].map(|vertex_id|model::VertexId::new(vertex_id as u32)).to_vec()), + //ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate + _=>None, + } + }) + ).collect() + )) + }).collect(); + Ok(model::Mesh{ + unique_pos:spam_pos, + unique_normal:spam_normal, + unique_tex:spam_tex, + unique_color:vec![glam::Vec4::ONE], + unique_vertices:spam_vertices, + polygon_groups, + graphics_groups, + physics_groups, + }) + } +}