summaryrefslogtreecommitdiff
path: root/src/gfx/map/atlas.rs
diff options
context:
space:
mode:
authorLizzy Fleckenstein <eliasfleckenstein@web.de>2023-05-08 17:51:21 +0200
committerLizzy Fleckenstein <eliasfleckenstein@web.de>2023-05-08 18:09:56 +0200
commitb6d021cded621e75ca66e83ebbec161130eb9f73 (patch)
tree421e015a98f76a2b8a3d5de50d02b8f1410d7e35 /src/gfx/map/atlas.rs
parentd5dcdf8078e6f2c6629469de7f38e2327ef6781e (diff)
downloadmt_client-b6d021cded621e75ca66e83ebbec161130eb9f73.tar.xz
map mesh generation performance improvements
Diffstat (limited to 'src/gfx/map/atlas.rs')
-rw-r--r--src/gfx/map/atlas.rs111
1 files changed, 111 insertions, 0 deletions
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<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)
+}