aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs87
-rw-r--r--src/render/composite.rs47
-rw-r--r--src/render/mod.rs3
-rw-r--r--src/render/models.rs36
-rw-r--r--src/render/processing.rs134
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;
+}