Compare commits

...

9 Commits

Author SHA1 Message Date
fda5baf4b1 todo 2024-02-13 22:27:45 -08:00
272ad40c44 wrap types + error 2024-02-13 22:27:45 -08:00
c02e69f02c move mesh loader prototype code to deferred_loader 2024-02-13 22:27:45 -08:00
4678318b3b implement bsp.models() conversion 2024-02-13 22:27:45 -08:00
0fb96d2c19 unused 2024-02-13 21:06:43 -08:00
ea55d4bcfb tab down this function 2024-02-13 21:06:43 -08:00
05f466727c switch to read-convert style 2024-02-13 21:06:43 -08:00
91f11f20d0 wip 2024-02-13 18:02:49 -08:00
957f62f9ea update common 2024-02-13 18:02:49 -08:00
4 changed files with 151 additions and 222 deletions

13
Cargo.lock generated
View File

@ -200,6 +200,16 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "id"
version = "0.1.0"
source = "git+https://git.itzana.me/Quaternions/id?rev=1f710976cc786c8853dab73d6e1cee53158deeb0#1f710976cc786c8853dab73d6e1cee53158deeb0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "indexmap"
version = "2.2.1"
@ -344,9 +354,10 @@ dependencies = [
[[package]]
name = "strafesnet_common"
version = "0.1.0"
source = "git+https://git.itzana.me/StrafesNET/common?rev=434ca29aef7e3015c9ca1ed45de8fef42e33fdfb#434ca29aef7e3015c9ca1ed45de8fef42e33fdfb"
source = "git+https://git.itzana.me/StrafesNET/common?rev=47cdea0c8a5d10a2440ca6270a975d560aa3642d#47cdea0c8a5d10a2440ca6270a975d560aa3642d"
dependencies = [
"glam",
"id",
]
[[package]]

View File

@ -7,6 +7,6 @@ edition = "2021"
[dependencies]
glam = "0.25.0"
strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "434ca29aef7e3015c9ca1ed45de8fef42e33fdfb" }
strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "47cdea0c8a5d10a2440ca6270a975d560aa3642d" }
vbsp = "0.5.0"
vmdl = "0.1.1"

View File

