use std::borrow::Cow; use vbsp_entities_css::Entity; 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 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, }) } 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:attr::CollisionAttributesId, debug_info:model::DebugInfo, ){ 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,debug_info} ); }, 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,debug_info} ); } } } 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 unique_attributes=vec![ attr::CollisionAttributes::Decoration, attr::CollisionAttributes::contact_default(), attr::CollisionAttributes::intersect_default(), // ladder attr::CollisionAttributes::Contact( attr::ContactAttributes{ contacting:attr::ContactingAttributes{ contact_behaviour:Some(attr::ContactingBehaviour::Ladder( attr::ContactingLadder{sticky:true} )) }, general:attr::GeneralAttributes::default(), } ), // water attr::CollisionAttributes::Intersect( attr::IntersectAttributes{ intersecting:attr::IntersectingAttributes{ water:Some(attr::IntersectingWater{ viscosity:integer::Planar64::ONE, density:integer::Planar64::ONE, velocity:integer::vec3::ZERO, }), }, general:attr::GeneralAttributes::default(), } ), ]; const ATTRIBUTE_DECORATION:attr::CollisionAttributesId=attr::CollisionAttributesId::new(0); const ATTRIBUTE_CONTACT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(1); const ATTRIBUTE_INTERSECT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(2); const ATTRIBUTE_LADDER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(3); const ATTRIBUTE_WATER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(4); //declare all prop models to Loader let mut prop_models=bsp.static_props().map(|prop|{ const DEG_TO_RAD:f32=std::f32::consts::TAU/360.0; //get or create mesh_id let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model()); model::Model{ mesh:mesh_id, attributes:ATTRIBUTE_DECORATION, transform:integer::Planar64Affine3::new( integer::mat3::try_from_f32_array_2d( //TODO: figure this out glam::Mat3A::from_euler( glam::EulerRot::XYZ, prop.angles.pitch*DEG_TO_RAD, prop.angles.yaw*DEG_TO_RAD, prop.angles.roll*DEG_TO_RAD ).to_cols_array_2d() ).unwrap(), valve_transform(prop.origin.into()), ), color:glam::Vec4::ONE, debug_info:model::DebugInfo::Prop, } }).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 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); 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 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, debug_info:model::DebugInfo::World, }); // THE CUBE OF DESTINY let destination_mesh_id=model::MeshId::new(world_meshes.len() as u32); world_meshes.push(crate::brush::unit_cube()); 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{ let debug_info=match model::EntityInfo::new(raw_ent.properties()){ Ok(entity_info)=>model::DebugInfo::Entity(entity_info), Err(_)=>{ println!("EntityInfoError"); model::DebugInfo::World }, }; 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,debug_info) }; } 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,debug_info) }; } 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,debug_info) }; } match raw_ent.parse(){ Ok(Entity::AmbientGeneric(_ambient_generic))=>(), Ok(Entity::Cycler(brush))=>ent_brush_default!(brush), Ok(Entity::EnvBeam(_env_beam))=>(), Ok(Entity::EnvBubbles(_env_bubbles))=>(), Ok(Entity::EnvDetailController(_env_detail_controller))=>(), Ok(Entity::EnvEmbers(_env_embers))=>(), Ok(Entity::EnvEntityMaker(_env_entity_maker))=>(), Ok(Entity::EnvExplosion(_env_explosion))=>(), Ok(Entity::EnvFade(_env_fade))=>(), Ok(Entity::EnvFire(_env_fire))=>(), Ok(Entity::EnvFireTrail(_env_fire_trail))=>(), Ok(Entity::EnvFiresource(_env_firesource))=>(), Ok(Entity::EnvFogController(_env_fog_controller))=>(), Ok(Entity::EnvHudhint(_env_hudhint))=>(), Ok(Entity::EnvLaser(_env_laser))=>(), Ok(Entity::EnvLightglow(_env_lightglow))=>(), Ok(Entity::EnvPhysexplosion(_env_physexplosion))=>(), Ok(Entity::EnvProjectedtexture(_env_projectedtexture))=>(), Ok(Entity::EnvScreenoverlay(_env_screenoverlay))=>(), Ok(Entity::EnvShake(_env_shake))=>(), Ok(Entity::EnvShooter(_env_shooter))=>(), Ok(Entity::EnvSmokestack(_env_smokestack))=>(), Ok(Entity::EnvSoundscape(_env_soundscape))=>(), Ok(Entity::EnvSoundscapeProxy(_env_soundscape_proxy))=>(), Ok(Entity::EnvSoundscapeTriggerable(_env_soundscape_triggerable))=>(), Ok(Entity::EnvSpark(_env_spark))=>(), Ok(Entity::EnvSprite(brush))=>ent_brush_default!(brush), Ok(Entity::EnvSpritetrail(_env_spritetrail))=>(), Ok(Entity::EnvSteam(_env_steam))=>(), Ok(Entity::EnvSun(_env_sun))=>(), Ok(Entity::EnvTonemapController(_env_tonemap_controller))=>(), Ok(Entity::EnvWind(_env_wind))=>(), // trigger_teleport.filtername probably has to do with one of these Ok(Entity::FilterActivatorClass(_filter_activator_class))=>(), Ok(Entity::FilterActivatorName(_filter_activator_name))=>(), Ok(Entity::FilterDamageType(_filter_damage_type))=>(), Ok(Entity::FilterMulti(_filter_multi))=>(), Ok(Entity::FuncAreaportal(_func_areaportal))=>(), Ok(Entity::FuncAreaportalwindow(_func_areaportalwindow))=>(), Ok(Entity::FuncBombTarget(_func_bomb_target))=>(), Ok(Entity::FuncBreakable(brush))=>ent_brush_default!(brush), Ok(Entity::FuncBreakableSurf(_func_breakable_surf))=>(), Ok(Entity::FuncBrush(brush))=>ent_brush_default!(brush), Ok(Entity::FuncButton(brush))=>ent_brush_default!(brush), Ok(Entity::FuncBuyzone(_func_buyzone))=>(), Ok(Entity::FuncClipVphysics(_func_clip_vphysics))=>(), Ok(Entity::FuncConveyor(_func_conveyor))=>(), // FuncDoor is Platform Ok(Entity::FuncDoor(brush))=>ent_brush_default!(brush), Ok(Entity::FuncDoorRotating(brush))=>ent_brush_default!(brush), Ok(Entity::FuncDustcloud(_func_dustcloud))=>(), Ok(Entity::FuncDustmotes(_func_dustmotes))=>(), 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,debug_info), Ok(Entity::FuncLod(_func_lod))=>(), Ok(Entity::FuncMonitor(brush))=>ent_brush_default!(brush), Ok(Entity::FuncMovelinear(brush))=>ent_brush_default!(brush), Ok(Entity::FuncOccluder(_func_occluder))=>(), Ok(Entity::FuncPhysbox(brush))=>ent_brush_default!(brush), Ok(Entity::FuncPhysboxMultiplayer(brush))=>ent_brush_default!(brush), Ok(Entity::FuncPrecipitation(_func_precipitation))=>(), Ok(Entity::FuncRotButton(brush))=>ent_brush_prop!(brush), Ok(Entity::FuncRotating(brush))=>ent_brush_default!(brush), 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,debug_info), 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,debug_info), 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,debug_info), Ok(Entity::GamePlayerEquip(_game_player_equip))=>(), Ok(Entity::GameText(_game_text))=>(), Ok(Entity::GameUi(_game_ui))=>(), Ok(Entity::GameWeaponManager(_game_weapon_manager))=>(), Ok(Entity::HostageEntity(_hostage_entity))=>(), Ok(Entity::InfoCameraLink(_info_camera_link))=>(), Ok(Entity::InfoLadder(_info_ladder))=>(), Ok(Entity::InfoLightingRelative(_info_lighting_relative))=>(), Ok(Entity::InfoMapParameters(_info_map_parameters))=>(), Ok(Entity::InfoNode(_info_node))=>(), Ok(Entity::InfoNodeHint(_info_node_hint))=>(), Ok(Entity::InfoParticleSystem(_info_particle_system))=>(), Ok(Entity::InfoPlayerCounterterrorist(spawn))=>found_spawn=Some(spawn.origin), Ok(Entity::InfoPlayerLogo(_info_player_logo))=>(), Ok(Entity::InfoPlayerStart(_info_player_start))=>(), 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::Infodecal(_infodecal))=>(), Ok(Entity::KeyframeRope(_keyframe_rope))=>(), Ok(Entity::Light(_light))=>(), Ok(Entity::LightEnvironment(_light_environment))=>(), Ok(Entity::LightSpot(_light_spot))=>(), Ok(Entity::LogicAuto(_logic_auto))=>(), Ok(Entity::LogicBranch(_logic_branch))=>(), Ok(Entity::LogicCase(_logic_case))=>(), Ok(Entity::LogicCompare(_logic_compare))=>(), Ok(Entity::LogicMeasureMovement(_logic_measure_movement))=>(), Ok(Entity::LogicRelay(_logic_relay))=>(), Ok(Entity::LogicTimer(_logic_timer))=>(), Ok(Entity::MathCounter(_math_counter))=>(), Ok(Entity::MoveRope(_move_rope))=>(), Ok(Entity::PathTrack(_path_track))=>(), Ok(Entity::PhysBallsocket(_phys_ballsocket))=>(), Ok(Entity::PhysConstraint(_phys_constraint))=>(), Ok(Entity::PhysConstraintsystem(_phys_constraintsystem))=>(), Ok(Entity::PhysHinge(_phys_hinge))=>(), Ok(Entity::PhysKeepupright(_phys_keepupright))=>(), Ok(Entity::PhysLengthconstraint(_phys_lengthconstraint))=>(), Ok(Entity::PhysPulleyconstraint(_phys_pulleyconstraint))=>(), Ok(Entity::PhysRagdollconstraint(_phys_ragdollconstraint))=>(), Ok(Entity::PhysRagdollmagnet(_phys_ragdollmagnet))=>(), Ok(Entity::PhysThruster(_phys_thruster))=>(), Ok(Entity::PhysTorque(_phys_torque))=>(), Ok(Entity::PlayerSpeedmod(_player_speedmod))=>(), Ok(Entity::PlayerWeaponstrip(_player_weaponstrip))=>(), Ok(Entity::PointCamera(_point_camera))=>(), Ok(Entity::PointClientcommand(_point_clientcommand))=>(), Ok(Entity::PointDevshotCamera(_point_devshot_camera))=>(), Ok(Entity::PointServercommand(_point_servercommand))=>(), Ok(Entity::PointSpotlight(_point_spotlight))=>(), Ok(Entity::PointSurroundtest(_point_surroundtest))=>(), Ok(Entity::PointTemplate(_point_template))=>(), Ok(Entity::PointTesla(_point_tesla))=>(), Ok(Entity::PointViewcontrol(_point_viewcontrol))=>(), Ok(Entity::PropDoorRotating(brush))=>ent_brush_prop!(brush), Ok(Entity::PropDynamic(brush))=>ent_brush_prop!(brush), Ok(Entity::PropDynamicOverride(brush))=>ent_brush_prop!(brush), Ok(Entity::PropPhysics(brush))=>ent_brush_prop!(brush), Ok(Entity::PropPhysicsMultiplayer(brush))=>ent_brush_prop!(brush), Ok(Entity::PropPhysicsOverride(brush))=>ent_brush_prop!(brush), Ok(Entity::PropRagdoll(brush))=>ent_brush_prop!(brush), Ok(Entity::ShadowControl(_shadow_control))=>(), Ok(Entity::SkyCamera(_sky_camera))=>(), 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,debug_info), 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,debug_info), Ok(Entity::TriggerVphysicsMotion(brush))=>ent_brush_trigger!(brush), Ok(Entity::TriggerWind(brush))=>ent_brush_trigger!(brush), Ok(Entity::WaterLodControl(_water_lod_control))=>(), Ok(Entity::WeaponAk47(_weapon_ak47))=>(), Ok(Entity::WeaponAwp(_weapon_awp))=>(), Ok(Entity::WeaponDeagle(_weapon_deagle))=>(), Ok(Entity::WeaponElite(_weapon_elite))=>(), Ok(Entity::WeaponFamas(_weapon_famas))=>(), Ok(Entity::WeaponFiveseven(_weapon_fiveseven))=>(), Ok(Entity::WeaponFlashbang(_weapon_flashbang))=>(), Ok(Entity::WeaponG3sg1(_weapon_g3sg1))=>(), Ok(Entity::WeaponGlock(_weapon_glock))=>(), Ok(Entity::WeaponHegrenade(_weapon_hegrenade))=>(), Ok(Entity::WeaponKnife(_weapon_knife))=>(), Ok(Entity::WeaponM249(_weapon_m249))=>(), Ok(Entity::WeaponM3(_weapon_m3))=>(), Ok(Entity::WeaponM4a1(_weapon_m4a1))=>(), Ok(Entity::WeaponMac10(_weapon_mac10))=>(), Ok(Entity::WeaponP228(_weapon_p228))=>(), Ok(Entity::WeaponP90(_weapon_p90))=>(), Ok(Entity::WeaponScout(_weapon_scout))=>(), Ok(Entity::WeaponSg550(_weapon_sg550))=>(), Ok(Entity::WeaponSmokegrenade(_weapon_smokegrenade))=>(), Ok(Entity::WeaponTmp(_weapon_tmp))=>(), Ok(Entity::WeaponUmp45(_weapon_ump45))=>(), Ok(Entity::WeaponUsp(_weapon_usp))=>(), Ok(Entity::WeaponXm1014(_weapon_xm1014))=>(), Ok(Entity::Worldspawn(_worldspawn))=>(), Err(e)=>{ println!("Bsp Entity parse error: {e}"); }, _=>(), } } // physics models for brush in &bsp.brushes{ const RELEVANT:vbsp::BrushFlags= vbsp::BrushFlags::SOLID .union(vbsp::BrushFlags::PLAYERCLIP) .union(vbsp::BrushFlags::WATER) .union(vbsp::BrushFlags::MOVEABLE) .union(vbsp::BrushFlags::LADDER); if !brush.flags.intersects(RELEVANT){ continue; } let is_ladder=brush.flags.contains(vbsp::BrushFlags::LADDER); let is_water=brush.flags.contains(vbsp::BrushFlags::WATER); let attributes=match (is_ladder,is_water){ (true,false)=>ATTRIBUTE_LADDER_DEFAULT, (false,true)=>ATTRIBUTE_WATER_DEFAULT, (false,false)=>ATTRIBUTE_CONTACT_DEFAULT, (true,true)=>{ // water ladder? wtf println!("brush is a water ladder o_o defaulting to ladder"); ATTRIBUTE_LADDER_DEFAULT } }; 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); let sides={ let brush_start_idx=brush.brush_side as usize; let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize; bsp.brush_sides[sides_range].iter().filter_map(|side|bsp.texture_info(side.texture_info as usize)).map(|texture_info|{ texture_info.flags }).collect() }; world_models.push(model::Model{ mesh:mesh_id, attributes, transform:integer::Planar64Affine3::new( integer::mat3::identity(), integer::vec3::ZERO, ), color:glam::Vec4::ONE, debug_info:model::DebugInfo::Brush(model::BrushInfo{flags:brush.flags,sides}), }); }, Err(e)=>println!("Brush mesh error: {e}"), } } let mut modes_list=Vec::new(); if let Some(spawn_point)=found_spawn{ // 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(spawn_point.into())), color:glam::Vec4::W, debug_info:model::DebugInfo::World, }); 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)); } PartialMap1{ attributes:unique_attributes, world_meshes, prop_models, world_models, modes:NormalizedModes::new(modes_list), } } //partially constructed map types pub struct PartialMap1{ attributes:Vec<attr::CollisionAttributes>, prop_models:Vec<model::Model>, world_meshes:Vec<model::Mesh>, world_models:Vec<model::Model>, modes:NormalizedModes, } 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<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, } 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, } } }