use std::io::Read; use std::collections::HashMap; use crate::newtypes; use crate::file::BlockId; use binrw::{binrw,BinReaderExt,BinWriterExt}; use strafesnet_common::model; use strafesnet_common::aabb::Aabb; use strafesnet_common::bvh::BvhNode; use strafesnet_common::gameplay_modes; #[derive(Debug)] pub enum Error{ InvalidHeader(binrw::Error), InvalidBlockId(BlockId), InvalidMeshId(model::MeshId), InvalidTextureId(model::TextureId), InvalidData(binrw::Error), IO(std::io::Error), File(crate::file::Error), } impl std::fmt::Display for Error{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ write!(f,"{self:?}") } } impl std::error::Error for Error{} /* block types BLOCK_MAP_HEADER: DefaultStyleInfo style_info //bvh goes here u32 num_nodes u32 num_spacial_blocks u32 num_resource_blocks u32 num_resources_external //node 0 parent node is implied to be None for node_id in 1..num_nodes{ u32 parent_node } //NOTE: alternate realities are not necessary. //portals/wormholes simply use an in-model and and out-model. //skyboxes are inverted models with a special shader. //ideally spacial blocks are sorted from distance to start zone //texture blocks are inserted before the first spacial block they are used in for spacial_block_id in 0..num_spacial_blocks{ u32 node_id u32 block_id //data block Aabb extents } //the order of these lists uniquely generates the incremental Ids //MeshId, TextureId etc. based on resource type //the first 8 bits of resource_uuid describe the type (mesh, texture, etc) //if the map file references only external resources, num_resource_blocks = 0 for resource_idx in 0..num_resource_blocks{ Resource resource_type u32 block_id } for resource_idx in 0..num_resources_external{ u128 resource_uuid } BLOCK_MAP_RESOURCE: Resource resource_type //an individual one of the following: - model (Mesh) - shader (compiled SPIR-V) - image (JpegXL) - sound (Opus) - video (AV1) - animation (Trey thing) BLOCK_MAP_REGION: u64 num_models for model_id in 0..num_models{ ModelInstance mode_instance } */ //if you hash the resource itself and set the first 8 bits to this, that's the resource uuid #[binrw] #[brw(little,repr=u8)] enum ResourceType{ Mesh, Texture, //Shader, //Sound, //Video, //Animation, } const RESOURCE_TYPE_VARIANT_COUNT:u8=2; #[binrw] #[brw(little)] struct ResourceId(u128); impl ResourceId{ fn resource_type(&self)->Option{ let discriminant=self.0 as u8; //TODO: use this when it is stabilized https://github.com/rust-lang/rust/issues/73662 //if (discriminant as usize)(){ match discriminantSome(unsafe{std::mem::transmute::(discriminant)}), false=>None, } } } struct ResourceMap{ meshes:HashMap, textures:HashMap, } impl Default for ResourceMap{ fn default()->Self{ Self{ meshes:HashMap::new(), textures:HashMap::new(), } } } #[binrw] #[brw(little)] struct SpacialBlockHeader{ id:BlockId, extents:newtypes::aabb::Aabb, } #[binrw] #[brw(little)] struct ResourceBlockHeader{ resource:ResourceType, id:BlockId, } #[binrw] #[brw(little)] struct ResourceExternalHeader{ resource_uuid:ResourceId, } #[binrw] #[brw(little)] struct MapHeader{ num_spacial_blocks:u32, num_resource_blocks:u32, //num_resources_external:u32, num_modes:u32, num_attributes:u32, num_render_configs:u32, #[br(count=num_spacial_blocks)] spacial_blocks:Vec, #[br(count=num_resource_blocks)] resource_blocks:Vec, //#[br(count=num_resources_external)] //external_resources:Vec, #[br(count=num_modes)] modes:Vec, #[br(count=num_attributes)] attributes:Vec, #[br(count=num_render_configs)] render_configs:Vec, } #[binrw] #[brw(little)] struct Region{ //consider including a bvh in the region instead of needing to rebalance the physics bvh on the fly model_count:u32, #[br(count=model_count)] models:Vec, } //code deduplication reused in into_complete_map fn read_region(file:&mut crate::file::File,block_id:BlockId)->Result,Error>{ //load region from disk //parse the models and determine what resources need to be loaded //load resources into self.resources //return Region let mut block=file.block_reader(block_id).map_err(Error::File)?; let region:Region=block.read_le().map_err(Error::InvalidData)?; Ok(region.models.into_iter().map(Into::into).collect()) } fn read_mesh(file:&mut crate::file::File,block_id:BlockId)->Result{ let mut block=file.block_reader(block_id).map_err(Error::File)?; let mesh:newtypes::model::Mesh=block.read_le().map_err(Error::InvalidData)?; Ok(mesh.into()) } fn read_texture(file:&mut crate::file::File,block_id:BlockId)->Result,Error>{ let mut block=file.block_reader(block_id).map_err(Error::File)?; let mut texture=Vec::new(); block.read_to_end(&mut texture).map_err(Error::IO)?; Ok(texture) } pub struct StreamableMap{ file:crate::file::File, //this includes every platform... move the unconstrained datas to their appropriate data block? modes:gameplay_modes::Modes, //this is every possible attribute... need some sort of streaming system attributes:Vec, //this is every possible render configuration... shaders and such... need streaming render_configs:Vec, //this makes sense to keep in memory for streaming, a map of which blocks occupy what space bvh:BvhNode, //something something resources hashmaps resource_blocks:ResourceMap, //resource_external:ResourceMap, } impl StreamableMap{ pub(crate) fn new(mut file:crate::file::File)->Result{ //assume the file seek is in the right place to start reading a map header let header:MapHeader=file.as_mut().read_le().map_err(Error::InvalidHeader)?; let modes=header.modes.into_iter().map(Into::into).collect(); let attributes=header.attributes.into_iter().map(Into::into).collect(); let render_configs=header.render_configs.into_iter().map(Into::into).collect(); let bvh=header.spacial_blocks.into_iter().map(|spacial_block| (spacial_block.id,spacial_block.extents.into()) ).collect(); //generate mesh ids and texture ids from resource list order let mut resource_blocks=ResourceMap::default(); for resource_block_header in header.resource_blocks{ match resource_block_header.resource{ ResourceType::Mesh=>{ resource_blocks.meshes.insert( //generate the id from the current length model::MeshId::new(resource_blocks.meshes.len() as u32), resource_block_header.id ); }, ResourceType::Texture=>{ resource_blocks.textures.insert( model::TextureId::new(resource_blocks.textures.len() as u32), resource_block_header.id ); }, } } Ok(Self{ file, modes:strafesnet_common::gameplay_modes::Modes::new(modes), attributes, render_configs, bvh:strafesnet_common::bvh::generate_bvh(bvh), resource_blocks, //resource_external:Default::default(), }) } pub fn get_intersecting_region_block_ids(&self,aabb:&Aabb)->Vec{ let mut block_ids=Vec::new(); self.bvh.the_tester(aabb,&mut |&block_id|block_ids.push(block_id)); block_ids } pub fn load_region(&mut self,block_id:BlockId)->Result,Error>{ read_region(&mut self.file,block_id) } pub fn load_mesh(&mut self,mesh_id:model::MeshId)->Result{ let block_id=*self.resource_blocks.meshes.get(&mesh_id).ok_or(Error::InvalidMeshId(mesh_id))?; read_mesh(&mut self.file,block_id) } pub fn load_texture(&mut self,texture_id:model::TextureId)->Result,Error>{ let block_id=*self.resource_blocks.textures.get(&texture_id).ok_or(Error::InvalidTextureId(texture_id))?; read_texture(&mut self.file,block_id) } pub fn into_complete_map(mut self)->Result{ //load all meshes let mut meshes=Vec::with_capacity(self.resource_blocks.meshes.len()); for mesh_id in 0..self.resource_blocks.meshes.len() as u32{ let mesh_id=model::MeshId::new(mesh_id); let block_id=self.resource_blocks.meshes[&mesh_id]; meshes.push(read_mesh(&mut self.file,block_id)?); }; //load all textures let mut textures=Vec::with_capacity(self.resource_blocks.textures.len()); for texture_id in 0..self.resource_blocks.textures.len() as u32{ let texture_id=model::TextureId::new(texture_id); let block_id=self.resource_blocks.textures[&texture_id]; textures.push(read_texture(&mut self.file,block_id)?); } let mut block_ids=Vec::new(); self.bvh.into_visitor(&mut |block_id|block_ids.push(block_id)); //load all regions let mut models=Vec::new(); for block_id in block_ids{ models.append(&mut read_region(&mut self.file,block_id)?); } Ok(strafesnet_common::map::CompleteMap{ modes:self.modes, attributes:self.attributes, meshes, models, textures, render_configs:self.render_configs, }) } } const BVH_NODE_MAX_WEIGHT:usize=64*1024;//64 kB fn collect_spacial_blocks( block_location:&mut Vec, block_headers:&mut Vec, sequential_block_data:&mut std::io::Cursor<&mut Vec>, bvh_node:strafesnet_common::bvh::BvhWeightNode )->Result<(),Error>{ //inspect the node weights top-down. //When a node weighs less than the limit, //serialize its entire contents into a region block if *bvh_node.weight(){ for bvh_node in bvh_node_list{ collect_spacial_blocks(block_location,block_headers,sequential_block_data,bvh_node)?; } }, strafesnet_common::bvh::RecursiveContent::Leaf(_)=>panic!(),//bvh branches are 20 leaves minimum } } Ok(()) } /// TODO: Optionally provide a bot that describes the path through the map /// otherwise sort by distance to start zone pub fn write_map(mut writer:W,map:strafesnet_common::map::CompleteMap)->Result<(),Error>{ //serialize models and make a bvh that knows the file size of the branch let boxen=map.models.into_iter().map(|model|{ //grow your own aabb let mesh=map.meshes.get(model.mesh.get() as usize).ok_or(Error::InvalidMeshId(model.mesh))?; let mut aabb=strafesnet_common::aabb::Aabb::default(); for &pos in &mesh.unique_pos{ aabb.grow(model.transform.transform_point3(pos)); } Ok((model.into(),aabb)) }).collect::,_>>()?; let bvh=strafesnet_common::bvh::generate_bvh(boxen).weigh_contents(&|_|std::mem::size_of::()); //build blocks //block location is initialized with two values //the first value represents the location of the first byte after the file header //the second value represents the first byte of the second data block //the first data block is always the map header, so the difference is the map header size. //this information is filled in later after the sizes are known. let mut block_location=vec![0,0];//for file header let mut spacial_blocks=Vec::new();//for map header let mut sequential_block_data=Vec::new(); let mut cursor_to_data=std::io::Cursor::new(&mut sequential_block_data); collect_spacial_blocks(&mut block_location,&mut spacial_blocks,&mut cursor_to_data,bvh)?; let mut block_count=spacial_blocks.len() as u32+1;//continue block id let mut resource_blocks=Vec::new();//for map header //meshes for mesh in map.meshes.into_iter(){ resource_blocks.push(ResourceBlockHeader{ resource:ResourceType::Mesh, id:BlockId::new(block_count), }); block_count+=1; let serializable_mesh:newtypes::model::Mesh=mesh.into(); binrw::BinWrite::write_le(&serializable_mesh,&mut cursor_to_data).map_err(Error::InvalidData)?; block_location.push(cursor_to_data.position()); } //textures for mut texture in map.textures.into_iter(){ resource_blocks.push(ResourceBlockHeader{ resource:ResourceType::Texture, id:BlockId::new(block_count), }); block_count+=1; sequential_block_data.append(&mut texture); block_location.push(sequential_block_data.len() as u64); } //build header let map_header=MapHeader{ num_spacial_blocks:spacial_blocks.len() as u32, num_resource_blocks:resource_blocks.len() as u32, //num_resources_external:0, num_modes:map.modes.modes.len() as u32, num_attributes:map.attributes.len() as u32, num_render_configs:map.render_configs.len() as u32, spacial_blocks, resource_blocks, //external_resources:Vec::new(), modes:map.modes.modes.into_iter().map(Into::into).collect(), attributes:map.attributes.into_iter().map(Into::into).collect(), render_configs:map.render_configs.into_iter().map(Into::into).collect(), }; let mut file_header=crate::file::Header{ fourcc:crate::file::FourCC::Map, version:0, priming:0, resource:0, block_count, block_location, }; //probe header length let mut file_header_data=Vec::new(); binrw::BinWrite::write_le(&file_header,&mut std::io::Cursor::new(&mut file_header_data)).map_err(Error::InvalidData)?; let mut map_header_data=Vec::new(); binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?; //update file header according to probe data let mut offset=file_header_data.len() as u64; file_header.priming=offset; file_header.block_location[0]=offset; offset+=map_header_data.len() as u64; for position in &mut file_header.block_location[1..]{ *position+=offset; } //write (updated) file header writer.write_le(&file_header).map_err(Error::InvalidData)?; //write map header writer.write(&map_header_data).map_err(Error::IO)?; //write blocks writer.write(&sequential_block_data).map_err(Error::IO)?; Ok(()) }