diff --git a/lib/bsp_loader/src/bsp.rs b/lib/bsp_loader/src/bsp.rs index 38fb404..9c7b11b 100644 --- a/lib/bsp_loader/src/bsp.rs +++ b/lib/bsp_loader/src/bsp.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::collections::HashMap; use vbsp_entities_css::Entity; @@ -6,7 +7,7 @@ use strafesnet_common::{map,model,integer,gameplay_attributes as attr}; use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader}; use strafesnet_deferred_loader::mesh::Meshes; use strafesnet_deferred_loader::texture::{RenderConfigs,Texture}; -use strafesnet_common::gameplay_modes::{NormalizedMode,NormalizedModes,Mode,Stage}; +use strafesnet_common::gameplay_modes::{self as modes,NormalizedMode,NormalizedModes,Mode,Stage}; use crate::valve_transform; @@ -35,6 +36,12 @@ fn ingest_vertex( }) } +#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +enum AddBrush{ + Available(model::ModelId), + Deferred(model::ModelId), +} + fn add_brush<'a>( mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>, world_models:&mut Vec<model::Model>, @@ -43,7 +50,7 @@ fn add_brush<'a>( origin:vbsp::Vector, rendercolor:vbsp::Color, attributes:attr::CollisionAttributesId, -){ +)->Option<AddBrush>{ let transform=integer::Planar64Affine3::from_translation( valve_transform(origin.into()) ); @@ -58,20 +65,24 @@ fn add_brush<'a>( ("*",id_str)=>match id_str.parse(){ Ok(mesh_id)=>{ let mesh=model::MeshId::new(mesh_id); + let model_id=model::ModelId::new(world_models.len() as u32); world_models.push( model::Model{mesh,attributes,transform,color} ); + Some(AddBrush::Available(model_id)) }, Err(e)=>{ println!("Brush model int parse error: {e} model={model}"); - return; + None }, }, _=>{ let mesh=mesh_deferred_loader.acquire_mesh_id(model); + let model_id=model::ModelId::new(prop_models.len() as u32); prop_models.push( model::Model{mesh,attributes,transform,color} ); + Some(AddBrush::Deferred(model_id)) } } } @@ -80,7 +91,7 @@ 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{ +)->PartialMap1<'a>{ let bsp=bsp.as_ref(); //figure out real attributes later let unique_attributes=vec![ @@ -151,7 +162,7 @@ pub fn convert<'a>( 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 mut render_id_to_graphics_group_id=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(); @@ -213,22 +224,25 @@ pub fn convert<'a>( let destination_mesh_id=model::MeshId::new(world_meshes.len() as u32); world_meshes.push(crate::brush::unit_cube()); + let mut teleports=HashMap::new(); + let mut teleport_destinations=HashMap::new(); + const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255}; const ENTITY_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=ATTRIBUTE_DECORATION; const ENTITY_TRIGGER_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=ATTRIBUTE_INTERSECT_DEFAULT; for raw_ent in &bsp.entities{ macro_rules! ent_brush_default{ ($entity:ident)=>{ - add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,$entity.rendercolor,ENTITY_ATTRIBUTE) + {add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,$entity.rendercolor,ENTITY_ATTRIBUTE);} }; } macro_rules! ent_brush_prop{ ($entity:ident)=>{ - add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_ATTRIBUTE) + {add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_ATTRIBUTE);} }; } macro_rules! ent_brush_trigger{ ($entity:ident)=>{ - add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE) + {add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE);} }; } match raw_ent.parse(){ @@ -287,7 +301,7 @@ pub fn convert<'a>( Ok(Entity::FuncFishPool(_func_fish_pool))=>(), Ok(Entity::FuncFootstepControl(_func_footstep_control))=>(), Ok(Entity::FuncHostageRescue(_func_hostage_rescue))=>(), - 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::FuncIllusionary(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION);}, Ok(Entity::FuncLod(_func_lod))=>(), Ok(Entity::FuncMonitor(brush))=>ent_brush_default!(brush), Ok(Entity::FuncMovelinear(brush))=>ent_brush_default!(brush), @@ -300,9 +314,9 @@ pub fn convert<'a>( Ok(Entity::FuncSmokevolume(_func_smokevolume))=>(), Ok(Entity::FuncTracktrain(brush))=>ent_brush_default!(brush), Ok(Entity::FuncTrain(brush))=>ent_brush_default!(brush), - Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE), - Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE), - Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ENTITY_ATTRIBUTE), + Ok(Entity::FuncWall(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE);}, + Ok(Entity::FuncWallToggle(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE);}, + Ok(Entity::FuncWaterAnalog(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ENTITY_ATTRIBUTE);}, Ok(Entity::GamePlayerEquip(_game_player_equip))=>(), Ok(Entity::GameText(_game_text))=>(), Ok(Entity::GameUi(_game_ui))=>(), @@ -321,7 +335,17 @@ pub fn convert<'a>( Ok(Entity::InfoPlayerTerrorist(spawn))=>found_spawn=Some(spawn.origin), Ok(Entity::InfoTarget(_info_target))=>(), // InfoTeleportDestination is Spawn# - Ok(Entity::InfoTeleportDestination(_info_teleport_destination))=>(), + Ok(Entity::InfoTeleportDestination(info_teleport_destination))=>if let Some(target)=info_teleport_destination.targetname{ + // create a new model + let model_id=model::ModelId::new(world_models.len() as u32); + world_models.push(model::Model{ + mesh:destination_mesh_id, + attributes:ATTRIBUTE_INTERSECT_DEFAULT, + transform:integer::Planar64Affine3::from_translation(valve_transform(info_teleport_destination.origin.into())), + color:glam::Vec4::W, + }); + teleport_destinations.insert(target,model_id); + }, Ok(Entity::Infodecal(_infodecal))=>(), Ok(Entity::KeyframeRope(_keyframe_rope))=>(), Ok(Entity::Light(_light))=>(), @@ -371,14 +395,18 @@ pub fn convert<'a>( Ok(Entity::TriggerGravity(brush))=>ent_brush_trigger!(brush), Ok(Entity::TriggerHurt(brush))=>ent_brush_trigger!(brush), Ok(Entity::TriggerLook(brush))=>ent_brush_trigger!(brush), - Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE), + Ok(Entity::TriggerMultiple(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE);}, Ok(Entity::TriggerOnce(brush))=>ent_brush_trigger!(brush), Ok(Entity::TriggerProximity(brush))=>ent_brush_trigger!(brush), // TriggerPush is booster Ok(Entity::TriggerPush(brush))=>ent_brush_trigger!(brush), Ok(Entity::TriggerSoundscape(brush))=>ent_brush_trigger!(brush), // TriggerTeleport is Trigger# - Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE), + Ok(Entity::TriggerTeleport(brush))=>{ + if let (Some(model_id),Some(target))=(add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE),brush.target){ + teleports.insert(model_id,target); + } + }, Ok(Entity::TriggerVphysicsMotion(brush))=>ent_brush_trigger!(brush), Ok(Entity::TriggerWind(brush))=>ent_brush_trigger!(brush), Ok(Entity::WaterLodControl(_water_lod_control))=>(), @@ -456,8 +484,7 @@ pub fn convert<'a>( } } - let mut modes_list=Vec::new(); - if let Some(spawn_point)=found_spawn{ + let first_stage=found_spawn.map(|spawn_point|{ // create a new model let model_id=model::ModelId::new(world_models.len() as u32); world_models.push(model::Model{ @@ -467,58 +494,58 @@ pub fn convert<'a>( color:glam::Vec4::W, }); - let first_stage=Stage::empty(model_id); - let main_mode=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(NormalizedMode::new(main_mode)); - } + Stage::empty(model_id) + }); PartialMap1{ attributes:unique_attributes, world_meshes, prop_models, world_models, - modes:NormalizedModes::new(modes_list), + first_stage, + teleports, + teleport_destinations, } } //partially constructed map types -pub struct PartialMap1{ +pub struct PartialMap1<'a>{ attributes:Vec<attr::CollisionAttributes>, prop_models:Vec<model::Model>, world_meshes:Vec<model::Mesh>, world_models:Vec<model::Model>, - modes:NormalizedModes, + first_stage:Option<Stage>, + teleports:HashMap<AddBrush,&'a str>, + teleport_destinations:HashMap<&'a str,model::ModelId>, } -impl PartialMap1{ - pub fn add_prop_meshes<'a>( +impl<'a> PartialMap1<'a>{ + pub fn add_prop_meshes( self, prop_meshes:Meshes, - )->PartialMap2{ + )->PartialMap2<'a>{ 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, + first_stage:self.first_stage, + teleports:self.teleports, + teleport_destinations:self.teleport_destinations, } } } -pub struct PartialMap2{ +pub struct PartialMap2<'a>{ attributes:Vec<attr::CollisionAttributes>, prop_meshes:Vec<(model::MeshId,model::Mesh)>, prop_models:Vec<model::Model>, world_meshes:Vec<model::Mesh>, world_models:Vec<model::Model>, - modes:NormalizedModes, + first_stage:Option<Stage>, + teleports:HashMap<AddBrush,&'a str>, + teleport_destinations:HashMap<&'a str,model::ModelId>, } -impl PartialMap2{ +impl PartialMap2<'_>{ pub fn add_render_configs_and_textures( mut self, render_configs:RenderConfigs, @@ -526,22 +553,70 @@ impl PartialMap2{ //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>) + let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,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| + + // cull models with a missing mesh + let model_id_offset=self.world_models.len(); + let mut prop_model_id_to_final_model_id=HashMap::new(); + self.world_models.extend(self.prop_models.into_iter().enumerate().filter_map(|(prop_model_id,mut model)| prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{ model.mesh=new_mesh_id; - model + (prop_model_id,model) }) + ).enumerate().map(|(model_id,(prop_model_id,model))|{ + prop_model_id_to_final_model_id.insert( + model::ModelId::new(prop_model_id as u32), + model::ModelId::new((model_id_offset+model_id) as u32), + ); + model + })); + + //calculate teleports + let first_stage_spawn_model_id=self.first_stage.as_ref().unwrap().spawn(); + let mut teleport_destinations=HashMap::new(); + let stages={ + let mut stages=self.teleport_destinations.iter().map(|(&target,&model_id)|(target,model_id)).collect::<Vec<_>>(); + stages.sort_by_key(|&(target,_)|target); + self.first_stage.into_iter().chain( + stages.into_iter().enumerate().map(|(stage_id,(target,model_id))|{ + let stage_id=modes::StageId::new(1+stage_id as u32); + teleport_destinations.insert(target,stage_id); + Stage::empty(model_id) + }) + ).collect() + }; + let mut elements=HashMap::new(); + for (teleport_model,target) in self.teleports{ + if let Some(&stage_id)=teleport_destinations.get(target){ + let model_id=match teleport_model{ + AddBrush::Available(model_id)=>Some(model_id), + AddBrush::Deferred(model_id)=>prop_model_id_to_final_model_id.get(&model_id).copied(), + }; + if let Some(model_id)=model_id{ + elements.insert(model_id,modes::StageElement::new( + stage_id, + true, + modes::StageElementBehaviour::Teleport, + None, + )); + } + } + } + let main_mode=NormalizedMode::new(Mode::new( + strafesnet_common::gameplay_style::StyleModifiers::default(), + first_stage_spawn_model_id, + HashMap::new(), + stages, + elements, )); - //let mut models=Vec::new(); + let modes=NormalizedModes::new(vec![main_mode]); + let (textures,render_configs)=render_configs.consume(); - let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>) + let (textures,texture_id_map):(Vec<Vec<u8>>,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)))|{ @@ -555,7 +630,7 @@ impl PartialMap2{ render_config }).collect(); map::CompleteMap{ - modes:self.modes, + modes, attributes:self.attributes, meshes:self.world_meshes, models:self.world_models,