diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 87 | ||||
-rw-r--r-- | src/render/composite.rs | 47 | ||||
-rw-r--r-- | src/render/mod.rs | 3 | ||||
-rw-r--r-- | src/render/models.rs | 36 | ||||
-rw-r--r-- | src/render/processing.rs | 134 |
5 files changed, 307 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..92d07b7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,87 @@ +pub mod render; + +use crate::render::{ + composite::{ + image_buffer_blit, isometric_coord_mapping, CHUNK_HEIGHT, CHUNK_SIZE, REGION_SIZE, + }, + models::processed_block_texture, + processing::Texture, +}; +use fastanvil::{tex::Render, Block, Chunk, CurrentJavaChunk, RegionLoader}; +use image::{ImageBuffer, Rgba}; +use log::debug; +use std::{collections::HashMap, path::Path}; + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .parse_env("LOG") + .build(); + + let loader = fastanvil::RegionFileLoader::new( + Path::new("/home/muffin/containers/games/home/user/server/world/region/").to_path_buf(), + ); + + let mut region = loader + .region(fastanvil::RCoord(0), fastanvil::RCoord(0)) + .unwrap(); + + let chunk = region.read_chunk(0, 0).unwrap().unwrap(); + + let chunk: CurrentJavaChunk = fastnbt::from_bytes(&chunk).unwrap(); + + let mut visible_blocks = Vec::new(); + + for x in 0..16 { + for y in -64..256 { + for z in 0..16 { + let solid = |x: isize, y: isize, z: isize| { + if x >= 0 && z >= 0 && x < 16 && z < 16 && y >= -64 && y < 256 { + chunk + .block(x as usize, y, z as usize) + .map(|b| b.name() != "minecraft:air") + .unwrap_or(false) + } else { + false + } + }; + let visible = solid(x, y, z) + && !(solid(x + 1, y, z) + && solid(x - 1, y, z) + && solid(x, y + 1, z) + && solid(x, y - 1, z) + && solid(x, y, z + 1) + && solid(x, y, z - 1)); + + if visible { + visible_blocks.push((x, y, z)) + } + } + } + } + + debug!("{} potentially visible blocks", visible_blocks.len()); + debug!("sorting by z-order"); + visible_blocks.sort_by_cached_key(|(x, y, z)| x + y + z); + debug!("done"); + + let mut view: Texture = ImageBuffer::new( + 16 * CHUNK_SIZE as u32, + 16 * (CHUNK_SIZE as u32 + CHUNK_HEIGHT as u32) / 2, + ); + + let mut textures = HashMap::<String, Texture>::new(); + + for (x, y, z) in visible_blocks { + let coords = isometric_coord_mapping(x as i32, y as i32, z as i32); + let name = &chunk.block(x as usize, y, z as usize).unwrap().name()["minecraft:".len()..]; + let texture = textures + .entry(name.to_owned()) + .or_insert_with(|| processed_block_texture(name)); + + println!("{coords:?}"); + image_buffer_blit(&mut view, texture, coords); + } + + view.save(format!("/tmp/a.png")).unwrap(); +} diff --git a/src/render/composite.rs b/src/render/composite.rs new file mode 100644 index 0000000..51c6f6a --- /dev/null +++ b/src/render/composite.rs @@ -0,0 +1,47 @@ +use image::ImageBuffer; +use image::Rgba; + +pub const REGION_SIZE: usize = 16 * 8; +pub const CHUNK_HEIGHT: usize = 384; +pub const CHUNK_SIZE: usize = 16; + +pub fn image_buffer_blit( + target: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, + source: &ImageBuffer<Rgba<u8>, Vec<u8>>, + offset: (u32, u32), +) { + for (x, y, source_pixel) in source.enumerate_pixels() { + let target_pixel = target.get_pixel_mut(x + offset.0, y + offset.1); + let sa = source_pixel.0[3] as u16; + let new_pixel = Rgba::from([ + ((target_pixel.0[0] as u16 * (255 - sa)) / 255 + (source_pixel.0[0] as u16 * sa) / 255) + as u8, + ((target_pixel.0[1] as u16 * (255 - sa)) / 255 + (source_pixel.0[1] as u16 * sa) / 255) + as u8, + ((target_pixel.0[2] as u16 * (255 - sa)) / 255 + (source_pixel.0[2] as u16 * sa) / 255) + as u8, + 255 - (((255 - target_pixel.0[3] as u16) * (255 - sa)) / 255) as u8, + ]); + *target_pixel = new_pixel; + } +} + +pub fn isometric_coord_mapping(x: i32, y: i32, z: i32) -> (u32, u32) { + // const BASE_X: i32 = 1016; + // const BASE_Y: i32 = 2040; + const BASE_X: i32 = 128 - 8; + const BASE_Y: i32 = 2040; + + const XDIFF: (i32, i32) = (-8, 4); + const ZDIFF: (i32, i32) = (8, 4); + const YDIFF: (i32, i32) = (0, -8); + + let diff = ( + XDIFF.0 * x + YDIFF.0 * y + ZDIFF.0 * z, + XDIFF.1 * x + YDIFF.1 * y + ZDIFF.1 * z, + ); + + let coords = (BASE_X + diff.0, BASE_Y + diff.1); + + (coords.0 as u32, coords.1 as u32) +} diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..cd9856e --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,3 @@ +pub mod processing; +pub mod models; +pub mod composite; diff --git a/src/render/models.rs b/src/render/models.rs new file mode 100644 index 0000000..66b8714 --- /dev/null +++ b/src/render/models.rs @@ -0,0 +1,36 @@ +use super::processing::*; +use image::{ImageBuffer, Rgba}; + +pub fn processed_block_texture(name: &str) -> ImageBuffer<Rgba<u8>, Vec<u8>> { + let auto_block_texture = || block_texture(name); + match name { + "grass_block" => full_isometric_sides( + &biome_tint(&block_texture("grass_block_top")), + &block_texture("grass_block_side"), + ), + "oak_leaves" | "birch_leaves" | "acacia_leaves" | "jungle_leaves" | "dark_oak_leaves" + | "spruce_leaves" => full_isometric(&biome_tint(&auto_block_texture())), + "grass" => crossed_planes(&biome_tint(&auto_block_texture())), + "dandelion" | "orange_tulip" | "azure_bluet" | "allium" | "poppy" | "cornflower" => { + crossed_planes(&auto_block_texture()) + } + "lilac" | "peony" | "rose_bush" | "tall_grass" => crossed_planes(&auto_block_texture()), + "water" => full_isometric_sides( + &crop16(&tint(&block_texture("water_still"), (0, 0, 255))), + &crop16(&tint(&block_texture("water_flow"), (0, 0, 255))), + ), + "lava" => full_isometric_sides( + &crop16(&block_texture("lava_still")), + &crop16(&block_texture("lava_flow")), + ), + "vine" => full_isometric(&biome_tint(&auto_block_texture())), + "lily_pad" => crossed_planes(&biome_tint(&auto_block_texture())), + "sugar_cane" => crossed_planes(&auto_block_texture()), + + "removed" => full_isometric(&transparent()), + _ => { + // println!("{}", name); + full_isometric(&auto_block_texture()) + } + } +} diff --git a/src/render/processing.rs b/src/render/processing.rs new file mode 100644 index 0000000..451fab3 --- /dev/null +++ b/src/render/processing.rs @@ -0,0 +1,134 @@ +use image::{ImageBuffer, Rgba}; +use imageproc::geometric_transformations::Projection; +use std::path::Path; + +pub type Texture = ImageBuffer<Rgba<u8>, Vec<u8>>; + +pub fn biome_tint(s: &Texture) -> Texture { + tint(s, (100, 200, 50)) +} + +pub fn tint(source: &Texture, tint: (u16, u16, u16)) -> Texture { + let mut t = ImageBuffer::new(16, 16); + for (x, y, p) in t.enumerate_pixels_mut() { + let sp = source.get_pixel(x, y); + *p = Rgba::from([ + (sp.0[0] as u16 * tint.0 / 255) as u8, + (sp.0[1] as u16 * tint.1 / 255) as u8, + (sp.0[2] as u16 * tint.2 / 255) as u8, + sp.0[3], + ]) + } + return t; +} + +pub fn crop16(tex: &Texture) -> Texture { + let mut t = ImageBuffer::new(16, 16); + for (x, y, p) in t.enumerate_pixels_mut() { + let sp = tex.get_pixel(x, y); + *p = sp.clone(); + } + return t; +} + +pub fn transparent() -> Texture { + let mut t = ImageBuffer::new(16, 16); + for (_,_,p) in t.enumerate_pixels_mut() { + *p = Rgba::from([0,0,0,255]) + } + return t; +} + +pub fn block_texture(block_name: &str) -> Texture { + image::open(&Path::new( + format!("./assets/assets/minecraft/textures/block/{}.png", block_name).as_str(), + )) + .unwrap_or_else(|_err| { + image::open(&Path::new( + format!("./assets/assets/minecraft/textures/block/debug.png").as_str(), + )) + .expect("Could not even load the fallback texture") + }) + .into_rgba8() +} + +pub fn full_isometric(tex: &Texture) -> Texture { + full_isometric_sides(tex, tex) +} + +pub fn full_isometric_sides(top: &Texture, side: &Texture) -> Texture { + let projection_y = Projection::from_control_points( + [(0.0, 0.0), (16.0, 0.0), (16.0, 16.0), (0.0, 16.0)], + [(0.0, 4.0), (8.0, 0.0), (16.0, 4.0), (8.0, 8.0)], + ) + .unwrap(); + let projection_x = Projection::from_control_points( + [(0.0, 0.0), (16.0, 0.0), (0.0, 16.0), (16.0, 16.0)], + [(0.0, 4.0), (8.0, 8.0), (0.0, 12.0), (8.0, 16.0)], + ) + .unwrap(); + let projection_z = Projection::from_control_points( + [(0.0, 0.0), (16.0, 0.0), (0.0, 16.0), (16.0, 16.0)], + [(8.0, 8.0), (16.0, 4.0), (8.0, 16.0), (16.0, 12.0)], + ) + .unwrap(); + + let face_x = imageproc::geometric_transformations::warp( + &side, + &projection_x, + imageproc::geometric_transformations::Interpolation::Nearest, + Rgba::from([0, 0, 0, 0]), + ); + let face_y = imageproc::geometric_transformations::warp( + &top, + &projection_y, + imageproc::geometric_transformations::Interpolation::Nearest, + Rgba::from([0, 0, 0, 0]), + ); + let face_z = imageproc::geometric_transformations::warp( + &side, + &projection_z, + imageproc::geometric_transformations::Interpolation::Nearest, + Rgba::from([0, 0, 0, 0]), + ); + + composite_block_faces(&vec![face_x, face_y, face_z]) +} + +pub fn crossed_planes(tex: &Texture) -> Texture { + let projection = Projection::scale(0.6, 0.6); + + imageproc::geometric_transformations::warp( + &tex, + &projection, + imageproc::geometric_transformations::Interpolation::Nearest, + Rgba::from([0, 0, 0, 0]), + ) +} + +pub fn composite_block_faces(faces: &Vec<Texture>) -> Texture { + let mut buf = image::ImageBuffer::new(16, 16); + for (x, y, pixel) in buf.enumerate_pixels_mut() { + let composite: (u16, u16, u16, u16) = faces + .iter() + .map(|f| { + let p = f.get_pixel(x, y).0; + (p[0] as u16, p[1] as u16, p[2] as u16, p[3] as u16) + }) + .fold((0, 0, 0, 0), |a, v| { + ( + (a.0 * (255 - v.3)) / 255 + (v.0 * v.3) / 255, + (a.1 * (255 - v.3)) / 255 + (v.1 * v.3) / 255, + (a.2 * (255 - v.3)) / 255 + (v.2 * v.3) / 255, + a.3 + v.3, + ) + }); + *pixel = Rgba::from([ + composite.0.min(255) as u8, + composite.1.min(255) as u8, + composite.2.min(255) as u8, + composite.3.min(255) as u8, + ]) + } + return buf; +} |