diff --git a/lib/bsp_loader/src/brush.rs b/lib/bsp_loader/src/brush.rs new file mode 100644 index 0000000..f0797a2 --- /dev/null +++ b/lib/bsp_loader/src/brush.rs @@ -0,0 +1,342 @@ +use strafesnet_common::integer::Planar64; +use strafesnet_common::{model,integer}; +use strafesnet_common::integer::{vec3::Vector3,Fixed,Ratio}; + +use crate::{valve_transform_normal,valve_transform_dist}; + +#[derive(Hash,Eq,PartialEq)] +struct Face{ + normal:integer::Planar64Vec3, + dot:integer::Planar64, +} + +#[derive(Debug)] +struct Faces{ + faces:Vec<Vec<integer::Planar64Vec3>>, +} + +fn solve3(c0:&Face,c1:&Face,c2:&Face)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<3,96>>>{ + let n0_n1=c0.normal.cross(c1.normal); + let det=c2.normal.dot(n0_n1); + if det.abs().is_zero(){ + return None; + } + Some(( + c1.normal.cross(c2.normal)*c0.dot + +c2.normal.cross(c0.normal)*c1.dot + +c0.normal.cross(c1.normal)*c2.dot + )/det) +} + +#[derive(Debug)] +pub enum PlanesToFacesError{ + InitFace1, + InitFace2, + InitIntersection, + FindNewIntersection, + EmptyFaces, + InfiniteLoop1, + InfiniteLoop2, +} +impl std::fmt::Display for PlanesToFacesError{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl core::error::Error for PlanesToFacesError{} + +fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,PlanesToFacesError>{ + let mut faces=Vec::new(); + // for each face, determine one edge at a time until you complete the face + 'face: for face0 in &face_list{ + // 1. find first edge + // 2. follow edges around face + + // === finding first edge === + // 1. pick the most perpendicular set of 3 faces + // 2. check if any faces occlude the intersection + // 3. use this test to replace left and right alternating until they are not occluded + + // find the most perpendicular face to face0 + let mut face1=face_list.iter().min_by_key(|&p|{ + face0.normal.dot(p.normal).abs() + }).ok_or(PlanesToFacesError::InitFace1)?; + + // direction of edge formed by face0 x face1 + let edge_dir=face0.normal.cross(face1.normal); + + // find the most perpendicular face to both face0 and face1 + let mut face2=face_list.iter().max_by_key(|&p|{ + // find the best *oriented* face (no .abs()) + edge_dir.dot(p.normal) + }).ok_or(PlanesToFacesError::InitFace2)?; + + let mut detect_loop=200u8; + + let mut intersection=solve3(face0,face1,face2).ok_or(PlanesToFacesError::InitIntersection)?; + + // repeatedly update face1, face2 until all faces form part of the convex solid + 'find: loop{ + detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop1)?; + // test if any *other* faces occlude the intersection + for new_face in &face_list{ + // new face occludes intersection point + if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){ + // replace one of the faces with the new face + // dont' try to replace face0 because we are exploring that face in particular + if let Some(new_intersection)=solve3(face0,new_face,face2){ + // face1 does not occlude (or intersect) the new intersection + if (face1.dot.fix_2()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){ + face1=new_face; + intersection=new_intersection; + continue 'find; + } + } + if let Some(new_intersection)=solve3(face0,face1,new_face){ + // face2 does not occlude (or intersect) the new intersection + if (face2.dot.fix_2()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){ + face2=new_face; + intersection=new_intersection; + continue 'find; + } + } + } + } + + // we have found a set of faces for which the intersection is on the convex solid + break 'find; + } + + // check if face0 must go, meaning it is a degenerate face and does not contribute anything to the convex solid + for new_face in &face_list{ + if core::ptr::eq(face0,new_face){ + continue; + } + if core::ptr::eq(face1,new_face){ + continue; + } + if core::ptr::eq(face2,new_face){ + continue; + } + // new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate + if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){ + // abort! reject face0 entirely + continue 'face; + } + } + + // === follow edges around face === + // Note that we chose face2 such that the 3 faces create a particular winding order. + // If we choose a consistent face to follow (face1, face2) it will always wind with a consistent chirality + + let mut detect_loop=200u8; + + // keep looping until we meet this face again + let face1=face1; + let mut face=Vec::new(); + loop{ + // push point onto vertices + // problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless + face.push(intersection.divide().fix_1()); + + // we looped back around to face1, we're done! + if core::ptr::eq(face1,face2){ + break; + } + + // the measure + let edge_dir=face0.normal.cross(face2.normal); + + // the dot product to beat + let d_intersection=edge_dir.dot(intersection.num)/intersection.den; + + // find the next face moving clockwise around face0 + let (new_face,new_intersection,_)=face_list.iter().filter_map(|new_face|{ + // ignore faces that are part of the current edge + if core::ptr::eq(face0,new_face) + |core::ptr::eq(face2,new_face){ + return None; + } + let new_intersection=solve3(face0,face2,new_face)?; + + // the d value must be larger + let d_new_intersection=edge_dir.dot(new_intersection.num)/new_intersection.den; + if d_new_intersection.le_ratio(d_intersection){ + return None; + } + + Some((new_face,new_intersection,d_new_intersection)) + }).min_by_key(|&(_,_,d)|d).ok_or(PlanesToFacesError::FindNewIntersection)?; + + face2=new_face; + intersection=new_intersection; + + detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop2)?; + } + + faces.push(face); + } + + if faces.is_empty(){ + Err(PlanesToFacesError::EmptyFaces) + }else{ + Ok(Faces{ + faces, + }) + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub enum BrushToMeshError{ + SliceBrushSides, + MissingPlane, + InvalidFaceCount{ + count:usize, + }, + InvalidPlanes(PlanesToFacesError), +} +impl std::fmt::Display for BrushToMeshError{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl core::error::Error for BrushToMeshError{} + +pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{ + // generate the mesh + let mut mb=model::MeshBuilder::new(); + let color=mb.acquire_color_id(glam::Vec4::ONE); + let tex=mb.acquire_tex_id(glam::Vec2::ZERO); + // normals are ignored by physics + let normal=mb.acquire_normal_id(integer::vec3::ZERO); + + let polygon_list=faces.into_iter().map(|face|{ + face.into_iter().map(|pos|{ + let pos=mb.acquire_pos_id(pos); + mb.acquire_vertex_id(model::IndexedVertex{ + pos, + tex, + normal, + color, + }) + }).collect() + }).collect(); + + let polygon_groups=vec![model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))]; + let physics_groups=vec![model::IndexedPhysicsGroup{ + groups:vec![model::PolygonGroupId::new(0)], + }]; + let graphics_groups=vec![]; + + mb.build(polygon_groups,graphics_groups,physics_groups) +} + +pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,BrushToMeshError>{ + let brush_start_idx=brush.brush_side as usize; + let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize; + let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?; + let face_list=sides.iter().map(|side|{ + // The so-called tumor brushes have TRIGGER bit set + // but also ignore visleaf hint and skip sides + const TUMOR:vbsp::TextureFlags=vbsp::TextureFlags::HINT.union(vbsp::TextureFlags::SKIP).union(vbsp::TextureFlags::TRIGGER); + if let Some(texture_info)=bsp.textures_info.get(side.texture_info as usize){ + if texture_info.flags.intersects(TUMOR){ + return None; + } + } + let plane=bsp.plane(side.plane as usize)?; + Some(Face{ + normal:valve_transform_normal(plane.normal.into()), + dot:valve_transform_dist(plane.dist.into()), + }) + }).collect::<Option<std::collections::HashSet<_>>>().ok_or(BrushToMeshError::MissingPlane)?; + + if face_list.len()<4{ + return Err(BrushToMeshError::InvalidFaceCount{count:face_list.len()}); + } + + let faces=planes_to_faces(face_list).map_err(BrushToMeshError::InvalidPlanes)?; + + let mesh=faces_to_mesh(faces.faces); + + Ok(mesh) +} + +pub fn unit_cube()->model::Mesh{ + let face_list=[ + Face{normal:integer::vec3::X,dot:Planar64::ONE}, + Face{normal:integer::vec3::Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::Z,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE}, + ].into_iter().collect(); + let faces=planes_to_faces(face_list).unwrap(); + let mesh=faces_to_mesh(faces.faces); + mesh +} + +#[cfg(test)] +mod test{ + use super::*; + #[test] + fn test_cube(){ + let face_list=[ + Face{normal:integer::vec3::X,dot:Planar64::ONE}, + Face{normal:integer::vec3::Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::Z,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE}, + ].into_iter().collect(); + let faces=planes_to_faces(face_list).unwrap(); + assert_eq!(faces.faces.len(),6); + dbg!(faces); + } + #[test] + fn test_cube_with_degernate_face(){ + let face_list=[ + Face{normal:integer::vec3::X,dot:Planar64::ONE}, + Face{normal:integer::vec3::Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::Z,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Z,dot:Planar64::EPSILON}, + ].into_iter().collect(); + let faces=planes_to_faces(face_list).unwrap(); + assert_eq!(faces.faces.len(),6); + dbg!(faces); + } + #[test] + fn test_cube_with_degernate_face2(){ + let face_list=[ + Face{normal:integer::vec3::X,dot:Planar64::ONE}, + Face{normal:integer::vec3::Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::Z,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:-Planar64::EPSILON}, + ].into_iter().collect(); + let faces=planes_to_faces(face_list).unwrap(); + assert_eq!(faces.faces.len(),5); + dbg!(faces); + } + #[test] + fn test_cube_with_degernate_face3(){ + let face_list=[ + Face{normal:integer::vec3::X,dot:Planar64::ONE}, + Face{normal:integer::vec3::Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::Z,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE}, + Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:Planar64::EPSILON}, + ].into_iter().collect(); + let faces=planes_to_faces(face_list).unwrap(); + assert_eq!(faces.faces.len(),7); + dbg!(faces); + } +} diff --git a/lib/bsp_loader/src/bsp.rs b/lib/bsp_loader/src/bsp.rs index 6c4897e..0cbf32b 100644 --- a/lib/bsp_loader/src/bsp.rs +++ b/lib/bsp_loader/src/bsp.rs @@ -1,5 +1,7 @@ use std::borrow::Cow; +use vbsp_entities::css::Entity; + use strafesnet_common::{map,model,integer,gameplay_attributes}; use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader}; use strafesnet_deferred_loader::mesh::Meshes; @@ -32,6 +34,47 @@ fn ingest_vertex( }) } +fn add_brush<'a>( + mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>, + world_models:&mut Vec<model::Model>, + prop_models:&mut Vec<model::Model>, + model:&'a str, + origin:vbsp::Vector, + rendercolor:vbsp::Color, + attributes:gameplay_attributes::CollisionAttributesId, +){ + let transform=integer::Planar64Affine3::from_translation( + valve_transform(origin.into()) + ); + let color=(glam::Vec3::from_array([ + rendercolor.r as f32, + rendercolor.g as f32, + rendercolor.b as f32 + ])/255.0).extend(1.0); + + match model.split_at(1){ + // The first character of brush.model is '*' + ("*",id_str)=>match id_str.parse(){ + Ok(mesh_id)=>{ + let mesh=model::MeshId::new(mesh_id); + world_models.push( + model::Model{mesh,attributes,transform,color} + ); + }, + Err(e)=>{ + println!("Brush model int parse error: {e} model={model}"); + return; + }, + }, + _=>{ + let mesh=mesh_deferred_loader.acquire_mesh_id(model); + prop_models.push( + model::Model{mesh,attributes,transform,color} + ); + } + } +} + pub fn convert<'a>( bsp:&'a crate::Bsp, render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>, @@ -41,16 +84,20 @@ pub fn convert<'a>( //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); + unique_attributes.push(gameplay_attributes::CollisionAttributes::contact_default()); + unique_attributes.push(gameplay_attributes::CollisionAttributes::intersect_default()); + const ATTRIBUTE_DECORATION:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0); + const ATTRIBUTE_CONTACT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(1); + const ATTRIBUTE_INTERSECT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(2); //declare all prop models to Loader - let prop_models=bsp.static_props().map(|prop|{ + let mut 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, + attributes:ATTRIBUTE_DECORATION, transform:integer::Planar64Affine3::new( integer::mat3::try_from_f32_array_2d(( glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale)) @@ -67,7 +114,7 @@ pub fn convert<'a>( //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 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); @@ -118,49 +165,126 @@ pub fn convert<'a>( 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), + let mut found_spawn=None; + + let mut world_models=Vec::new(); + + // the one and only world model 0 + world_models.push(model::Model{ + mesh:model::MeshId::new(0), + attributes:ATTRIBUTE_DECORATION, + transform:integer::Planar64Affine3::IDENTITY, + color:glam::Vec4::W, + }); + + const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255}; + for raw_ent in &bsp.entities{ + match raw_ent.parse(){ + Ok(Entity::Cycler(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::EnvSprite(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.parse().unwrap_or(WHITE),ATTRIBUTE_DECORATION), + Ok(Entity::FuncBreakable(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncBrush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncDoor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncIllusionary(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncMonitor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncMovelinear(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncPhysbox(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncPhysboxMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncRotButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::FuncRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncTracktrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncTrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION), + Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ATTRIBUTE_DECORATION), + Ok(Entity::PropDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::PropDynamic(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::PropDynamicOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::PropPhysics(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::PropPhysicsMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::PropPhysicsOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::PropRagdoll(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerGravity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerHurt(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerLook(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerOnce(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerProximity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerPush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerSoundscape(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerVphysicsMotion(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::TriggerWind(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION), + Ok(Entity::InfoPlayerCounterterrorist(spawn))=>{ + found_spawn=Some(valve_transform(spawn.origin.into())); + }, + Ok(Entity::InfoPlayerTerrorist(spawn))=>{ + found_spawn=Some(valve_transform(spawn.origin.into())); + }, + Err(e)=>{ + println!("Bsp Entity parse error: {e}"); + }, + _=>(), } - }).collect(); + } + + // physics models + for brush in &bsp.brushes{ + if !brush.flags.contains(vbsp::BrushFlags::SOLID){ + continue; + } + let mesh_result=crate::brush::brush_to_mesh(bsp,brush); + match mesh_result{ + Ok(mesh)=>{ + let mesh_id=model::MeshId::new(world_meshes.len() as u32); + world_meshes.push(mesh); + world_models.push(model::Model{ + mesh:mesh_id, + attributes:ATTRIBUTE_CONTACT_DEFAULT, + transform:integer::Planar64Affine3::new( + integer::mat3::identity(), + integer::vec3::ZERO, + ), + color:glam::Vec4::ONE, + }); + }, + Err(e)=>println!("Brush mesh error: {e}"), + } + } + + let mut modes_list=Vec::new(); + if let Some(spawn_point)=found_spawn{ + // create a new mesh + let mesh_id=model::MeshId::new(world_meshes.len() as u32); + world_meshes.push(crate::brush::unit_cube()); + // create a new model + let model_id=model::ModelId::new(world_models.len() as u32); + world_models.push(model::Model{ + mesh:mesh_id, + attributes:ATTRIBUTE_INTERSECT_DEFAULT, + transform:integer::Planar64Affine3::from_translation(spawn_point), + color:glam::Vec4::W, + }); + + let first_stage=strafesnet_common::gameplay_modes::Stage::empty(model_id); + let main_mode=strafesnet_common::gameplay_modes::Mode::new( + strafesnet_common::gameplay_style::StyleModifiers::source_bhop(), + model_id, + std::collections::HashMap::new(), + vec![first_stage], + std::collections::HashMap::new(), + ); + modes_list.push(main_mode); + } PartialMap1{ attributes:unique_attributes, world_meshes, prop_models, world_models, - modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()), + modes:strafesnet_common::gameplay_modes::Modes::new(modes_list), } } diff --git a/lib/bsp_loader/src/lib.rs b/lib/bsp_loader/src/lib.rs index 2905a16..6774ccc 100644 --- a/lib/bsp_loader/src/lib.rs +++ b/lib/bsp_loader/src/lib.rs @@ -2,6 +2,7 @@ use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLo mod bsp; mod mesh; +mod brush; pub mod loader; const VALVE_SCALE:f32=1.0/16.0;