2024-02-14 02:02:32 +00:00
|
|
|
use strafesnet_common::{map,model,integer,gameplay_attributes};
|
2024-01-31 01:42:41 +00:00
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
2024-02-15 01:11:41 +00:00
|
|
|
pub fn convert_bsp<AcquireRenderConfigId,AcquireMeshId>(
|
2024-02-14 02:02:32 +00:00
|
|
|
bsp:&vbsp::Bsp,
|
2024-02-15 09:41:26 +00:00
|
|
|
mut acquire_render_config_id:AcquireRenderConfigId,
|
2024-02-14 02:02:32 +00:00
|
|
|
mut acquire_mesh_id:AcquireMeshId
|
2024-02-15 01:11:41 +00:00
|
|
|
)->PartialMap1
|
2024-02-14 02:02:32 +00:00
|
|
|
where
|
2024-02-15 01:11:41 +00:00
|
|
|
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
2024-02-14 02:02:32 +00:00
|
|
|
AcquireMeshId:FnMut(&str)->model::MeshId,
|
|
|
|
{
|
2024-02-15 01:11:41 +00:00
|
|
|
//figure out real attributes later
|
2024-02-14 02:02:32 +00:00
|
|
|
let mut unique_attributes=Vec::new();
|
2024-02-15 09:03:10 +00:00
|
|
|
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
2024-02-14 02:02:32 +00:00
|
|
|
const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
|
|
|
|
|
2024-02-15 01:11:41 +00:00
|
|
|
let mut prop_mesh_count=0;
|
2024-02-14 02:02:32 +00:00
|
|
|
//declare all prop models to Loader
|
2024-02-15 01:11:41 +00:00
|
|
|
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
|
2024-02-21 17:15:05 +00:00
|
|
|
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))
|
2024-02-15 01:11:41 +00:00
|
|
|
).unwrap(),
|
2024-02-21 17:15:05 +00:00
|
|
|
valve_transform(placement.origin.into()),
|
2024-02-15 01:11:41 +00:00
|
|
|
),
|
|
|
|
color:glam::Vec4::ONE,
|
|
|
|
}
|
|
|
|
}).collect();
|
2024-01-31 01:42:41 +00:00
|
|
|
|
2024-02-14 02:02:32 +00:00
|
|
|
//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.
|
2024-02-15 01:11:41 +00:00
|
|
|
let (world_meshes,world_models):(Vec<model::Mesh>,Vec<model::Model>)=bsp.models().enumerate().map(|(mesh_id,world_model)|{
|
2024-02-14 02:02:32 +00:00
|
|
|
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();
|
2024-02-18 04:31:02 +00:00
|
|
|
//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);
|
2024-02-14 02:02:32 +00:00
|
|
|
|
2024-02-15 01:11:41 +00:00
|
|
|
//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()));
|
2024-02-14 02:02:32 +00:00
|
|
|
|
|
|
|
//normal
|
|
|
|
let normal=face.normal();
|
|
|
|
let normal_idx=spam_normal.len() as u32;
|
2024-02-21 17:15:05 +00:00
|
|
|
spam_normal.push(valve_transform(normal.into()));
|
2024-02-18 03:33:00 +00:00
|
|
|
let mut polygon_iter=face.vertex_positions().map(|vertex_position|{
|
2024-02-21 17:15:05 +00:00
|
|
|
let vertex_xyz=vertex_position.into();
|
2024-02-14 02:02:32 +00:00
|
|
|
let pos_idx=spam_pos.len();
|
|
|
|
spam_pos.push(valve_transform(vertex_xyz));
|
|
|
|
|
|
|
|
//calculate texture coordinates
|
2024-02-18 04:31:02 +00:00
|
|
|
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));
|
2024-02-14 02:02:32 +00:00
|
|
|
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
|
2024-02-18 03:33:00 +00:00
|
|
|
});
|
|
|
|
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,
|
|
|
|
}
|
2024-01-31 01:42:41 +00:00
|
|
|
}).collect();
|
2024-02-14 02:02:32 +00:00
|
|
|
if face.is_visible(){
|
2024-02-14 08:42:30 +00:00
|
|
|
//TODO: deduplicate graphics groups by render id
|
2024-02-14 02:02:32 +00:00
|
|
|
graphics_groups.push(model::IndexedGraphicsGroup{
|
|
|
|
render:render_id,
|
|
|
|
groups:vec![polygon_group_id],
|
|
|
|
})
|
2024-01-31 01:42:41 +00:00
|
|
|
}
|
2024-02-14 02:02:32 +00:00
|
|
|
physics_group.groups.push(polygon_group_id);
|
2024-02-18 03:33:00 +00:00
|
|
|
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
2024-02-14 02:02:32 +00:00
|
|
|
}).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(),
|
2024-02-21 17:15:05 +00:00
|
|
|
valve_transform(world_model.origin.into())
|
2024-02-14 02:02:32 +00:00
|
|
|
),
|
|
|
|
color:glam::Vec4::ONE,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}).unzip();
|
|
|
|
|
2024-02-15 01:11:41 +00:00
|
|
|
PartialMap1{
|
2024-02-14 02:02:32 +00:00
|
|
|
attributes:unique_attributes,
|
2024-02-15 01:11:41 +00:00
|
|
|
world_meshes,
|
|
|
|
prop_models,
|
|
|
|
world_models,
|
2024-02-14 02:02:32 +00:00
|
|
|
modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
|
|
|
|
}
|
|
|
|
}
|
2024-01-31 01:42:41 +00:00
|
|
|
|
2024-02-15 01:11:41 +00:00
|
|
|
//partially constructed map types
|
|
|
|
pub struct PartialMap1{
|
|
|
|
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
|
|
|
prop_models:Vec<model::Model>,
|
|
|
|
world_meshes:Vec<model::Mesh>,
|
|
|
|
world_models:Vec<model::Model>,
|
|
|
|
modes:strafesnet_common::gameplay_modes::Modes,
|
|
|
|
}
|
|
|
|
impl PartialMap1{
|
|
|
|
pub fn add_prop_meshes<AcquireRenderConfigId>(
|
|
|
|
self,
|
|
|
|
prop_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::ModelData)>,
|
2024-02-15 09:41:26 +00:00
|
|
|
mut acquire_render_config_id:AcquireRenderConfigId,
|
2024-02-15 01:11:41 +00:00
|
|
|
)->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
|
2024-02-15 09:41:26 +00:00
|
|
|
match convert_mesh(model_data,&mut acquire_render_config_id){
|
2024-02-15 01:11:41 +00:00
|
|
|
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<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
|
|
|
prop_meshes:Vec<(model::MeshId,model::Mesh)>,
|
|
|
|
prop_models:Vec<model::Model>,
|
|
|
|
world_meshes:Vec<model::Mesh>,
|
|
|
|
world_models:Vec<model::Model>,
|
|
|
|
modes:strafesnet_common::gameplay_modes::Modes,
|
|
|
|
}
|
|
|
|
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>)>,
|
|
|
|
)->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<model::Mesh>,std::collections::HashMap<model::MeshId,model::MeshId>)
|
|
|
|
=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<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))|{
|
|
|
|
(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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-01-31 01:42:41 +00:00
|
|
|
|
2024-02-15 01:11:41 +00:00
|
|
|
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();
|
2024-01-31 01:42:41 +00:00
|
|
|
|
2024-02-15 01:11:41 +00:00
|
|
|
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(){
|
2024-02-21 17:15:05 +00:00
|
|
|
spam_pos.push(valve_transform(vertex.position.into()));
|
|
|
|
spam_normal.push(valve_transform(vertex.normal.into()));
|
2024-02-15 01:11:41 +00:00
|
|
|
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);
|
2024-01-31 01:42:41 +00:00
|
|
|
|
2024-02-15 01:11:41 +00:00
|
|
|
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)
|
|
|
|
};
|
2024-01-31 01:42:41 +00:00
|
|
|
|
2024-02-15 01:11:41 +00:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|