@ -1,39 +1,44 @@
use strafesnet_common::{model,integer};
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 generate_indexed_models<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<model::IndexedModelInstances,vbsp::BspError>{
let mut s=Vec::new();
pub fn convert<AcquireTextureId,AcquireMeshId>(
bsp:&vbsp::Bsp,
mut acquire_texture_id:AcquireTextureId,
mut acquire_mesh_id:AcquireMeshId
)->strafesnet_common::map::CompleteMap
where
AcquireTextureId:FnMut(&str)->model::TextureId,
AcquireMeshId:FnMut(&str)->model::MeshId,
{
let mut unique_render_configs=Vec::new();
match input.read_to_end(&mut s){
Ok(_)=>(),
Err(e)=>println!("load_bsp::generate_indexed_models read_to_end failed: {:?}",e),
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);
//declare all prop models to Loader
for prop in bsp.static_props(){
acquire_mesh_id(prop.model());
}
match vbsp::Bsp::read(s.as_slice()){
Ok(bsp)=>{
let mut spawn_point=integer::Planar64Vec3::ZERO;
//TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups
let vertices: Vec<_> = bsp
.vertices
.iter()
.map(|vertex|<[f32;3]>::from(vertex.position))
.collect();
let mut name_from_texture_id=Vec::new();
let mut texture_id_from_name=std::collections::HashMap::new();
let mut models=bsp.models().map(|world_model|{
//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<model::Mesh>,Vec<model::Model>)=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 groups=world_model.faces()
.filter(|face| face.is_visible())//TODO: look at this
.map(|face|{
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();
let (texture_u,texture_v)=(glam::Vec3A::from_slice(&face_texture.texture_transforms_u[0..3]),glam::Vec3A::from_slice(&face_texture.texture_transforms_v[0..3]));
@ -41,197 +46,75 @@ pub fn generate_indexed_models<R:std::io::Read+std::io::Seek>(input:&mut R)->Res
let texture_size=glam::vec2(face_texture_data.width as f32,face_texture_data.height as f32);
//texture
let texture_id=if let Some(&texture_id)=texture_id_from_name.get(face_texture_data.name()){
texture_id
}else{
let texture_id=name_from_texture_id.len() as u32;
texture_id_from_name.insert(face_texture_data.name().to_string(),texture_id);
name_from_texture_id.push(face_texture_data.name().to_string());
texture_id
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());
//normal
let normal=face.normal();
let normal_idx=spam_normal.len() as u32;
spam_normal.push(valve_transform(<[f32;3]>::from(normal)));
let mut vertices:Vec<u32>=face.vertex_indexes().map(|vertex_index|{
let pos=glam::Vec3A::from_array(vertices[vertex_index as usize]);
let mut indices:Vec<model::VertexId>=face.vertex_positions().map(|vertex_position|{
let vertex_xyz=<[f32;3]>::from(vertex_position);
let pos=glam::Vec3A::from_array(vertex_xyz);
let pos_idx=spam_pos.len();
spam_pos.push(valve_transform(vertices[vertex_index as usize]));
spam_pos.push(valve_transform(vertex_xyz));
//calculate texture coordinates
let tex=(glam::vec2(pos.dot(texture_u),pos.dot(texture_v))+texture_offset)/texture_size;
let tex_idx=spam_tex.len() as u32;
spam_tex.push(tex);
let i=spam_vertices.len() as u32;
let vertex_id=model::VertexId::new(spam_vertices.len() as u32);
spam_vertices.push(model::IndexedVertex{
pos: pos_idx as u32,
tex: tex_idx as u32,
normal: normal_idx,
color: 0,
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),
});
i
vertex_id
}).collect();
vertices.reverse();
model::IndexedGroup{
texture:Some(texture_id),
polys:vec![model::IndexedPolygon{vertices}],
if face.is_visible(){
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(vec![indices]))
}).collect();
model::IndexedModel{
(
model::Mesh{
unique_pos:spam_pos,
unique_tex:spam_tex,
unique_normal:spam_normal,
unique_color:vec![glam::Vec4::ONE],
unique_vertices:spam_vertices,
groups,
instances:vec![model::ModelInstance{
attributes:model::CollisionAttributes::Decoration,
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(<[f32;3]>::from(world_model.origin))
),
..Default::default()
}],
}
}).collect();
//dedupe prop models
let mut model_dedupe=std::collections::HashSet::new();
for prop in bsp.static_props(){
model_dedupe.insert(prop.model());
}
//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:i as u32,
tex:i as u32,
normal:i as u32,
color:0,
});
}
let model_id=prop_models.len();
model_map.insert(model_name,model_id);
prop_models.push(model::IndexedModel{
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::<Vec<usize>>().chunks(3).map(|tri|{
model::IndexedPolygon{vertices:vec![tri[0] as u32,tri[1] as u32,tri[2] as u32]}
}).collect::<Vec<model::IndexedPolygon>>()
}).flatten().collect()
color:glam::Vec4::ONE,
},
}
}).collect(),
instances:Vec::new(),
});
},
_=>println!("model_name={} error",model_name),
}
},
_=>println!("no model name={}",model_name),
}
}
)
}).unzip();
//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);
}
}
//actually add the prop models
prop_models.append(&mut models);
Ok(model::IndexedModelInstances{
textures:name_from_texture_id,
models:prop_models,
spawn_point,
modes:Vec::new(),
})
},
Err(e)=>{
println!("rotten {:?}",e);
Err(e)
},
map::CompleteMap{
attributes:unique_attributes,
meshes,
render_configs:unique_render_configs,
models,
modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
}
}

View File

@ -1 +1,36 @@
pub mod bsp;
mod bsp;
pub struct Bsp(vbsp::Bsp);
#[derive(Debug)]
pub enum ReadError{
Bsp(vbsp::BspError),
Io(std::io::Error),
}
impl std::fmt::Display for ReadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ReadError{}
pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
let mut s=Vec::new();
//TODO: mmap
input.read_to_end(&mut s).map_err(ReadError::Io)?;
vbsp::Bsp::read(s.as_slice()).map(Bsp).map_err(ReadError::Bsp)
}
pub fn convert<AcquireTextureId,AcquireMeshId>(
bsp:&Bsp,
acquire_texture_id:AcquireTextureId,
acquire_mesh_id:AcquireMeshId
)->strafesnet_common::map::CompleteMap
where
AcquireTextureId:FnMut(&str)->strafesnet_common::model::TextureId,
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
{
bsp::convert(&bsp.0,acquire_texture_id,acquire_mesh_id)
}