1 use super::{media::MediaMgr, state::State, util::MatrixUniform};
2 use cgmath::{prelude::*, Matrix4, Point3, Vector3};
3 use mt_net::{MapBlock, NodeDef};
5 use std::{collections::HashMap, ops::Range};
6 use wgpu::util::DeviceExt;
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>,
18 #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
25 const ATTRIBS: [wgpu::VertexAttribute; 2] =
26 wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2];
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,
38 vertex_buffer: wgpu::Buffer,
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, &[]);
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);
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) {
66 use std::array::from_fn as array;
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);
77 if let Some(ndef) = self.nodes.get(&block.param_0[nindex as usize]) {
78 if ndef.draw_type == DrawType::Cube {
84 let tile = &def.tiles[f];
85 let rect = self.textures.get(&tile.texture).unwrap();
87 for vertex in face.iter() {
89 "{:?} {:?} {:?} {:?}",
90 (vertex.1[0], vertex.1[1]),
91 (rect[0].start, rect[1].start),
92 (rect[0].end, rect[1].end),
94 vertex.1[0].lerp(rect[0].start, rect[0].end),
95 vertex.1[1].lerp(rect[1].start, rect[1].end)
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])),
117 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
118 label: Some("mapblock.vertex_buffer"),
119 contents: bytemuck::cast_slice(&vertices),
120 usage: wgpu::BufferUsages::VERTEX,
122 num_vertices: vertices.len() as u32,
123 model: MatrixUniform::new(
126 Matrix4::from_translation(
127 pos.cast::<f32>().unwrap().to_vec() * 16.0 + Vector3::new(8.5, 8.5, 8.5),
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));
141 for node in nodes.values() {
145 .chain(node.overlay_tiles.iter())
146 .chain(node.special_tiles.iter());
148 let load_texture = |texture: &str| {
151 .ok_or_else(|| format!("texture not found: {texture}"))?;
153 image::load_from_memory(payload)
155 image::load_from_memory_with_format(payload, image::ImageFormat::Tga)
157 .map_err(|e| format!("failed to load texture {texture}: {e}"))
158 .map(|x| image::imageops::flip_vertical(&x))
161 let mut make_texture = |texture: &str| {
164 .map(|part| match load_texture(part) {
167 if !texture.is_empty() && !texture.contains('[') {
171 let mut img = image::RgbImage::new(1, 1);
172 rng.fill(&mut img.get_pixel_mut(0, 0).0);
174 image::DynamicImage::from(img).to_rgba8()
177 .reduce(|mut base, top| {
178 image::imageops::overlay(&mut base, &top, 0, 0);
185 atlas_map.entry(tile.texture.clone()).or_insert_with(|| {
186 let img = make_texture(&tile.texture);
188 let dimensions = img.dimensions();
189 let size = guillotiere::size2(dimensions.0 as i32, dimensions.1 as i32);
192 match atlas_alloc.allocate(size) {
194 let mut atlas_size = atlas_alloc.size();
195 atlas_size.width *= 2;
196 atlas_size.height *= 2;
197 atlas_alloc.grow(atlas_size);
199 Some(v) => return (img, v),
206 let atlas_size = atlas_alloc.size();
207 let mut atlas = image::RgbaImage::new(atlas_size.width as u32, atlas_size.height as u32);
209 let textures = atlas_map
211 .map(|(name, (img, rect))| {
212 let w = atlas_size.width as f32;
213 let h = atlas_size.height as f32;
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);
218 use image::GenericImage;
220 .copy_from(&img, rect.min.x as u32, rect.min.y as u32)
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,
233 let atlas_texture = state.device.create_texture(&wgpu::TextureDescriptor {
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"),
244 state.queue.write_texture(
245 wgpu::ImageCopyTexture {
246 texture: &atlas_texture,
248 origin: wgpu::Origin3d::ZERO,
249 aspect: wgpu::TextureAspect::All,
252 wgpu::ImageDataLayout {
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),
260 let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default());
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,
274 let atlas_bind_group_layout =
277 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
279 wgpu::BindGroupLayoutEntry {
281 visibility: wgpu::ShaderStages::FRAGMENT,
282 ty: wgpu::BindingType::Texture {
284 view_dimension: wgpu::TextureViewDimension::D2,
285 sample_type: wgpu::TextureSampleType::Float { filterable: true },
289 wgpu::BindGroupLayoutEntry {
291 visibility: wgpu::ShaderStages::FRAGMENT,
292 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
296 label: Some("atlas.bind_group_layout"),
299 let atlas_bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor {
300 layout: &atlas_bind_group_layout,
302 wgpu::BindGroupEntry {
304 resource: wgpu::BindingResource::TextureView(&atlas_view),
306 wgpu::BindGroupEntry {
308 resource: wgpu::BindingResource::Sampler(&atlas_sampler),
311 label: Some("atlas.bind_group"),
314 let model_bind_group_layout = MatrixUniform::layout(&state.device, "mapblock");
318 .create_shader_module(wgpu::include_wgsl!("../../assets/shaders/map.wgsl"));
320 let pipeline_layout =
323 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
325 bind_group_layouts: &[
326 &atlas_bind_group_layout,
327 &model_bind_group_layout,
328 &state.camera_bind_group_layout,
330 push_constant_ranges: &[],
335 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
337 layout: Some(&pipeline_layout),
338 vertex: wgpu::VertexState {
340 entry_point: "vs_main",
341 buffers: &[Vertex::desc()],
343 fragment: Some(wgpu::FragmentState {
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,
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,
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(),
368 multisample: wgpu::MultisampleState {
371 alpha_to_coverage_enabled: false,
380 atlas: atlas_bind_group,
381 model: model_bind_group_layout,
382 blocks: HashMap::new(),
388 const CUBE: [[([f32; 3], [f32; 2]); 6]; 6] = [
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]),
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]),
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]),
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]),
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]),
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]),
440 const FACE_DIR: [[i16; 3]; 6] = [