bsp_loader: triggers

This commit is contained in:
Quaternions 2025-03-10 13:32:45 -07:00
parent 70fb970582
commit 51d7d75e9a

@ -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,67 @@ 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 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);
stages.into_iter().enumerate().map(|(stage_id,(target,model_id))|{
let stage_id=modes::StageId::new(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,
false,
modes::StageElementBehaviour::Teleport,
None,
));
}
}
}
let main_mode=NormalizedMode::new(Mode::new(
strafesnet_common::gameplay_style::StyleModifiers::default(),
self.first_stage.unwrap().spawn(),
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 +627,7 @@ impl PartialMap2{
render_config
}).collect();
map::CompleteMap{
modes:self.modes,
modes,
attributes:self.attributes,
meshes:self.world_meshes,
models:self.world_models,