]> git.lizzy.rs Git - mt_client.git/blob - src/gfx/map.rs
Basic map rendering
[mt_client.git] / src / gfx / map.rs
1 use super::{media::MediaMgr, state::State, util::MatrixUniform};
2 use cgmath::{prelude::*, Matrix4, Point3, Vector3};
3 use mt_net::{MapBlock, NodeDef};
4 use rand::Rng;
5 use std::{collections::HashMap, ops::Range};
6 use wgpu::util::DeviceExt;
7
8 pub struct MapRender {
9     pipeline: wgpu::RenderPipeline,
10     textures: HashMap<String, [Range<f32>; 2]>,
11     nodes: HashMap<u16, NodeDef>,
12     atlas: wgpu::BindGroup,
13     model: wgpu::BindGroupLayout,
14     blocks: HashMap<[i16; 3], BlockMesh>,
15 }
16
17 #[repr(C)]
18 #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
19 struct Vertex {
20     pos: [f32; 3],
21     tex_coords: [f32; 2],
22 }
23
24 impl Vertex {
25     const ATTRIBS: [wgpu::VertexAttribute; 2] =
26         wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2];
27
28     fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
29         wgpu::VertexBufferLayout {
30             array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
31             step_mode: wgpu::VertexStepMode::Vertex,
32             attributes: &Self::ATTRIBS,
33         }
34     }
35 }
36
37 struct BlockMesh {
38     vertex_buffer: wgpu::Buffer,
39     num_vertices: u32,
40     model: MatrixUniform,
41 }
42
43 impl MapRender {
44     pub fn render<'a>(&'a self, state: &'a State, pass: &mut wgpu::RenderPass<'a>) {
45         pass.set_pipeline(&self.pipeline);
46         pass.set_bind_group(0, &self.atlas, &[]);
47         pass.set_bind_group(1, &state.camera_uniform.bind_group, &[]);
48
49         for mesh in self.blocks.values() {
50             pass.set_bind_group(2, &mesh.model.bind_group, &[]);
51             pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
52             pass.draw(0..mesh.num_vertices, 0..1);
53         }
54     }
55
56     pub fn add_block(&mut self, state: &mut State, pos: Point3<i16>, block: Box<MapBlock>) {
57         let mut vertices = Vec::with_capacity(10000);
58         for (index, content) in block.param_0.iter().enumerate() {
59             let def = match self.nodes.get(content) {
60                 Some(x) => x,
61                 None => continue,
62             };
63
64             use lerp::Lerp;
65             use mt_net::DrawType;
66             use std::array::from_fn as array;
67
68             match def.draw_type {
69                 DrawType::Cube => {
70                     let pos: [i16; 3] = array(|i| ((index >> (4 * i)) & 0xf) as i16);
71                     for (f, face) in CUBE.iter().enumerate() {
72                         let dir = FACE_DIR[f];
73                         let npos: [i16; 3] = array(|i| dir[i] + pos[i]);
74                         if npos.iter().all(|x| (0..16).contains(x)) {
75                             let nindex = npos[0] | (npos[1] << 4) | (npos[2] << 8);
76
77                             if let Some(ndef) = self.nodes.get(&block.param_0[nindex as usize]) {
78                                 if ndef.draw_type == DrawType::Cube {
79                                     continue;
80                                 }
81                             }
82                         }
83
84                         let tile = &def.tiles[f];
85                         let rect = self.textures.get(&tile.texture).unwrap();
86
87                         for vertex in face.iter() {
88                             /*println!(
89                                 "{:?} {:?} {:?} {:?}",
90                                 (vertex.1[0], vertex.1[1]),
91                                 (rect[0].start, rect[1].start),
92                                 (rect[0].end, rect[1].end),
93                                 (
94                                     vertex.1[0].lerp(rect[0].start, rect[0].end),
95                                     vertex.1[1].lerp(rect[1].start, rect[1].end)
96                                 )
97                             );*/
98                             vertices.push(Vertex {
99                                 pos: array(|i| pos[i] as f32 - 8.5 + vertex.0[i]),
100                                 tex_coords: array(|i| rect[i].start.lerp(rect[i].end, vertex.1[i])),
101                             })
102                         }
103                     }
104                 }
105                 DrawType::None => {}
106                 _ => {
107                     // TODO
108                 }
109             }
110         }
111
112         self.blocks.insert(
113             pos.into(),
114             BlockMesh {
115                 vertex_buffer: state
116                     .device
117                     .create_buffer_init(&wgpu::util::BufferInitDescriptor {
118                         label: Some("mapblock.vertex_buffer"),
119                         contents: bytemuck::cast_slice(&vertices),
120                         usage: wgpu::BufferUsages::VERTEX,
121                     }),
122                 num_vertices: vertices.len() as u32,
123                 model: MatrixUniform::new(
124                     &state.device,
125                     &self.model,
126                     Matrix4::from_translation(
127                         pos.cast::<f32>().unwrap().to_vec() * 16.0 + Vector3::new(8.5, 8.5, 8.5),
128                     ),
129                     "mapblock",
130                     false,
131                 ),
132             },
133         );
134     }
135
136     pub fn new(state: &mut State, media: &MediaMgr, nodes: HashMap<u16, NodeDef>) -> Self {
137         let mut rng = rand::thread_rng();
138         let mut atlas_map = HashMap::new();
139         let mut atlas_alloc = guillotiere::SimpleAtlasAllocator::new(guillotiere::size2(1, 1));
140
141         for node in nodes.values() {
142             let tiles = node
143                 .tiles
144                 .iter()
145                 .chain(node.overlay_tiles.iter())
146                 .chain(node.special_tiles.iter());
147
148             let load_texture = |texture: &str| {
149                 let payload = media
150                     .get(texture)
151                     .ok_or_else(|| format!("texture not found: {texture}"))?;
152
153                 image::load_from_memory(payload)
154                     .or_else(|_| {
155                         image::load_from_memory_with_format(payload, image::ImageFormat::Tga)
156                     })
157                     .map_err(|e| format!("failed to load texture {texture}: {e}"))
158                     .map(|x| image::imageops::flip_vertical(&x))
159             };
160
161             let mut make_texture = |texture: &str| {
162                 texture
163                     .split('^')
164                     .map(|part| match load_texture(part) {
165                         Ok(v) => v,
166                         Err(e) => {
167                             if !texture.is_empty() && !texture.contains('[') {
168                                 eprintln!("{e}");
169                             }
170
171                             let mut img = image::RgbImage::new(1, 1);
172                             rng.fill(&mut img.get_pixel_mut(0, 0).0);
173
174                             image::DynamicImage::from(img).to_rgba8()
175                         }
176                     })
177                     .reduce(|mut base, top| {
178                         image::imageops::overlay(&mut base, &top, 0, 0);
179                         base
180                     })
181                     .unwrap()
182             };
183
184             for tile in tiles {
185                 atlas_map.entry(tile.texture.clone()).or_insert_with(|| {
186                     let img = make_texture(&tile.texture);
187
188                     let dimensions = img.dimensions();
189                     let size = guillotiere::size2(dimensions.0 as i32, dimensions.1 as i32);
190
191                     loop {
192                         match atlas_alloc.allocate(size) {
193                             None => {
194                                 let mut atlas_size = atlas_alloc.size();
195                                 atlas_size.width *= 2;
196                                 atlas_size.height *= 2;
197                                 atlas_alloc.grow(atlas_size);
198                             }
199                             Some(v) => return (img, v),
200                         }
201                     }
202                 });
203             }
204         }
205
206         let atlas_size = atlas_alloc.size();
207         let mut atlas = image::RgbaImage::new(atlas_size.width as u32, atlas_size.height as u32);
208
209         let textures = atlas_map
210             .into_iter()
211             .map(|(name, (img, rect))| {
212                 let w = atlas_size.width as f32;
213                 let h = atlas_size.height as f32;
214
215                 let x = (rect.min.x as f32 / w)..(rect.max.x as f32 / w);
216                 let y = (rect.min.y as f32 / h)..(rect.max.y as f32 / h);
217
218                 use image::GenericImage;
219                 atlas
220                     .copy_from(&img, rect.min.x as u32, rect.min.y as u32)
221                     .unwrap();
222
223                 (name, [x, y])
224             })
225             .collect();
226
227         let size = wgpu::Extent3d {
228             width: atlas_size.width as u32,
229             height: atlas_size.height as u32,
230             depth_or_array_layers: 1,
231         };
232
233         let atlas_texture = state.device.create_texture(&wgpu::TextureDescriptor {
234             size,
235             mip_level_count: 1,
236             sample_count: 1,
237             dimension: wgpu::TextureDimension::D2,
238             format: wgpu::TextureFormat::Rgba8UnormSrgb,
239             usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
240             label: Some("tile_atlas"),
241             view_formats: &[],
242         });
243
244         state.queue.write_texture(
245             wgpu::ImageCopyTexture {
246                 texture: &atlas_texture,
247                 mip_level: 0,
248                 origin: wgpu::Origin3d::ZERO,
249                 aspect: wgpu::TextureAspect::All,
250             },
251             &atlas,
252             wgpu::ImageDataLayout {
253                 offset: 0,
254                 bytes_per_row: std::num::NonZeroU32::new(4 * atlas_size.width as u32),
255                 rows_per_image: std::num::NonZeroU32::new(atlas_size.height as u32),
256             },
257             size,
258         );
259
260         let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default());
261
262         let atlas_sampler = state.device.create_sampler(&wgpu::SamplerDescriptor {
263             address_mode_u: wgpu::AddressMode::ClampToEdge,
264             address_mode_v: wgpu::AddressMode::ClampToEdge,
265             address_mode_w: wgpu::AddressMode::ClampToEdge,
266             // "We've got you surrounded, stop using Nearest filter"
267             // - "I hate bilinear filtering I hate bilinear filtering I hate bilinear filtering"
268             mag_filter: wgpu::FilterMode::Nearest,
269             min_filter: wgpu::FilterMode::Nearest,
270             mipmap_filter: wgpu::FilterMode::Nearest,
271             ..Default::default()
272         });
273
274         let atlas_bind_group_layout =
275             state
276                 .device
277                 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
278                     entries: &[
279                         wgpu::BindGroupLayoutEntry {
280                             binding: 0,
281                             visibility: wgpu::ShaderStages::FRAGMENT,
282                             ty: wgpu::BindingType::Texture {
283                                 multisampled: false,
284                                 view_dimension: wgpu::TextureViewDimension::D2,
285                                 sample_type: wgpu::TextureSampleType::Float { filterable: true },
286                             },
287                             count: None,
288                         },
289                         wgpu::BindGroupLayoutEntry {
290                             binding: 1,
291                             visibility: wgpu::ShaderStages::FRAGMENT,
292                             ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
293                             count: None,
294                         },
295                     ],
296                     label: Some("atlas.bind_group_layout"),
297                 });
298
299         let atlas_bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor {
300             layout: &atlas_bind_group_layout,
301             entries: &[
302                 wgpu::BindGroupEntry {
303                     binding: 0,
304                     resource: wgpu::BindingResource::TextureView(&atlas_view),
305                 },
306                 wgpu::BindGroupEntry {
307                     binding: 1,
308                     resource: wgpu::BindingResource::Sampler(&atlas_sampler),
309                 },
310             ],
311             label: Some("atlas.bind_group"),
312         });
313
314         let model_bind_group_layout = MatrixUniform::layout(&state.device, "mapblock");
315
316         let shader = state
317             .device
318             .create_shader_module(wgpu::include_wgsl!("../../assets/shaders/map.wgsl"));
319
320         let pipeline_layout =
321             state
322                 .device
323                 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
324                     label: None,
325                     bind_group_layouts: &[
326                         &atlas_bind_group_layout,
327                         &model_bind_group_layout,
328                         &state.camera_bind_group_layout,
329                     ],
330                     push_constant_ranges: &[],
331                 });
332
333         let pipeline = state
334             .device
335             .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
336                 label: None,
337                 layout: Some(&pipeline_layout),
338                 vertex: wgpu::VertexState {
339                     module: &shader,
340                     entry_point: "vs_main",
341                     buffers: &[Vertex::desc()],
342                 },
343                 fragment: Some(wgpu::FragmentState {
344                     module: &shader,
345                     entry_point: "fs_main",
346                     targets: &[Some(wgpu::ColorTargetState {
347                         format: state.config.format,
348                         blend: Some(wgpu::BlendState::REPLACE),
349                         write_mask: wgpu::ColorWrites::ALL,
350                     })],
351                 }),
352                 primitive: wgpu::PrimitiveState {
353                     topology: wgpu::PrimitiveTopology::TriangleList,
354                     strip_index_format: None,
355                     front_face: wgpu::FrontFace::Ccw,
356                     cull_mode: Some(wgpu::Face::Back),
357                     polygon_mode: wgpu::PolygonMode::Fill,
358                     unclipped_depth: false,
359                     conservative: false,
360                 },
361                 depth_stencil: Some(wgpu::DepthStencilState {
362                     format: wgpu::TextureFormat::Depth32Float,
363                     depth_write_enabled: true,
364                     depth_compare: wgpu::CompareFunction::Less,
365                     stencil: wgpu::StencilState::default(),
366                     bias: wgpu::DepthBiasState::default(),
367                 }),
368                 multisample: wgpu::MultisampleState {
369                     count: 1,
370                     mask: !0,
371                     alpha_to_coverage_enabled: false,
372                 },
373                 multiview: None,
374             });
375
376         Self {
377             pipeline,
378             nodes,
379             textures,
380             atlas: atlas_bind_group,
381             model: model_bind_group_layout,
382             blocks: HashMap::new(),
383         }
384     }
385 }
386
387 #[rustfmt::skip]
388 const CUBE: [[([f32; 3], [f32; 2]); 6]; 6] = [
389         [
390                 ([-0.5,  0.5, -0.5], [ 0.0,  1.0]),
391                 ([ 0.5,  0.5,  0.5], [ 1.0,  0.0]),
392                 ([ 0.5,  0.5, -0.5], [ 1.0,  1.0]),
393                 ([ 0.5,  0.5,  0.5], [ 1.0,  0.0]),
394                 ([-0.5,  0.5, -0.5], [ 0.0,  1.0]),
395                 ([-0.5,  0.5,  0.5], [ 0.0,  0.0]),
396         ],
397         [
398                 ([-0.5, -0.5, -0.5], [ 0.0,  1.0]),
399                 ([ 0.5, -0.5, -0.5], [ 1.0,  1.0]),
400                 ([ 0.5, -0.5,  0.5], [ 1.0,  0.0]),
401                 ([ 0.5, -0.5,  0.5], [ 1.0,  0.0]),
402                 ([-0.5, -0.5,  0.5], [ 0.0,  0.0]),
403                 ([-0.5, -0.5, -0.5], [ 0.0,  1.0]),
404         ],
405         [
406                 ([ 0.5,  0.5,  0.5], [ 1.0,  1.0]),
407                 ([ 0.5, -0.5, -0.5], [ 0.0,  0.0]),
408                 ([ 0.5,  0.5, -0.5], [ 0.0,  1.0]),
409                 ([ 0.5, -0.5, -0.5], [ 0.0,  0.0]),
410                 ([ 0.5,  0.5,  0.5], [ 1.0,  1.0]),
411                 ([ 0.5, -0.5,  0.5], [ 1.0,  0.0]),
412         ],
413         [
414                 ([-0.5,  0.5,  0.5], [ 1.0,  1.0]),
415                 ([-0.5,  0.5, -0.5], [ 0.0,  1.0]),
416                 ([-0.5, -0.5, -0.5], [ 0.0,  0.0]),
417                 ([-0.5, -0.5, -0.5], [ 0.0,  0.0]),
418                 ([-0.5, -0.5,  0.5], [ 1.0,  0.0]),
419                 ([-0.5,  0.5,  0.5], [ 1.0,  1.0]),
420         ],
421         [
422                 ([-0.5, -0.5,  0.5], [ 0.0,  0.0]),
423                 ([ 0.5, -0.5,  0.5], [ 1.0,  0.0]),
424                 ([ 0.5,  0.5,  0.5], [ 1.0,  1.0]),
425                 ([ 0.5,  0.5,  0.5], [ 1.0,  1.0]),
426                 ([-0.5,  0.5,  0.5], [ 0.0,  1.0]),
427                 ([-0.5, -0.5,  0.5], [ 0.0,  0.0]),
428         ],
429         [
430                 ([-0.5, -0.5, -0.5], [ 0.0,  0.0]),
431                 ([ 0.5,  0.5, -0.5], [ 1.0,  1.0]),
432                 ([ 0.5, -0.5, -0.5], [ 1.0,  0.0]),
433                 ([ 0.5,  0.5, -0.5], [ 1.0,  1.0]),
434                 ([-0.5, -0.5, -0.5], [ 0.0,  0.0]),
435                 ([-0.5,  0.5, -0.5], [ 0.0,  1.0]),
436         ],
437 ];
438
439 #[rustfmt::skip]
440 const FACE_DIR: [[i16; 3]; 6] = [
441         [ 0,  1,  0],
442         [ 0, -1,  0],
443         [ 1,  0,  0],
444         [-1,  0,  0],
445         [ 0,  0,  1],
446         [ 0,  0, -1],
447 ];