use strafesnet_common::{map,model,integer,gameplay_attributes}; 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_bsp( 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, { //figure out real attributes later let mut unique_attributes=Vec::new(); unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration); 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 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_array(placement.rotation.into())) ).unwrap(), valve_transform(placement.origin.into()), ), 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 (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(); let mut spam_tex=Vec::new(); let mut spam_normal=Vec::new(); let mut spam_vertices=Vec::new(); let mut graphics_groups=Vec::new(); let mut physics_group=model::IndexedPhysicsGroup::default(); let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{ let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32); let face_texture=face.texture(); let face_texture_data=face_texture.texture_data(); //this would be better as a 4x2 matrix let texture_transform_u=glam::Vec4::from_array(face_texture.texture_transforms_u)/(face_texture_data.width as f32); let texture_transform_v=glam::Vec4::from_array(face_texture.texture_transforms_v)/(face_texture_data.height as f32); //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(); let normal_idx=spam_normal.len() as u32; spam_normal.push(valve_transform(normal.into())); let mut polygon_iter=face.vertex_positions().map(|vertex_position|{ let vertex_xyz=vertex_position.into(); let pos_idx=spam_pos.len(); spam_pos.push(valve_transform(vertex_xyz)); //calculate texture coordinates let pos=glam::Vec3::from_array(vertex_xyz).extend(1.0); let tex=glam::vec2(texture_transform_u.dot(pos),texture_transform_v.dot(pos)); let tex_idx=spam_tex.len() as u32; spam_tex.push(tex); let vertex_id=model::VertexId::new(spam_vertices.len() as u32); spam_vertices.push(model::IndexedVertex{ pos:model::PositionId::new(pos_idx as u32), tex:model::TextureCoordinateId::new(tex_idx as u32), normal:model::NormalId::new(normal_idx), color:model::ColorId::new(0), }); vertex_id }); let polygon_list=std::iter::from_fn(move||{ match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){ (Some(v1),Some(v2),Some(v3))=>Some(vec![v1,v2,v3]), //ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate _=>None, } }).collect(); if face.is_visible(){ //TODO: deduplicate graphics groups by render id graphics_groups.push(model::IndexedGraphicsGroup{ render:render_id, groups:vec![polygon_group_id], }) } physics_group.groups.push(polygon_group_id); model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list)) }).collect(); ( model::Mesh{ unique_pos:spam_pos, unique_tex:spam_tex, unique_normal:spam_normal, unique_color:vec![glam::Vec4::ONE], unique_vertices:spam_vertices, polygon_groups, graphics_groups, physics_groups:vec![physics_group], }, model::Model{ mesh:mesh_id, attributes:TEMP_TOUCH_ME_ATTRIBUTE, transform:integer::Planar64Affine3::new( integer::Planar64Mat3::default(), valve_transform(world_model.origin.into()) ), color:glam::Vec4::ONE, }, ) }).unzip(); PartialMap1{ attributes:unique_attributes, world_meshes, prop_models, world_models, modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()), } } //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, mut acquire_render_config_id: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,&mut 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, } } } 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(); 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, }) }