forked from StrafesNET/strafe-project
244 lines
8.7 KiB
Rust
244 lines
8.7 KiB
Rust
use std::borrow::Cow;
|
|
|
|
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;
|
|
|
|
fn ingest_vertex(
|
|
mb:&mut model::MeshBuilder,
|
|
world_position:vbsp::Vector,
|
|
texture_transform_u:glam::Vec4,
|
|
texture_transform_v:glam::Vec4,
|
|
normal:model::NormalId,
|
|
color:model::ColorId,
|
|
)->model::VertexId{
|
|
//world_model.origin seems to always be 0,0,0
|
|
let vertex_xyz=world_position.into();
|
|
let pos=mb.acquire_pos_id(valve_transform(vertex_xyz));
|
|
|
|
//calculate texture coordinates
|
|
let pos_4d=glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
|
let tex=glam::vec2(texture_transform_u.dot(pos_4d),texture_transform_v.dot(pos_4d));
|
|
let tex=mb.acquire_tex_id(tex);
|
|
|
|
mb.acquire_vertex_id(model::IndexedVertex{
|
|
pos,
|
|
tex,
|
|
normal,
|
|
color,
|
|
})
|
|
}
|
|
|
|
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);
|
|
const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
|
|
|
|
//declare all prop models to Loader
|
|
let prop_models=bsp.static_props().map(|prop|{
|
|
//get or create mesh_id
|
|
let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model());
|
|
let placement=prop.as_prop_placement();
|
|
model::Model{
|
|
mesh:mesh_id,
|
|
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
|
transform:integer::Planar64Affine3::new(
|
|
integer::mat3::try_from_f32_array_2d((
|
|
glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
|
|
//TODO: figure this out
|
|
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))
|
|
).to_cols_array_2d()).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:Vec<model::Mesh>=bsp.models().map(|world_model|{
|
|
let mut mb=model::MeshBuilder::new();
|
|
|
|
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
|
let mut graphics_groups=Vec::new();
|
|
let mut render_id_to_graphics_group_id=std::collections::HashMap::new();
|
|
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);
|
|
|
|
//normal
|
|
let normal=mb.acquire_normal_id(valve_transform(face.normal().into()));
|
|
let mut polygon_iter=face.vertex_positions().map(|vertex_position|
|
|
world_model.origin+vertex_position
|
|
);
|
|
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([v1,v2,v3]),
|
|
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
|
_=>None,
|
|
}
|
|
}).map(|triplet|{
|
|
triplet.map(|world_position|
|
|
ingest_vertex(&mut mb,world_position,texture_transform_u,texture_transform_v,normal,color)
|
|
).to_vec()
|
|
}).collect();
|
|
if face.is_visible(){
|
|
//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=render_config_deferred_loader.acquire_render_config_id(Some(Cow::Borrowed(face_texture_data.name())));
|
|
//deduplicate graphics groups by render id
|
|
let graphics_group_id=*render_id_to_graphics_group_id.entry(render_id).or_insert_with(||{
|
|
let graphics_group_id=graphics_groups.len();
|
|
graphics_groups.push(model::IndexedGraphicsGroup{
|
|
render:render_id,
|
|
groups:vec![],
|
|
});
|
|
graphics_group_id
|
|
});
|
|
graphics_groups[graphics_group_id].groups.push(polygon_group_id);
|
|
}
|
|
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
|
}).collect();
|
|
|
|
mb.build(polygon_groups,graphics_groups,vec![])
|
|
}).collect();
|
|
|
|
let world_models:Vec<model::Model>=
|
|
//one instance of the main world mesh
|
|
std::iter::once((
|
|
//world_model
|
|
model::MeshId::new(0),
|
|
//model_origin
|
|
vbsp::Vector::from([0.0,0.0,0.0]),
|
|
//model_color
|
|
vbsp::Color{r:255,g:255,b:255},
|
|
)).chain(
|
|
//entities sprinkle instances of the other meshes around
|
|
bsp.entities.iter()
|
|
.flat_map(|ent|ent.parse())//ignore entity parsing errors
|
|
.filter_map(|ent|match ent{
|
|
vbsp::Entity::Brush(brush)=>Some(brush),
|
|
vbsp::Entity::BrushIllusionary(brush)=>Some(brush),
|
|
vbsp::Entity::BrushWall(brush)=>Some(brush),
|
|
vbsp::Entity::BrushWallToggle(brush)=>Some(brush),
|
|
_=>None,
|
|
}).flat_map(|brush|
|
|
//The first character of brush.model is '*'
|
|
brush.model[1..].parse().map(|mesh_id|//ignore parse int errors
|
|
(model::MeshId::new(mesh_id),brush.origin,brush.color)
|
|
)
|
|
)
|
|
).map(|(mesh_id,model_origin,vbsp::Color{r,g,b})|{
|
|
model::Model{
|
|
mesh:mesh_id,
|
|
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
|
transform:integer::Planar64Affine3::new(
|
|
integer::mat3::identity(),
|
|
valve_transform(model_origin.into())
|
|
),
|
|
color:(glam::Vec3::from_array([r as f32,g as f32,b as f32])/255.0).extend(1.0),
|
|
}
|
|
}).collect();
|
|
|
|
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<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<'a>(
|
|
self,
|
|
prop_meshes:Meshes,
|
|
)->PartialMap2{
|
|
PartialMap2{
|
|
attributes:self.attributes,
|
|
prop_meshes:prop_meshes.consume().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:RenderConfigs,
|
|
)->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,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::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)|{
|
|
//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,
|
|
}
|
|
}
|
|
}
|