From b6d021cded621e75ca66e83ebbec161130eb9f73 Mon Sep 17 00:00:00 2001 From: Lizzy Fleckenstein Date: Mon, 8 May 2023 17:51:21 +0200 Subject: map mesh generation performance improvements --- .gitignore | 4 + Cargo.lock | 94 +++++++++++---- Cargo.toml | 2 + src/gfx/map.rs | 335 +++++++++++++++++++++------------------------------ src/gfx/map/atlas.rs | 111 +++++++++++++++++ src/gfx/map/mesh.rs | 125 +++++++++++++++++++ 6 files changed, 451 insertions(+), 220 deletions(-) create mode 100644 src/gfx/map/atlas.rs create mode 100644 src/gfx/map/mesh.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..bf05131 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /target +config.yml +/flamegraph.svg +/perf.data +/perf.data.old diff --git a/Cargo.lock b/Cargo.lock index 907a375..73e08c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,7 +115,7 @@ checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -126,7 +126,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -237,7 +237,7 @@ checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -453,7 +453,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.107", ] [[package]] @@ -464,7 +464,7 @@ checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -475,7 +475,7 @@ checksum = "d358e0ec5c59a5e1603b933def447096886121660fc680dc1e64a0753981fe3c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -525,7 +525,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -642,7 +642,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -867,6 +867,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + [[package]] name = "jni-sys" version = "0.3.0" @@ -1063,6 +1069,8 @@ dependencies = [ "mt_net", "rand 0.8.5", "rust-embed", + "serde", + "serde_yaml", "sha2", "srp", "threadpool", @@ -1074,7 +1082,7 @@ dependencies = [ [[package]] name = "mt_net" version = "0.1.0" -source = "git+https://github.com/minetest-rust/mt_net#85d55e42119ea80cd1cd9e9e34c05ea7d07b3a88" +source = "git+https://github.com/minetest-rust/mt_net#2bf4f75254ddfca9ef13c6a1b2a8e6210f298ab5" dependencies = [ "async-trait", "cgmath", @@ -1124,7 +1132,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1350,7 +1358,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1533,9 +1541,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -1557,9 +1565,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1768,7 +1776,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 1.0.107", "walkdir", ] @@ -1794,6 +1802,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + [[package]] name = "same-file" version = "1.0.6" @@ -1830,22 +1844,35 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", +] + +[[package]] +name = "serde_yaml" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", ] [[package]] @@ -1993,6 +2020,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2019,7 +2057,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2081,7 +2119,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2137,6 +2175,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unsafe-libyaml" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2202,7 +2246,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -2236,7 +2280,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 309b64d..4e8f8a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ lerp = "0.4.0" mt_net = { git = "https://github.com/minetest-rust/mt_net", features = ["conn", "client"] } rand = "0.8.5" rust-embed = "6.4.2" +serde = { version = "1.0.159", features = ["derive"] } +serde_yaml = "0.9.21" sha2 = "0.10.6" srp = { git = "https://github.com/minetest-rust/PAKEs.git" } threadpool = "1.8.1" diff --git a/src/gfx/map.rs b/src/gfx/map.rs index dbedb77..e5a42f3 100644 --- a/src/gfx/map.rs +++ b/src/gfx/map.rs @@ -1,17 +1,55 @@ +mod atlas; +mod mesh; + use super::{media::MediaMgr, state::State, util::MatrixUniform}; +use atlas::create_atlas; use cgmath::{prelude::*, Matrix4, Point3, Vector3}; +use mesh::create_mesh; use mt_net::{MapBlock, NodeDef}; -use rand::Rng; -use std::{collections::HashMap, ops::Range}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, sync::Arc}; use wgpu::util::DeviceExt; +#[derive(Serialize, Deserialize, PartialEq, Eq, Copy, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub enum LeavesMode { + Opaque, + Simple, + Fancy, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct MapRenderSettings { + pub leaves: LeavesMode, + pub opaque_liquids: bool, +} + +impl Default for MapRenderSettings { + fn default() -> Self { + Self { + leaves: LeavesMode::Fancy, + opaque_liquids: false, + } + } +} + +struct AtlasSlice { + cube_tex_coords: [[[f32; 2]; 6]; 6], +} + +struct MeshMakeInfo { + // i optimized the shit out of these + textures: Vec, + nodes: [Option>; u16::MAX as usize + 1], +} + pub struct MapRender { pipeline: wgpu::RenderPipeline, - textures: HashMap; 2]>, - nodes: HashMap, atlas: wgpu::BindGroup, model: wgpu::BindGroupLayout, - blocks: HashMap<[i16; 3], BlockMesh>, + blocks: HashMap<[i16; 3], BlockModel>, + mesh_make_info: Arc, + mesh_data_buffer: usize, } #[repr(C)] @@ -38,7 +76,41 @@ impl Vertex { struct BlockMesh { vertex_buffer: wgpu::Buffer, num_vertices: u32, - model: MatrixUniform, +} + +impl BlockMesh { + fn new(state: &State, vertices: &[Vertex]) -> Option { + if vertices.is_empty() { + return None; + } + + Some(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, + }) + } + + fn render<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, transform: &'a MatrixUniform) { + pass.set_bind_group(2, &transform.bind_group, &[]); + pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + pass.draw(0..self.num_vertices, 0..1); + } +} + +struct BlockModel { + mesh: Option, + mesh_blend: Option, + transform: MatrixUniform, +} + +fn block_float_pos(pos: Vector3) -> Vector3 { + pos.cast::().unwrap() * 16.0 } impl MapRender { @@ -47,95 +119,63 @@ impl MapRender { 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); + struct BlendEntry<'a> { + dist: f32, + index: usize, + mesh: &'a BlockMesh, + transform: &'a MatrixUniform, } - } - pub fn add_block(&mut self, state: &mut State, pos: Point3, block: Box) { - 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, Param1Type}; - use std::array::from_fn as array; - - match def.draw_type { - DrawType::Cube | DrawType::AllFaces | DrawType::AllFacesOpt => { - let light = match def.param1_type { - Param1Type::Light => { - println!("{}", block.param_1[index]); - - block.param_1[index] as f32 / 15.0 - } - _ => 1.0, - }; - - 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])), - light, - }) - } - } - } - DrawType::None => {} - _ => { - // TODO - } + let mut blend = Vec::new(); + + for (index, (pos, model)) in self.blocks.iter().enumerate() { + if let Some(mesh) = &model.mesh { + mesh.render(pass, &model.transform); + } + + if let Some(mesh) = &model.mesh_blend { + blend.push(BlendEntry { + index, + dist: (block_float_pos(Vector3::from(*pos)) + - Vector3::from(state.camera.position)) + .magnitude(), + mesh, + transform: &model.transform, + }); } } + blend.sort_unstable_by(|a, b| { + a.dist + .partial_cmp(&b.dist) + .unwrap_or(std::cmp::Ordering::Equal) + .then_with(|| a.index.cmp(&b.index)) + }); + + for entry in blend { + entry.mesh.render(pass, entry.transform); + } + } + + pub fn add_block(&mut self, state: &mut State, pos: Point3, block: Box) { + let (pos, data) = create_mesh( + &mut self.mesh_data_buffer, + self.mesh_make_info.clone(), + &Default::default(), + pos, + block, + ); + 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( + BlockModel { + mesh: BlockMesh::new(state, &data.vertices), + mesh_blend: BlockMesh::new(state, &data.vertices_blend), + transform: MatrixUniform::new( &state.device, &self.model, Matrix4::from_translation( - pos.cast::().unwrap().to_vec() * 16.0 + Vector3::new(8.5, 8.5, 8.5), + block_float_pos(pos.to_vec()) + Vector3::new(8.5, 8.5, 8.5), ), "mapblock", false, @@ -144,105 +184,17 @@ impl MapRender { ); } - pub fn new(state: &mut State, media: &MediaMgr, nodes: HashMap) -> 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; + pub fn new(state: &mut State, media: &MediaMgr, mut nodes: HashMap) -> Self { + let (atlas_img, atlas_slices) = create_atlas(&mut nodes, media); - 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, + let atlas_size = wgpu::Extent3d { + width: atlas_img.width(), + height: atlas_img.height(), depth_or_array_layers: 1, }; let atlas_texture = state.device.create_texture(&wgpu::TextureDescriptor { - size, + size: atlas_size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, @@ -259,13 +211,13 @@ impl MapRender { origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, - &atlas, + &atlas_img, 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), + bytes_per_row: std::num::NonZeroU32::new(4 * atlas_img.width()), + rows_per_image: std::num::NonZeroU32::new(atlas_img.height()), }, - size, + atlas_size, ); let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default()); @@ -386,8 +338,11 @@ impl MapRender { Self { pipeline, - nodes, - textures, + mesh_make_info: Arc::new(MeshMakeInfo { + nodes: std::array::from_fn(|i| nodes.get(&(i as u16)).cloned().map(Box::new)), + textures: atlas_slices, + }), + mesh_data_buffer: 16384, atlas: atlas_bind_group, model: model_bind_group_layout, blocks: HashMap::new(), @@ -446,13 +401,3 @@ const CUBE: [[([f32; 3], [f32; 2]); 6]; 6] = [ ([-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], -]; diff --git a/src/gfx/map/atlas.rs b/src/gfx/map/atlas.rs new file mode 100644 index 0000000..920fbb4 --- /dev/null +++ b/src/gfx/map/atlas.rs @@ -0,0 +1,111 @@ +use super::{super::media::MediaMgr, AtlasSlice, CUBE}; +use mt_net::NodeDef; +use rand::Rng; +use std::collections::HashMap; + +pub(super) fn create_atlas( + nodes: &mut HashMap, + media: &MediaMgr, +) -> (image::RgbaImage, Vec) { + let mut rng = rand::thread_rng(); + let mut allocator = guillotiere::SimpleAtlasAllocator::new(guillotiere::size2(1, 1)); + let mut textures = Vec::new(); + + for node in nodes.values_mut() { + let tiles = std::iter::empty() + .chain(node.tiles.iter_mut()) + .chain(node.overlay_tiles.iter_mut()) + .chain(node.special_tiles.iter_mut()); + + 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() + }; + + let mut id_map = HashMap::new(); + + for tile in tiles { + tile.texture.custom = *id_map.entry(tile.texture.name.clone()).or_insert_with(|| { + let img = make_texture(&tile.texture.name); + + let dimensions = img.dimensions(); + let size = guillotiere::size2(dimensions.0 as i32, dimensions.1 as i32); + + loop { + match allocator.allocate(size) { + None => { + let mut atlas_size = allocator.size(); + atlas_size.width *= 2; + atlas_size.height *= 2; + allocator.grow(atlas_size); + } + Some(rect) => { + let id = textures.len(); + textures.push((img, rect)); + return id; + } + } + } + }) + } + } + + let size = allocator.size(); + let mut atlas = image::RgbaImage::new(size.width as u32, size.height as u32); + + let slices = textures + .into_iter() + .map(|(img, rect)| { + let w = size.width as f32; + let h = 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(); + + use lerp::Lerp; + use std::array::from_fn as array; + + let rect = [x, y]; + let cube_tex_coords = + array(|f| array(|v| array(|i| rect[i].start.lerp(rect[i].end, CUBE[f][v].1[i])))); + + AtlasSlice { cube_tex_coords } + }) + .collect(); + + (atlas, slices) +} diff --git a/src/gfx/map/mesh.rs b/src/gfx/map/mesh.rs new file mode 100644 index 0000000..0befb1e --- /dev/null +++ b/src/gfx/map/mesh.rs @@ -0,0 +1,125 @@ +use super::{LeavesMode, MapRenderSettings, MeshMakeInfo, Vertex, CUBE}; +use cgmath::Point3; +use mt_net::MapBlock; +use std::ops::Deref; +use std::sync::Arc; + +#[derive(Clone)] +pub(super) struct MeshData { + pub vertices: Vec, + pub vertices_blend: Vec, +} + +impl MeshData { + pub fn new(cap: usize) -> Self { + Self { + vertices: Vec::with_capacity(cap), + vertices_blend: Vec::with_capacity(cap), + } + } + + pub fn cap(&self) -> usize { + std::cmp::max(self.vertices.capacity(), self.vertices_blend.capacity()) + } +} + +pub(super) fn create_mesh( + buffer_cap: &mut usize, + mkinfo: Arc, + settings: &MapRenderSettings, + pos: Point3, + block: Box, +) -> (Point3, MeshData) { + let mkinfo = mkinfo.deref(); + let mut buffer = MeshData::new(*buffer_cap); + + for (index, content) in block.param_0.iter().enumerate() { + let def = match &mkinfo.nodes[*content as usize] { + Some(x) => x, + None => continue, + }; + + use mt_net::{DrawType, Param1Type}; + use std::array::from_fn as array; + + let mut tiles = &def.tiles; + let mut draw_type = def.draw_type; + + match draw_type { + DrawType::AllFacesOpt => { + draw_type = match settings.leaves { + LeavesMode::Opaque => DrawType::Cube, + LeavesMode::Simple => { + tiles = &def.special_tiles; + + DrawType::GlassLike + } + LeavesMode::Fancy => DrawType::AllFaces, + }; + } + DrawType::None => continue, + _ => {} + } + + let light = match def.param1_type { + Param1Type::Light => block.param_1[index] as f32 / 15.0, + _ => 1.0, + }; + + 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) = &mkinfo.nodes[block.param_0[nindex as usize] as usize] { + if ndef.draw_type == DrawType::Cube { + continue; + } + } + } + + let tile = &tiles[f]; + let texture = mkinfo.textures[tile.texture.custom].cube_tex_coords[f]; + + let mut add_vertex = |vertex: (usize, &([f32; 3], [f32; 2]))| { + /*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) + ) + );*/ + + buffer.vertices.push(Vertex { + pos: array(|i| pos[i] as f32 - 8.5 + vertex.1 .0[i]), + tex_coords: texture[vertex.0], + light, + }); + }; + + face.iter().enumerate().for_each(&mut add_vertex); + /*if !backface_cull { + face.iter().enumerate().rev().for_each(&mut add_vertex); + }*/ + } + } + + *buffer_cap = buffer.cap(); + (pos, buffer) +} + +#[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], +]; -- cgit v1.2.3