diff options
author | Lizzy Fleckenstein <eliasfleckenstein@web.de> | 2023-02-28 18:14:06 +0100 |
---|---|---|
committer | Lizzy Fleckenstein <eliasfleckenstein@web.de> | 2023-02-28 18:14:06 +0100 |
commit | a87186860ec602c19f0d11528bef2d5123bc7e48 (patch) | |
tree | 1a5bfc0b554cd0ed195b61b1bc19f2dd250f8c8c /src/gfx/map.rs | |
parent | 146702340fbad28f9146d75e298234f63c0d5033 (diff) | |
download | mt_client-a87186860ec602c19f0d11528bef2d5123bc7e48.tar.xz |
Basic map rendering
Diffstat (limited to 'src/gfx/map.rs')
-rw-r--r-- | src/gfx/map.rs | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/src/gfx/map.rs b/src/gfx/map.rs new file mode 100644 index 0000000..d95a4d2 --- /dev/null +++ b/src/gfx/map.rs @@ -0,0 +1,447 @@ +use super::{media::MediaMgr, state::State, util::MatrixUniform}; +use cgmath::{prelude::*, Matrix4, Point3, Vector3}; +use mt_net::{MapBlock, NodeDef}; +use rand::Rng; +use std::{collections::HashMap, ops::Range}; +use wgpu::util::DeviceExt; + +pub struct MapRender { + pipeline: wgpu::RenderPipeline, + textures: HashMap<String, [Range<f32>; 2]>, + nodes: HashMap<u16, NodeDef>, + atlas: wgpu::BindGroup, + model: wgpu::BindGroupLayout, + blocks: HashMap<[i16; 3], BlockMesh>, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct Vertex { + pos: [f32; 3], + tex_coords: [f32; 2], +} + +impl Vertex { + const ATTRIBS: [wgpu::VertexAttribute; 2] = + wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2]; + + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBS, + } + } +} + +struct BlockMesh { + vertex_buffer: wgpu::Buffer, + num_vertices: u32, + model: MatrixUniform, +} + +impl MapRender { + pub fn render<'a>(&'a self, state: &'a State, pass: &mut wgpu::RenderPass<'a>) { + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.atlas, &[]); + pass.set_bind_group(1, &state.camera_uniform.bind_group, &[]); + + for mesh in self.blocks.values() { + pass.set_bind_group(2, &mesh.model.bind_group, &[]); + pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); + pass.draw(0..mesh.num_vertices, 0..1); + } + } + + pub fn add_block(&mut self, state: &mut State, pos: Point3<i16>, block: Box<MapBlock>) { + let mut vertices = Vec::with_capacity(10000); + for (index, content) in block.param_0.iter().enumerate() { + let def = match self.nodes.get(content) { + Some(x) => x, + None => continue, + }; + + use lerp::Lerp; + use mt_net::DrawType; + use std::array::from_fn as array; + + match def.draw_type { + DrawType::Cube => { + let pos: [i16; 3] = array(|i| ((index >> (4 * i)) & 0xf) as i16); + for (f, face) in CUBE.iter().enumerate() { + let dir = FACE_DIR[f]; + let npos: [i16; 3] = array(|i| dir[i] + pos[i]); + if npos.iter().all(|x| (0..16).contains(x)) { + let nindex = npos[0] | (npos[1] << 4) | (npos[2] << 8); + + if let Some(ndef) = self.nodes.get(&block.param_0[nindex as usize]) { + if ndef.draw_type == DrawType::Cube { + continue; + } + } + } + + let tile = &def.tiles[f]; + let rect = self.textures.get(&tile.texture).unwrap(); + + for vertex in face.iter() { + /*println!( + "{:?} {:?} {:?} {:?}", + (vertex.1[0], vertex.1[1]), + (rect[0].start, rect[1].start), + (rect[0].end, rect[1].end), + ( + vertex.1[0].lerp(rect[0].start, rect[0].end), + vertex.1[1].lerp(rect[1].start, rect[1].end) + ) + );*/ + vertices.push(Vertex { + pos: array(|i| pos[i] as f32 - 8.5 + vertex.0[i]), + tex_coords: array(|i| rect[i].start.lerp(rect[i].end, vertex.1[i])), + }) + } + } + } + DrawType::None => {} + _ => { + // TODO + } + } + } + + self.blocks.insert( + pos.into(), + BlockMesh { + vertex_buffer: state + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("mapblock.vertex_buffer"), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }), + num_vertices: vertices.len() as u32, + model: MatrixUniform::new( + &state.device, + &self.model, + Matrix4::from_translation( + pos.cast::<f32>().unwrap().to_vec() * 16.0 + Vector3::new(8.5, 8.5, 8.5), + ), + "mapblock", + false, + ), + }, + ); + } + + pub fn new(state: &mut State, media: &MediaMgr, nodes: HashMap<u16, NodeDef>) -> Self { + let mut rng = rand::thread_rng(); + let mut atlas_map = HashMap::new(); + let mut atlas_alloc = guillotiere::SimpleAtlasAllocator::new(guillotiere::size2(1, 1)); + + for node in nodes.values() { + let tiles = node + .tiles + .iter() + .chain(node.overlay_tiles.iter()) + .chain(node.special_tiles.iter()); + + let load_texture = |texture: &str| { + let payload = media + .get(texture) + .ok_or_else(|| format!("texture not found: {texture}"))?; + + image::load_from_memory(payload) + .or_else(|_| { + image::load_from_memory_with_format(payload, image::ImageFormat::Tga) + }) + .map_err(|e| format!("failed to load texture {texture}: {e}")) + .map(|x| image::imageops::flip_vertical(&x)) + }; + + let mut make_texture = |texture: &str| { + texture + .split('^') + .map(|part| match load_texture(part) { + Ok(v) => v, + Err(e) => { + if !texture.is_empty() && !texture.contains('[') { + eprintln!("{e}"); + } + + let mut img = image::RgbImage::new(1, 1); + rng.fill(&mut img.get_pixel_mut(0, 0).0); + + image::DynamicImage::from(img).to_rgba8() + } + }) + .reduce(|mut base, top| { + image::imageops::overlay(&mut base, &top, 0, 0); + base + }) + .unwrap() + }; + + for tile in tiles { + atlas_map.entry(tile.texture.clone()).or_insert_with(|| { + let img = make_texture(&tile.texture); + + let dimensions = img.dimensions(); + let size = guillotiere::size2(dimensions.0 as i32, dimensions.1 as i32); + + loop { + match atlas_alloc.allocate(size) { + None => { + let mut atlas_size = atlas_alloc.size(); + atlas_size.width *= 2; + atlas_size.height *= 2; + atlas_alloc.grow(atlas_size); + } + Some(v) => return (img, v), + } + } + }); + } + } + + let atlas_size = atlas_alloc.size(); + let mut atlas = image::RgbaImage::new(atlas_size.width as u32, atlas_size.height as u32); + + let textures = atlas_map + .into_iter() + .map(|(name, (img, rect))| { + let w = atlas_size.width as f32; + let h = atlas_size.height as f32; + + let x = (rect.min.x as f32 / w)..(rect.max.x as f32 / w); + let y = (rect.min.y as f32 / h)..(rect.max.y as f32 / h); + + use image::GenericImage; + atlas + .copy_from(&img, rect.min.x as u32, rect.min.y as u32) + .unwrap(); + + (name, [x, y]) + }) + .collect(); + + let size = wgpu::Extent3d { + width: atlas_size.width as u32, + height: atlas_size.height as u32, + depth_or_array_layers: 1, + }; + + let atlas_texture = state.device.create_texture(&wgpu::TextureDescriptor { + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + label: Some("tile_atlas"), + view_formats: &[], + }); + + state.queue.write_texture( + wgpu::ImageCopyTexture { + texture: &atlas_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + &atlas, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: std::num::NonZeroU32::new(4 * atlas_size.width as u32), + rows_per_image: std::num::NonZeroU32::new(atlas_size.height as u32), + }, + size, + ); + + let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let atlas_sampler = state.device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + // "We've got you surrounded, stop using Nearest filter" + // - "I hate bilinear filtering I hate bilinear filtering I hate bilinear filtering" + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let atlas_bind_group_layout = + state + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("atlas.bind_group_layout"), + }); + + let atlas_bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &atlas_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&atlas_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&atlas_sampler), + }, + ], + label: Some("atlas.bind_group"), + }); + + let model_bind_group_layout = MatrixUniform::layout(&state.device, "mapblock"); + + let shader = state + .device + .create_shader_module(wgpu::include_wgsl!("../../assets/shaders/map.wgsl")); + + let pipeline_layout = + state + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[ + &atlas_bind_group_layout, + &model_bind_group_layout, + &state.camera_bind_group_layout, + ], + push_constant_ranges: &[], + }); + + let pipeline = state + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[Vertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: state.config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + + Self { + pipeline, + nodes, + textures, + atlas: atlas_bind_group, + model: model_bind_group_layout, + blocks: HashMap::new(), + } + } +} + +#[rustfmt::skip] +const CUBE: [[([f32; 3], [f32; 2]); 6]; 6] = [ + [ + ([-0.5, 0.5, -0.5], [ 0.0, 1.0]), + ([ 0.5, 0.5, 0.5], [ 1.0, 0.0]), + ([ 0.5, 0.5, -0.5], [ 1.0, 1.0]), + ([ 0.5, 0.5, 0.5], [ 1.0, 0.0]), + ([-0.5, 0.5, -0.5], [ 0.0, 1.0]), + ([-0.5, 0.5, 0.5], [ 0.0, 0.0]), + ], + [ + ([-0.5, -0.5, -0.5], [ 0.0, 1.0]), + ([ 0.5, -0.5, -0.5], [ 1.0, 1.0]), + ([ 0.5, -0.5, 0.5], [ 1.0, 0.0]), + ([ 0.5, -0.5, 0.5], [ 1.0, 0.0]), + ([-0.5, -0.5, 0.5], [ 0.0, 0.0]), + ([-0.5, -0.5, -0.5], [ 0.0, 1.0]), + ], + [ + ([ 0.5, 0.5, 0.5], [ 1.0, 1.0]), + ([ 0.5, -0.5, -0.5], [ 0.0, 0.0]), + ([ 0.5, 0.5, -0.5], [ 0.0, 1.0]), + ([ 0.5, -0.5, -0.5], [ 0.0, 0.0]), + ([ 0.5, 0.5, 0.5], [ 1.0, 1.0]), + ([ 0.5, -0.5, 0.5], [ 1.0, 0.0]), + ], + [ + ([-0.5, 0.5, 0.5], [ 1.0, 1.0]), + ([-0.5, 0.5, -0.5], [ 0.0, 1.0]), + ([-0.5, -0.5, -0.5], [ 0.0, 0.0]), + ([-0.5, -0.5, -0.5], [ 0.0, 0.0]), + ([-0.5, -0.5, 0.5], [ 1.0, 0.0]), + ([-0.5, 0.5, 0.5], [ 1.0, 1.0]), + ], + [ + ([-0.5, -0.5, 0.5], [ 0.0, 0.0]), + ([ 0.5, -0.5, 0.5], [ 1.0, 0.0]), + ([ 0.5, 0.5, 0.5], [ 1.0, 1.0]), + ([ 0.5, 0.5, 0.5], [ 1.0, 1.0]), + ([-0.5, 0.5, 0.5], [ 0.0, 1.0]), + ([-0.5, -0.5, 0.5], [ 0.0, 0.0]), + ], + [ + ([-0.5, -0.5, -0.5], [ 0.0, 0.0]), + ([ 0.5, 0.5, -0.5], [ 1.0, 1.0]), + ([ 0.5, -0.5, -0.5], [ 1.0, 0.0]), + ([ 0.5, 0.5, -0.5], [ 1.0, 1.0]), + ([-0.5, -0.5, -0.5], [ 0.0, 0.0]), + ([-0.5, 0.5, -0.5], [ 0.0, 1.0]), + ], +]; + +#[rustfmt::skip] +const FACE_DIR: [[i16; 3]; 6] = [ + [ 0, 1, 0], + [ 0, -1, 0], + [ 1, 0, 0], + [-1, 0, 0], + [ 0, 0, 1], + [ 0, 0, -1], +]; |