diff --git a/Cargo.lock b/Cargo.lock index 26020d0..279d009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,7 +354,7 @@ dependencies = [ [[package]] name = "strafesnet_common" version = "0.1.0" -source = "git+https://git.itzana.me/StrafesNET/common?rev=47cdea0c8a5d10a2440ca6270a975d560aa3642d#47cdea0c8a5d10a2440ca6270a975d560aa3642d" +source = "git+https://git.itzana.me/StrafesNET/common?rev=093a54c527134ef7020a22a0f5778df8cba60228#093a54c527134ef7020a22a0f5778df8cba60228" dependencies = [ "glam", "id", diff --git a/Cargo.toml b/Cargo.toml index 127ff5e..f25a566 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" [dependencies] glam = "0.25.0" -strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "47cdea0c8a5d10a2440ca6270a975d560aa3642d" } +strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "093a54c527134ef7020a22a0f5778df8cba60228" } vbsp = "0.5.0" vmdl = "0.1.1" diff --git a/src/bsp.rs b/src/bsp.rs index 7111c74..b657217 100644 --- a/src/bsp.rs +++ b/src/bsp.rs @@ -4,31 +4,55 @@ const VALVE_SCALE:f32=1.0/16.0; fn valve_transform(v:[f32;3])->integer::Planar64Vec3{ integer::Planar64Vec3::try_from([v[0]*VALVE_SCALE,v[2]*VALVE_SCALE,-v[1]*VALVE_SCALE]).unwrap() } -pub fn convert( +pub fn convert_bsp( bsp:&vbsp::Bsp, - mut acquire_texture_id:AcquireTextureId, + acquire_render_config_id:&mut AcquireRenderConfigId, mut acquire_mesh_id:AcquireMeshId -)->strafesnet_common::map::CompleteMap +)->PartialMap1 where - AcquireTextureId:FnMut(&str)->model::TextureId, + AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId, AcquireMeshId:FnMut(&str)->model::MeshId, { - let mut unique_render_configs=Vec::new(); - + //figure out real attributes later let mut unique_attributes=Vec::new(); unique_attributes.push(gameplay_attributes::CollisionAttributes::contact_default()); const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0); + let mut prop_mesh_count=0; //declare all prop models to Loader - for prop in bsp.static_props(){ - acquire_mesh_id(prop.model()); - } + let prop_models=bsp.static_props().map(|prop|{ + //get or create mesh_id + let mesh_id=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; + }; + let placement=prop.as_prop_placement(); + model::Model{ + mesh:mesh_id, + attributes:TEMP_TOUCH_ME_ATTRIBUTE, + transform:integer::Planar64Affine3::new( + integer::Planar64Mat3::try_from( + glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale)) + //TODO: figure this out + *glam::Mat3A::from_quat(glam::Quat::from_xyzw( + placement.rotation.v.x,//b + placement.rotation.v.y,//c + placement.rotation.v.z,//d + placement.rotation.s,//a + )) + ).unwrap(), + valve_transform(<[f32;3]>::from(placement.origin)), + ), + color:glam::Vec4::ONE, + } + }).collect(); //TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups //the generated MeshIds in here will collide with the Loader Mesh Ids //but I can't think of a good workaround other than just remapping one later. - let (meshes,models):(Vec,Vec)=bsp.models().enumerate().map(|(mesh_id,world_model)|{ + let (world_meshes,world_models):(Vec,Vec)=bsp.models().enumerate().map(|(mesh_id,world_model)|{ let mesh_id=model::MeshId::new(mesh_id as u32); //non-deduplicated let mut spam_pos=Vec::new(); @@ -45,14 +69,9 @@ where let texture_offset=glam::vec2(face_texture.texture_transforms_u[3],face_texture.texture_transforms_v[3]); let texture_size=glam::vec2(face_texture_data.width as f32,face_texture_data.height as f32); - //texture - let texture_id=acquire_texture_id(face_texture_data.name()); //this is equivalent to a get_or_create pattern because there is a singular no-texture RenderId - //so RenderId==TextureId - //not the most failsafe code but this is just for the map tool lmao - if unique_render_configs.len()==texture_id.get() as usize{ - unique_render_configs.push(model::RenderConfig::texture(texture_id)); - }; - let render_id=model::RenderConfigId::new(texture_id.get()); + //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())); //normal let normal=face.normal(); @@ -111,133 +130,176 @@ where ) }).unzip(); - map::CompleteMap{ + PartialMap1{ attributes:unique_attributes, - meshes, - render_configs:unique_render_configs, - models, + world_meshes, + prop_models, + world_models, modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()), } } -/* - //call self.acquire_texture_id for each texture in the mesh - //generate unique meshes - let mut model_map=std::collections::HashMap::with_capacity(model_dedupe.len()); - let mut prop_models=Vec::new(); - for model_name in model_dedupe{ - let model_name_lower=model_name.to_lowercase(); - //.mdl, .vvd, .dx90.vtx - let mut path=std::path::PathBuf::from(model_name_lower.as_str()); - let file_name=std::path::PathBuf::from(path.file_stem().unwrap()); - path.pop(); - path.push(file_name); - let mut vvd_path=path.clone(); - let mut vtx_path=path.clone(); - vvd_path.set_extension("vvd"); - vtx_path.set_extension("dx90.vtx"); - match (bsp.pack.get(model_name_lower.as_str()),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){ - (Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{ - match (vmdl::mdl::Mdl::read(mdl_file.as_ref()),vmdl::vvd::Vvd::read(vvd_file.as_ref()),vmdl::vtx::Vtx::read(vtx_file.as_ref())){ - (Ok(mdl),Ok(vvd),Ok(vtx))=>{ - let model=vmdl::Model::from_parts(mdl,vtx,vvd); - 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(<[f32;3]>::from(vertex.position))); - spam_normal.push(valve_transform(<[f32;3]>::from(vertex.normal))); - 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 model_id=prop_models.len(); - model_map.insert(model_name,model_id); - prop_models.push(model::Mesh{ - unique_pos:spam_pos, - unique_normal:spam_normal, - unique_tex:spam_tex, - unique_color:vec![glam::Vec4::ONE], - unique_vertices:spam_vertices, - groups:model.meshes().map(|mesh|{ - let texture=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 texture_location=path.as_os_str().to_str().unwrap(); - let texture_id=if let Some(&texture_id)=texture_id_from_name.get(texture_location){ - texture_id - }else{ - println!("texture! {}",texture_location); - let texture_id=name_from_texture_id.len() as u32; - texture_id_from_name.insert(texture_location.to_string(),texture_id); - name_from_texture_id.push(texture_location.to_string()); - texture_id - }; - Some(texture_id) - }else{ - None - }; - - model::IndexedGroup{ - texture, - polys:{ - //looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function - mesh.vertex_strip_indices().map(|strip|{ - strip.collect::>().chunks(3).map(|tri|{ - model::IndexedPolygon{vertices:vec![tri[0] as u32,tri[1] as u32,tri[2] as u32]} - }).collect::>() - }).flatten().collect() - }, - } - }).collect(), - instances:Vec::new(), - }); - }, - _=>println!("model_name={} error",model_name), - } - }, - _=>println!("no model name={}",model_name), +//partially constructed map types +pub struct PartialMap1{ + attributes:Vec, + prop_models:Vec, + world_meshes:Vec, + world_models:Vec, + modes:strafesnet_common::gameplay_modes::Modes, +} +impl PartialMap1{ + pub fn add_prop_meshes( + self, + prop_meshes:impl IntoIterator, + acquire_render_config_id:&mut AcquireRenderConfigId, + )->PartialMap2 + where + AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId, + { + 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,acquire_render_config_id){ + Ok(mesh)=>Some((mesh_id,mesh)), + Err(e)=>{ + println!("error converting mesh: {e}"); + None + } } - } + ).collect(), + prop_models:self.prop_models, + world_meshes:self.world_meshes, + world_models:self.world_models, + modes:self.modes, + } + } +} +pub struct PartialMap2{ + attributes:Vec, + prop_meshes:Vec<(model::MeshId,model::Mesh)>, + prop_models:Vec, + world_meshes:Vec, + world_models:Vec, + modes:strafesnet_common::gameplay_modes::Modes, +} +impl PartialMap2{ + pub fn add_render_configs_and_textures( + mut self, + render_configs:impl IntoIterator, + textures:impl IntoIterator)>, + )->map::CompleteMap{ + //merge mesh and model lists, flatten and remap all ids + let mesh_id_offset=self.world_meshes.len(); + println!("prop_meshes.len()={}",self.prop_meshes.len()); + let (mut prop_meshes,prop_mesh_id_map):(Vec,std::collections::HashMap) + =self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{ + (mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32))) + }).unzip(); + self.world_meshes.append(&mut prop_meshes); + //there is no modes or runtime behaviour with references to the model ids currently + //so just relentlessly cull them if the mesh is missing + self.world_models.extend(self.prop_models.into_iter().filter_map(|mut model| + prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{ + model.mesh=new_mesh_id; + model + }) + )); + //let mut models=Vec::new(); + let (textures,texture_id_map):(Vec>,std::collections::HashMap) + =textures.into_iter() + //.filter_map(f) cull unused textures + .enumerate().map(|(new_texture_id,(old_texture_id,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)|{ + //this may generate duplicate no-texture render configs but idc + render_config.texture=render_config.texture.and_then(|texture_id| + texture_id_map.get(&texture_id).copied() + ); + render_config + }).collect(); + map::CompleteMap{ + modes:self.modes, + attributes:self.attributes, + meshes:self.world_meshes, + models:self.world_models, + textures, + render_configs, + } + } +} - //generate model instances - for prop in bsp.static_props(){ - let placement=prop.as_prop_placement(); - if let Some(&model_index)=model_map.get(placement.model){ - prop_models[model_index].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::new( - integer::Planar64Mat3::try_from( - glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale)) - //TODO: figure this out - *glam::Mat3A::from_quat(glam::Quat::from_xyzw( - placement.rotation.v.x,//b - placement.rotation.v.y,//c - placement.rotation.v.z,//d - placement.rotation.s,//a - )) - ).unwrap(), - valve_transform(<[f32;3]>::from(placement.origin)), - ), - attributes:model::CollisionAttributes::Decoration, - ..Default::default() - }); - }else{ - //println!("model not found {}",placement.model); - } - } +fn convert_mesh( + model_data:crate::data::ModelData, + acquire_render_config_id:&mut AcquireRenderConfigId, +)->Result +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(); - //actually add the prop models - prop_models.append(&mut models); -*/ + 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(<[f32;3]>::from(vertex.position))); + spam_normal.push(valve_transform(<[f32;3]>::from(vertex.normal))); + 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/src/data.rs b/src/data.rs new file mode 100644 index 0000000..83ca678 --- /dev/null +++ b/src/data.rs @@ -0,0 +1,60 @@ +pub struct Bsp(vbsp::Bsp); +impl Bsp{ + pub const fn new(value:vbsp::Bsp)->Self{ + Self(value) + } +} +impl AsRef for Bsp{ + fn as_ref(&self)->&vbsp::Bsp{ + &self.0 + } +} + +pub struct MdlData(Vec); +impl MdlData{ + pub const fn new(value:Vec)->Self{ + Self(value) + } +} +impl AsRef<[u8]> for MdlData{ + fn as_ref(&self)->&[u8]{ + self.0.as_ref() + } +} +pub struct VtxData(Vec); +impl VtxData{ + pub const fn new(value:Vec)->Self{ + Self(value) + } +} +impl AsRef<[u8]> for VtxData{ + fn as_ref(&self)->&[u8]{ + self.0.as_ref() + } +} +pub struct VvdData(Vec); +impl VvdData{ + pub const fn new(value:Vec)->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{ + 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/src/lib.rs b/src/lib.rs index f683f49..81978ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ mod bsp; +pub mod data; -pub struct Bsp(vbsp::Bsp); +pub use data::Bsp; #[derive(Debug)] pub enum ReadError{ @@ -20,17 +21,17 @@ pub fn read(mut input:R)->Result{ //TODO: mmap input.read_to_end(&mut s).map_err(ReadError::Io)?; - vbsp::Bsp::read(s.as_slice()).map(Bsp).map_err(ReadError::Bsp) + vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp) } -pub fn convert( +pub fn convert( bsp:&Bsp, - acquire_texture_id:AcquireTextureId, + acquire_render_config_id:&mut AcquireRenderConfigId, acquire_mesh_id:AcquireMeshId -)->strafesnet_common::map::CompleteMap +)->bsp::PartialMap1 where - AcquireTextureId:FnMut(&str)->strafesnet_common::model::TextureId, + AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId, AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId, { - bsp::convert(&bsp.0,acquire_texture_id,acquire_mesh_id) + bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id) } \ No newline at end of file