]> git.lizzy.rs Git - mt_client.git/commitdiff
map mesh generation performance improvements
authorLizzy Fleckenstein <eliasfleckenstein@web.de>
Mon, 8 May 2023 15:51:21 +0000 (17:51 +0200)
committerLizzy Fleckenstein <eliasfleckenstein@web.de>
Mon, 8 May 2023 16:09:56 +0000 (18:09 +0200)
.gitignore
Cargo.lock
Cargo.toml
src/gfx/map.rs
src/gfx/map/atlas.rs [new file with mode: 0644]
src/gfx/map/mesh.rs [new file with mode: 0644]

index ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba..bf051319198d8b64cc665917d57f4b2062132807 100644 (file)
@@ -1 +1,5 @@
 /target
+config.yml
+/flamegraph.svg
+/perf.data
+/perf.data.old
index 907a3751c516842de0bc2e8c1260ca351226cf97..73e08c80118de63446ced15a13656a2b91c3f025 100644 (file)
@@ -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",
 ]
index 309b64d91d09073b5d3cb083e8da753eac814ff5..4e8f8a90c6a132906d2f0023f967da3f826a0a14 100644 (file)
@@ -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"
index dbedb77b60a248caa5380b512973c2c48c078f3f..e5a42f3a11f67dd622a14e6e179b56ea7457b700 100644 (file)
@@ -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<AtlasSlice>,
+    nodes: [Option<Box<NodeDef>>; u16::MAX as usize + 1],
+}
+
 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>,
+    blocks: HashMap<[i16; 3], BlockModel>,
+    mesh_make_info: Arc<MeshMakeInfo>,
+    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<Self> {
+        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<BlockMesh>,
+    mesh_blend: Option<BlockMesh>,
+    transform: MatrixUniform,
+}
+
+fn block_float_pos(pos: Vector3<i16>) -> Vector3<f32> {
+    pos.cast::<f32>().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<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, 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<i16>, block: Box<MapBlock>) {
+        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::<f32>().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<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;
+    pub fn new(state: &mut State, media: &MediaMgr, mut nodes: HashMap<u16, NodeDef>) -> 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 (file)
index 0000000..920fbb4
--- /dev/null
@@ -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<u16, NodeDef>,
+    media: &MediaMgr,
+) -> (image::RgbaImage, Vec<AtlasSlice>) {
+    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 (file)
index 0000000..0befb1e
--- /dev/null
@@ -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<Vertex>,
+    pub vertices_blend: Vec<Vertex>,
+}
+
+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<MeshMakeInfo>,
+    settings: &MapRenderSettings,
+    pos: Point3<i16>,
+    block: Box<MapBlock>,
+) -> (Point3<i16>, 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],
+];