diff --git a/gaiku-3d/examples/common/mod.rs b/gaiku-3d/examples/common/mod.rs index cb1539a..02c223b 100644 --- a/gaiku-3d/examples/common/mod.rs +++ b/gaiku-3d/examples/common/mod.rs @@ -1,3 +1,45 @@ +use std::time::Instant; + +use gaiku_3d::common::{Baker, Chunk, FileFormat}; + mod exporter; pub use self::exporter::export; + +pub fn bake(name: &str, chunks: Vec, now: Instant, suffix: &str) +where + T: Baker, +{ + let mut meshes = vec![]; + + let reader_elapsed = now.elapsed().as_micros(); + let now = Instant::now(); + + for chunk in chunks.iter() { + let mesh = T::bake(chunk); + if let Some(mesh) = mesh { + meshes.push((mesh, chunk.position())); + } + } + + let baker_elapsed = now.elapsed().as_micros(); + + export(meshes, &format!("{}_{}", name, suffix)); + + println!( + "\t<<{}>>\n\t\tChunk count: {}\n\t\tReader: {} micros\n\t\tBaker: {} micros", + name, + chunks.len(), + reader_elapsed, + baker_elapsed, + ); +} + +pub fn read(name: &str, suffix: &str) +where + F: FileFormat, + B: Baker, +{ + let file = format!("{}/examples/assets/{}", env!("CARGO_MANIFEST_DIR"), name); + bake::(name, F::read(&file), Instant::now(), suffix); +} diff --git a/gaiku-3d/examples/heightmap.rs b/gaiku-3d/examples/heightmap.rs index 41c9490..d61dbfc 100644 --- a/gaiku-3d/examples/heightmap.rs +++ b/gaiku-3d/examples/heightmap.rs @@ -1,54 +1,11 @@ -use std::time::Instant; - -use gaiku_3d::{ - bakers::HeightMapBaker, - common::{Baker, FileFormat}, - formats::PNGReader, -}; +use gaiku_3d::{bakers::HeightMapBaker, formats::PNGReader}; mod common; -use crate::common::export; - -fn read(name: &str) -> std::io::Result<()> { - let now = Instant::now(); - let file = format!( - "{}/examples/assets/{}.png", - env!("CARGO_MANIFEST_DIR"), - name - ); - let chunks = PNGReader::read(&file); - let mut meshes = vec![]; - - let reader_elapsed = now.elapsed().as_secs(); - let now = Instant::now(); - - for chunk in chunks.iter() { - let mesh = HeightMapBaker::bake(chunk); - if let Some(mesh) = mesh { - meshes.push((mesh, chunk.position())); - } - } - - let baker_elapsed = now.elapsed().as_secs(); - let now = Instant::now(); - - export(meshes, &format!("{}_png", name)); - - println!( - "<<{}>> Chunks: {} Reader: {} Baker: {} secs Export: {} secs", - name, - chunks.len(), - reader_elapsed, - baker_elapsed, - now.elapsed().as_secs() - ); - - Ok(()) -} - -fn main() -> std::io::Result<()> { - let _ = read("heightmap"); +use common::read; - Ok(()) +fn main() { + println!("\nHeightmap"); + println!("---------------------------"); + read::("heightmap.png", "hm"); } diff --git a/gaiku-3d/examples/marching_cubes.rs b/gaiku-3d/examples/marching_cubes.rs index 1c61128..a57746f 100644 --- a/gaiku-3d/examples/marching_cubes.rs +++ b/gaiku-3d/examples/marching_cubes.rs @@ -1,56 +1,13 @@ -use std::time::Instant; - -use gaiku_3d::{ - bakers::MarchingCubesBaker, - common::{Baker, FileFormat}, - formats::GoxReader, -}; +use gaiku_3d::{bakers::MarchingCubesBaker, formats::GoxReader}; mod common; -use crate::common::export; - -fn read(name: &str) -> std::io::Result<()> { - let now = Instant::now(); - let file = format!( - "{}/examples/assets/{}.gox", - env!("CARGO_MANIFEST_DIR"), - name - ); - let chunks = GoxReader::read(&file); - let mut meshes = vec![]; - - let reader_elapsed = now.elapsed().as_secs(); - let now = Instant::now(); - - for chunk in chunks.iter() { - let mesh = MarchingCubesBaker::bake(chunk); - if let Some(mesh) = mesh { - meshes.push((mesh, chunk.position())); - } - } - - let baker_elapsed = now.elapsed().as_secs(); - let now = Instant::now(); - - export(meshes, &format!("{}_mc", name)); - - println!( - "<<{}>> Chunks: {} Reader: {} Baker: {} secs Export: {} secs", - name, - chunks.len(), - reader_elapsed, - baker_elapsed, - now.elapsed().as_secs() - ); - - Ok(()) -} - -fn main() -> std::io::Result<()> { - let _ = read("small_tree"); - let _ = read("terrain"); - let _ = read("planet"); +use common::read; - Ok(()) +fn main() { + println!("\nMarching cubes"); + println!("---------------------------"); + read::("small_tree.gox", "mc"); + read::("terrain.gox", "mc"); + read::("planet.gox", "mc"); } diff --git a/gaiku-3d/examples/voxel.rs b/gaiku-3d/examples/voxel.rs index 4e3e8a3..17188d1 100644 --- a/gaiku-3d/examples/voxel.rs +++ b/gaiku-3d/examples/voxel.rs @@ -1,56 +1,61 @@ use std::time::Instant; use gaiku_3d::{ - bakers::VoxelBaker, - common::{Baker, FileFormat}, + bakers::voxel::{GreedyMeshingBaker, LinearBaker, PyramidBaker}, + common::{Baker, Chunk, Chunkify}, formats::GoxReader, }; mod common; -use crate::common::export; - -fn read(name: &str) -> std::io::Result<()> { - let now = Instant::now(); - let file = format!( - "{}/examples/assets/{}.gox", - env!("CARGO_MANIFEST_DIR"), - name - ); - let chunks = GoxReader::read(&file); - let mut meshes = vec![]; - - let reader_elapsed = now.elapsed().as_secs(); - let now = Instant::now(); - - for chunk in chunks.iter() { - let mesh = VoxelBaker::bake(chunk); - if let Some(mesh) = mesh { - meshes.push((mesh, chunk.position())); +use common::{bake, read}; + +fn run(algorithm: &str, suffix: &str) +where + T: Baker, +{ + println!("\n{}", algorithm); + println!("---------------------------"); + /* + read::("small_tree.gox", suffix); + read::("terrain.gox", suffix); + read::("planet.gox", suffix); + */ + // Cube + let mut chunk = Chunk::new([0.0, 0.0, 0.0], 16, 16, 16); + + for x in 0..chunk.width() { + for y in 0..chunk.width() { + for z in 0..chunk.width() { + chunk.set(x, y, z, 255); + } } } - let baker_elapsed = now.elapsed().as_secs(); - let now = Instant::now(); + bake::("cube", vec![chunk], Instant::now(), suffix); - export(meshes, &format!("{}_vx", name)); + // Custom chunk + let mut chunk = Chunk::new([0.0, 0.0, 0.0], 16, 16, 16); - println!( - "<<{}>> Chunks: {} Reader: {} Baker: {} secs Export: {} secs", - name, - chunks.len(), - reader_elapsed, - baker_elapsed, - now.elapsed().as_secs() - ); + for x in 0..chunk.width() { + for y in 0..chunk.width() { + for z in 0..chunk.width() { + chunk.set(x, y, z, 255); + } + } + } - Ok(()) -} + chunk.set(2, 0, 0, 0); + chunk.set(0, 1, 0, 0); + chunk.set(3, 1, 0, 0); + chunk.set(4, 1, 0, 0); + chunk.set(1, 3, 0, 0); -fn main() -> std::io::Result<()> { - let _ = read("small_tree"); - let _ = read("terrain"); - let _ = read("planet"); + bake::("custom chunk", vec![chunk], Instant::now(), suffix); +} - Ok(()) +fn main() { + //run::("Linear", "vx_l"); + run::("Greedy meshing", "vx_gm"); + //run::("Pyramid", "vx_p"); } diff --git a/gaiku-3d/src/bakers.rs b/gaiku-3d/src/bakers.rs index d5d14fa..bbe1463 100644 --- a/gaiku-3d/src/bakers.rs +++ b/gaiku-3d/src/bakers.rs @@ -1,5 +1,5 @@ mod heightmap; mod marching_cubes; -mod voxel; +pub mod voxel; -pub use self::{heightmap::HeightMapBaker, marching_cubes::MarchingCubesBaker, voxel::VoxelBaker}; +pub use self::{heightmap::HeightMapBaker, marching_cubes::MarchingCubesBaker}; diff --git a/gaiku-3d/src/bakers/voxel.rs b/gaiku-3d/src/bakers/voxel.rs old mode 100755 new mode 100644 index c1c6a62..00f6ac2 --- a/gaiku-3d/src/bakers/voxel.rs +++ b/gaiku-3d/src/bakers/voxel.rs @@ -1,180 +1,5 @@ -use std::collections::HashMap; +mod greedy_meshing; +mod linear; +mod pyramid; -use gaiku_common::{ - mint::{Vector3, Vector4}, - Baker, Chunk, Chunkify, Mesh, -}; - -pub struct VoxelBaker; - -// TODO: Optimize, don't create faces between chunks if there's a non empty voxel -impl Baker for VoxelBaker { - fn bake(chunk: &Chunk) -> Option { - let mut indices = vec![]; - let mut vertices_cache = HashMap::new(); - // FIXME calculate correctly how many indices we need - let mut colors = vec![ - Vector4 { - x: 0, - y: 0, - z: 0, - w: 0 - }; - chunk.width() * chunk.height() * chunk.depth() - ]; - let x_limit = chunk.width() - 1; - let y_limit = chunk.height() - 1; - let z_limit = chunk.depth() - 1; - - for x in 0..chunk.width() { - let fx = x as f32; - for y in 0..chunk.height() { - let fy = y as f32; - for z in 0..chunk.depth() { - let fz = z as f32; - - let (value, color) = chunk.get_with_color(x, y, z); - if value == 0 { - continue; - } - - let top_left_back = - Self::index(&mut vertices_cache, [fx - 0.5, fy + 0.5, fz - 0.5].into()); - let top_right_back = - Self::index(&mut vertices_cache, [fx + 0.5, fy + 0.5, fz - 0.5].into()); - let top_right_front = - Self::index(&mut vertices_cache, [fx + 0.5, fy + 0.5, fz + 0.5].into()); - let top_left_front = - Self::index(&mut vertices_cache, [fx - 0.5, fy + 0.5, fz + 0.5].into()); - let bottom_left_back = - Self::index(&mut vertices_cache, [fx - 0.5, fy - 0.5, fz - 0.5].into()); - let bottom_right_back = - Self::index(&mut vertices_cache, [fx + 0.5, fy - 0.5, fz - 0.5].into()); - let bottom_right_front = - Self::index(&mut vertices_cache, [fx + 0.5, fy - 0.5, fz + 0.5].into()); - let bottom_left_front = - Self::index(&mut vertices_cache, [fx - 0.5, fy - 0.5, fz + 0.5].into()); - - // Top - if y == y_limit || chunk.is_air(x, y + 1, z) { - // indices.push(top_left_back); - // indices.push(top_right_back); - // indices.push(top_left_front); - - // indices.push(top_right_back); - // indices.push(top_right_front); - // indices.push(top_left_front); - create_face( - &mut indices, - &mut colors, - top_left_back, - top_right_back, - top_right_front, - top_left_front, - color, - ); - } - - // Bottom - if y == 0 || (y > 0 && chunk.is_air(x, y - 1, z)) { - create_face( - &mut indices, - &mut colors, - bottom_right_back, - bottom_left_back, - bottom_left_front, - bottom_right_front, - color, - ); - } - - // Left - if x == 0 || (x > 0 && chunk.is_air(x - 1, y, z)) { - create_face( - &mut indices, - &mut colors, - top_left_back, - top_left_front, - bottom_left_front, - bottom_left_back, - color, - ); - } - - // Right - if x == x_limit || chunk.is_air(x + 1, y, z) { - create_face( - &mut indices, - &mut colors, - top_right_front, - top_right_back, - bottom_right_back, - bottom_right_front, - color, - ); - } - - // Front - if z == z_limit || chunk.is_air(x, y, z + 1) { - create_face( - &mut indices, - &mut colors, - top_left_front, - top_right_front, - bottom_right_front, - bottom_left_front, - color, - ); - } - - // Back - if z == 0 || chunk.is_air(x, y, z - 1) { - create_face( - &mut indices, - &mut colors, - top_right_back, - top_left_back, - bottom_left_back, - bottom_right_back, - color, - ); - } - } - } - } - - let mut vertices: Vec> = vec![[0.0, 0.0, 0.0].into(); vertices_cache.len()]; - for (_, (vertex, index)) in vertices_cache { - vertices[index as usize] = vertex; - } - - if !indices.is_empty() { - let end = vertices.len(); - Some(Mesh { - indices, - vertices, - normals: vec![], - colors: colors[0..end].iter().copied().collect::>(), - uv: vec![], - tangents: vec![], - }) - } else { - None - } - } -} - -fn create_face( - indices: &mut Vec, - colors: &mut Vec>, - p1: u16, - p2: u16, - p3: u16, - p4: u16, - color: Vector4, -) { - [p1, p4, p2, p2, p4, p3].iter().for_each(|i| { - indices.push(*i); - colors.insert((*i) as usize, color) - }); -} +pub use self::{greedy_meshing::GreedyMeshingBaker, linear::LinearBaker, pyramid::PyramidBaker}; diff --git a/gaiku-3d/src/bakers/voxel/greedy_meshing.rs b/gaiku-3d/src/bakers/voxel/greedy_meshing.rs new file mode 100644 index 0000000..8182f4d --- /dev/null +++ b/gaiku-3d/src/bakers/voxel/greedy_meshing.rs @@ -0,0 +1,223 @@ +use std::collections::HashMap; + +use gaiku_common::{ + mint::{Vector3, Vector4}, + Baker, Chunk, Chunkify, Mesh, +}; + +pub struct GreedyMeshingBaker; + +impl Baker for GreedyMeshingBaker { + fn bake(chunk: &Chunk) -> Option { + let mut indices = vec![]; + let mut vertices_cache: HashMap, u16)> = HashMap::new(); + // FIXME calculate correctly how many indices we need + let mut colors = vec![ + Vector4 { + x: 0, + y: 0, + z: 0, + w: 0 + }; + chunk.width() * chunk.height() * chunk.depth() + ]; + + let dimension = [chunk.width(), chunk.width(), chunk.height()]; + + // Sweep over each axis (X, Y and Z) + for axis in 0..3 { + let mut i = 0; + let u = (axis + 1) % 3; + let v = (axis + 2) % 3; + let mut position: [i32; 3] = [0; 3]; + let mut direction: [usize; 3] = [0; 3]; + + let mut mask = vec![false; dimension[u] * dimension[v]]; + position[axis] = -1; + direction[axis] = 1; + + // Check each slice of the chunk one at the time + while position[axis] < dimension[axis] as i32 { + let mut compute_mask = 0; + + for _v in 0..dimension[v] { + position[v] = _v as i32; + for _u in 0..dimension[u] { + position[u] = _u as i32; + // q determines the direction (x, y or z) that we are searching + + let block_current = if position[axis] >= 0 { + chunk.get( + position[0] as usize, + position[1] as usize, + position[2] as usize, + ) > 0 + } else { + false + }; + println!( + "{} + {} = {}", + position[axis], + dimension[axis], + position[axis] + dimension[axis] as i32 + ); + let block_compare = if position[axis] + dimension[axis] as i32 >= 0 + && position[axis] < dimension[axis] as i32 - 1 + { + chunk.get( + (position[0] + direction[0] as i32) as usize, + (position[1] + direction[1] as i32) as usize, + (position[2] + direction[2] as i32) as usize, + ) > 0 + } else { + false + }; + + // The mask is set to true if there is a visible face between two blocks, + // i.e. both aren't empty and both aren't blocks + mask[compute_mask] = block_current != block_compare; + compute_mask += 1; + } + } + + position[axis] += 1; + compute_mask = 0; + + // Generate a mesh from the mask using lexicographic ordering, + // by looping over each block in this slice of the chunk + for j in 0..dimension[v] { + while i < dimension[u] { + if mask[compute_mask] { + // Compute the width of this quad and store it in `width` + // this is done by searching along the current axis until mask[compute_mask + width] is false + let mut width = 1; + let mut height = 1; + + while i + width < dimension[u] && mask[compute_mask + width] { + width += 1; + } + + // Compute the height of this quad and store it in `height` + // This is done by checking every block next to this row (range 0 to width) is also part of the mask. + // For example, if width is 5 we currently have a quad of dimension 1 x 5. To reduce triangle count, + // greedy meshing will attempt to expand this quad out to chunk_size x 5, but will stop if it reaches a hole in the mask. + 'outer: while height < dimension[v] { + // Check each block next to this quad + for k in 0..width { + // If there's a hole in the mask, exit + if !mask[compute_mask + k + height * dimension[u]] { + break 'outer; + } + } + + height += 1; + } + + position[u] = i as i32; + position[v] = j as i32; + + // axis_u and axis_v determine the size and orientation of this face + let mut axis_u = [0; 3]; + axis_u[u] = width; + + let mut axis_v = [0; 3]; + axis_v[v] = height; + + create_face( + &mut indices, + &mut colors, + Self::index( + &mut vertices_cache, + to_vector3([ + position[0] as usize, + position[1] as usize, + position[2] as usize, + ]), + ), + Self::index( + &mut vertices_cache, + to_vector3([ + position[0] as usize + axis_u[0], + position[1] as usize + axis_u[1], + position[2] as usize + axis_u[2], + ]), + ), + Self::index( + &mut vertices_cache, + to_vector3([ + position[0] as usize + axis_u[0] + axis_v[0], + position[1] as usize + axis_u[1] + axis_v[1], + position[2] as usize + axis_u[2] + axis_v[2], + ]), + ), + Self::index( + &mut vertices_cache, + to_vector3([ + position[0] as usize + axis_v[0], + position[1] as usize + axis_v[1], + position[2] as usize + axis_v[2], + ]), + ), + [255, 255, 255, 255].into(), + ); + + // Clear this part of the mask, so we don't add duplicate faces + for h in 0..height { + for w in 0..width { + mask[compute_mask + w + h * dimension[u]] = false; + } + } + + // Increment counters and continue + i += width; + compute_mask += width; + } else { + i += 1; + compute_mask += 1; + } + } + } + } + } + + let mut vertices: Vec> = vec![[0.0, 0.0, 0.0].into(); vertices_cache.len()]; + for (_, (vertex, index)) in vertices_cache { + vertices[index as usize] = vertex; + } + + println!("{} {}", vertices.len(), indices.len()); + + if !indices.is_empty() { + let end = vertices.len(); + Some(Mesh { + indices, + vertices, + normals: vec![], + colors: colors[0..end].iter().copied().collect::>(), + uv: vec![], + tangents: vec![], + }) + } else { + None + } + } +} + +fn to_vector3(a: [usize; 3]) -> Vector3 { + [a[0] as f32, a[1] as f32, a[2] as f32].into() +} + +fn create_face( + indices: &mut Vec, + colors: &mut Vec>, + p1: u16, + p2: u16, + p3: u16, + p4: u16, + color: Vector4, +) { + [p1, p4, p2, p2, p4, p3].iter().for_each(|i| { + indices.push(*i); + colors.insert((*i) as usize, color) + }); +} diff --git a/gaiku-3d/src/bakers/voxel/linear.rs b/gaiku-3d/src/bakers/voxel/linear.rs new file mode 100755 index 0000000..1203238 --- /dev/null +++ b/gaiku-3d/src/bakers/voxel/linear.rs @@ -0,0 +1,179 @@ +use std::collections::HashMap; + +use gaiku_common::{ + mint::{Vector3, Vector4}, + Baker, Chunk, Chunkify, Mesh, +}; + +pub struct LinearBaker; + +impl Baker for LinearBaker { + fn bake(chunk: &Chunk) -> Option { + let mut indices = vec![]; + let mut vertices_cache = HashMap::new(); + // FIXME calculate correctly how many indices we need + let mut colors = vec![ + Vector4 { + x: 0, + y: 0, + z: 0, + w: 0 + }; + chunk.width() * chunk.height() * chunk.depth() + ]; + let x_limit = chunk.width() - 1; + let y_limit = chunk.height() - 1; + let z_limit = chunk.depth() - 1; + + for x in 0..chunk.width() { + let fx = x as f32; + for y in 0..chunk.height() { + let fy = y as f32; + for z in 0..chunk.depth() { + let fz = z as f32; + + let (value, color) = chunk.get_with_color(x, y, z); + if value == 0 { + continue; + } + + let top_left_back = + Self::index(&mut vertices_cache, [fx - 0.5, fy + 0.5, fz - 0.5].into()); + let top_right_back = + Self::index(&mut vertices_cache, [fx + 0.5, fy + 0.5, fz - 0.5].into()); + let top_right_front = + Self::index(&mut vertices_cache, [fx + 0.5, fy + 0.5, fz + 0.5].into()); + let top_left_front = + Self::index(&mut vertices_cache, [fx - 0.5, fy + 0.5, fz + 0.5].into()); + let bottom_left_back = + Self::index(&mut vertices_cache, [fx - 0.5, fy - 0.5, fz - 0.5].into()); + let bottom_right_back = + Self::index(&mut vertices_cache, [fx + 0.5, fy - 0.5, fz - 0.5].into()); + let bottom_right_front = + Self::index(&mut vertices_cache, [fx + 0.5, fy - 0.5, fz + 0.5].into()); + let bottom_left_front = + Self::index(&mut vertices_cache, [fx - 0.5, fy - 0.5, fz + 0.5].into()); + + // Top + if y == y_limit || chunk.is_air(x, y + 1, z) { + // indices.push(top_left_back); + // indices.push(top_right_back); + // indices.push(top_left_front); + + // indices.push(top_right_back); + // indices.push(top_right_front); + // indices.push(top_left_front); + create_face( + &mut indices, + &mut colors, + top_left_back, + top_right_back, + top_right_front, + top_left_front, + color, + ); + } + + // Bottom + if y == 0 || (y > 0 && chunk.is_air(x, y - 1, z)) { + create_face( + &mut indices, + &mut colors, + bottom_right_back, + bottom_left_back, + bottom_left_front, + bottom_right_front, + color, + ); + } + + // Left + if x == 0 || (x > 0 && chunk.is_air(x - 1, y, z)) { + create_face( + &mut indices, + &mut colors, + top_left_back, + top_left_front, + bottom_left_front, + bottom_left_back, + color, + ); + } + + // Right + if x == x_limit || chunk.is_air(x + 1, y, z) { + create_face( + &mut indices, + &mut colors, + top_right_front, + top_right_back, + bottom_right_back, + bottom_right_front, + color, + ); + } + + // Front + if z == z_limit || chunk.is_air(x, y, z + 1) { + create_face( + &mut indices, + &mut colors, + top_left_front, + top_right_front, + bottom_right_front, + bottom_left_front, + color, + ); + } + + // Back + if z == 0 || chunk.is_air(x, y, z - 1) { + create_face( + &mut indices, + &mut colors, + top_right_back, + top_left_back, + bottom_left_back, + bottom_right_back, + color, + ); + } + } + } + } + + let mut vertices: Vec> = vec![[0.0, 0.0, 0.0].into(); vertices_cache.len()]; + for (_, (vertex, index)) in vertices_cache { + vertices[index as usize] = vertex; + } + + if !indices.is_empty() { + let end = vertices.len(); + Some(Mesh { + indices, + vertices, + normals: vec![], + colors: colors[0..end].iter().copied().collect::>(), + uv: vec![], + tangents: vec![], + }) + } else { + None + } + } +} + +fn create_face( + indices: &mut Vec, + colors: &mut Vec>, + p1: u16, + p2: u16, + p3: u16, + p4: u16, + color: Vector4, +) { + [p1, p4, p2, p2, p4, p3].iter().for_each(|i| { + indices.push(*i); + colors.insert((*i) as usize, color) + }); +} diff --git a/gaiku-3d/src/bakers/voxel/pyramid.rs b/gaiku-3d/src/bakers/voxel/pyramid.rs new file mode 100755 index 0000000..9e6b1bd --- /dev/null +++ b/gaiku-3d/src/bakers/voxel/pyramid.rs @@ -0,0 +1,306 @@ +use std::collections::HashMap; + +use gaiku_common::{ + mint::{Vector3, Vector4}, + Baker, Chunk, Chunkify, Mesh, +}; + +pub struct PyramidBaker; + +// TODO: Optimize, don't create faces between chunks if there's a non empty voxel +impl Baker for PyramidBaker { + fn bake(chunk: &Chunk) -> Option { + let mut indices = vec![]; + let mut vertices_cache = HashMap::new(); + // FIXME calculate correctly how many indices we need + let mut colors = vec![ + Vector4 { + x: 0, + y: 0, + z: 0, + w: 0 + }; + chunk.width() * chunk.height() * chunk.depth() + ]; + let x_limit = chunk.width() - 1; + let y_limit = chunk.height() - 1; + let z_limit = chunk.depth() - 1; + + let half_x = chunk.width() / 2; + let half_y = chunk.height() / 2; + let half_z = chunk.depth() / 2; + + // Top + piramid_loop(chunk.width(), chunk.depth(), half_y, |x, z, y| { + create_faces( + chunk, + &mut vertices_cache, + &mut indices, + &mut colors, + x_limit, + y_limit, + z_limit, + x_limit - x, + y_limit - y, + z_limit - z, + ) + }); + + // Bottom + piramid_loop(chunk.width(), chunk.depth(), half_y, |x, z, y| { + create_faces( + chunk, + &mut vertices_cache, + &mut indices, + &mut colors, + x_limit, + y_limit, + z_limit, + x, + y, + z, + ) + }); + + // Left + piramid_loop(chunk.height(), chunk.depth(), half_x, |y, z, x| { + create_faces( + chunk, + &mut vertices_cache, + &mut indices, + &mut colors, + x_limit, + y_limit, + z_limit, + x_limit - x, + y_limit - y, + z_limit - z, + ) + }); + + // Left + piramid_loop(chunk.height(), chunk.depth(), half_x, |y, z, x| { + create_faces( + chunk, + &mut vertices_cache, + &mut indices, + &mut colors, + x_limit, + y_limit, + z_limit, + x, + y, + z, + ) + }); + + // Front + piramid_loop(chunk.width(), chunk.height(), half_z, |x, y, z| { + create_faces( + chunk, + &mut vertices_cache, + &mut indices, + &mut colors, + x_limit, + y_limit, + z_limit, + x, + y, + z, + ) + }); + + // Back + piramid_loop(chunk.width(), chunk.height(), half_z, |x, y, z| { + create_faces( + chunk, + &mut vertices_cache, + &mut indices, + &mut colors, + x_limit, + y_limit, + z_limit, + x_limit - x, + y_limit - y, + z_limit - z, + ) + }); + + let mut vertices: Vec> = vec![[0.0, 0.0, 0.0].into(); vertices_cache.len()]; + for (_, (vertex, index)) in vertices_cache { + vertices[index as usize] = vertex; + } + + if !indices.is_empty() { + let end = vertices.len(); + Some(Mesh { + indices, + vertices, + normals: vec![], + colors: colors[0..end].iter().copied().collect::>(), + uv: vec![], + tangents: vec![], + }) + } else { + None + } + } +} + +fn piramid_loop(max_x: usize, max_y: usize, max_level: usize, mut callback: F) +where + F: FnMut(usize, usize, usize) -> u8, +{ + let mut current_level = 0; + let mut is_solid = true; + + while current_level < max_level { + for x in current_level..max_x - current_level { + for y in current_level..max_y - current_level { + let value = callback(x, y, current_level); + + if value == 0 { + is_solid = false; + } + } + } + + if is_solid { + break; + } + + current_level += 1; + } +} + +fn create_faces( + chunk: &Chunk, + vertices_cache: &mut HashMap, u16)>, + indices: &mut Vec, + colors: &mut Vec>, + x_limit: usize, + y_limit: usize, + z_limit: usize, + x: usize, + y: usize, + z: usize, +) -> u8 { + let (value, color) = chunk.get_with_color(x, y, z); + if value == 0 { + return 0; + } + + let fx = x as f32; + let fy = y as f32; + let fz = z as f32; + + let top_left_back = index(vertices_cache, [fx - 0.5, fy + 0.5, fz - 0.5].into()); + let top_right_back = index(vertices_cache, [fx + 0.5, fy + 0.5, fz - 0.5].into()); + let top_right_front = index(vertices_cache, [fx + 0.5, fy + 0.5, fz + 0.5].into()); + let top_left_front = index(vertices_cache, [fx - 0.5, fy + 0.5, fz + 0.5].into()); + let bottom_left_back = index(vertices_cache, [fx - 0.5, fy - 0.5, fz - 0.5].into()); + let bottom_right_back = index(vertices_cache, [fx + 0.5, fy - 0.5, fz - 0.5].into()); + let bottom_right_front = index(vertices_cache, [fx + 0.5, fy - 0.5, fz + 0.5].into()); + let bottom_left_front = index(vertices_cache, [fx - 0.5, fy - 0.5, fz + 0.5].into()); + + // Top + if y == y_limit || chunk.is_air(x, y + 1, z) { + create_face( + indices, + colors, + top_left_back, + top_right_back, + top_right_front, + top_left_front, + color, + ); + } + + // Bottom + if y == 0 || (y > 0 && chunk.is_air(x, y - 1, z)) { + create_face( + indices, + colors, + bottom_right_back, + bottom_left_back, + bottom_left_front, + bottom_right_front, + color, + ); + } + + // Left + if x == 0 || (x > 0 && chunk.is_air(x - 1, y, z)) { + create_face( + indices, + colors, + top_left_back, + top_left_front, + bottom_left_front, + bottom_left_back, + color, + ); + } + + // Right + if x == x_limit || chunk.is_air(x + 1, y, z) { + create_face( + indices, + colors, + top_right_front, + top_right_back, + bottom_right_back, + bottom_right_front, + color, + ); + } + + // Front + if z == z_limit || chunk.is_air(x, y, z + 1) { + create_face( + indices, + colors, + top_left_front, + top_right_front, + bottom_right_front, + bottom_left_front, + color, + ); + } + + // Back + if z == 0 || chunk.is_air(x, y, z - 1) { + create_face( + indices, + colors, + top_right_back, + top_left_back, + bottom_left_back, + bottom_right_back, + color, + ); + } + + value +} + +fn index(vertices: &mut HashMap, u16)>, vertex: Vector3) -> u16 { + let index = vertices.len(); + let key = format!("{:?}", vertex); + vertices.entry(key).or_insert((vertex, index as u16)).1 +} + +fn create_face( + indices: &mut Vec, + colors: &mut Vec>, + p1: u16, + p2: u16, + p3: u16, + p4: u16, + color: Vector4, +) { + [p1, p4, p2, p2, p4, p3].iter().for_each(|i| { + indices.push(*i); + colors.insert((*i) as usize, color) + }); +} diff --git a/gaiku-3d/src/formats/png.rs b/gaiku-3d/src/formats/png.rs index 4c76577..a40cb98 100755 --- a/gaiku-3d/src/formats/png.rs +++ b/gaiku-3d/src/formats/png.rs @@ -13,6 +13,7 @@ impl FileFormat for PNGReader { match decoder.read_info() { Ok((info, mut reader)) => { + /* println!( "PNG w: {} h: {} bit_depth: {:?} buffer_size: {} color_type: {:?}", info.width, @@ -21,6 +22,7 @@ impl FileFormat for PNGReader { info.buffer_size(), info.color_type ); + */ let mut buf = vec![0; info.buffer_size()]; @@ -49,7 +51,7 @@ impl FileFormat for PNGReader { }; let mut colors = vec![[0; 4]; (info.width * info.height) as usize]; - let now = std::time::Instant::now(); + //let now = std::time::Instant::now(); for (i, color) in data.chunks(4).enumerate() { if color.len() == 3 { colors[i] = [color[0], color[1], color[2], 255]; @@ -57,7 +59,7 @@ impl FileFormat for PNGReader { colors[i] = [color[0], color[1], color[2], color[3]]; } } - println!("elapsed {}", now.elapsed().as_micros()); + //println!("elapsed {}", now.elapsed().as_micros()); let mut chunk = Chunk::new( [0.0, 0.0, 0.0], diff --git a/gaiku-common/src/data/chunk.rs b/gaiku-common/src/data/chunk.rs index 4c2f876..02af863 100755 --- a/gaiku-common/src/data/chunk.rs +++ b/gaiku-common/src/data/chunk.rs @@ -27,12 +27,15 @@ impl Chunk { } pub fn get_with_color(&self, x: usize, y: usize, z: usize) -> (u8, Vector4) { - let index = self.index(x, y, z); - (self.values[index], self.colors[index]) + if let Some(index) = self.index(x, y, z) { + (self.values[index], self.colors[index]) + } else { + (0, [0, 0, 0, 0].into()) + } } - fn index(&self, x: usize, y: usize, z: usize) -> usize { - get_index_from(x, y, z, self.width, self.height) + fn index(&self, x: usize, y: usize, z: usize) -> Option { + get_index_from(x, y, z, self.width, self.height, self.depth) } pub fn position(&self) -> &Vector3 { @@ -48,10 +51,10 @@ impl Chunk { } pub fn set_with_color(&mut self, x: usize, y: usize, z: usize, value: u8, color: Vector4) { - let index = self.index(x, y, z); - - self.values[index] = value; - self.colors[index] = color; + if let Some(index) = self.index(x, y, z) { + self.values[index] = value; + self.colors[index] = color; + } } pub fn update_neighbor_data(&mut self, _neighbor: &Chunk) { @@ -76,15 +79,19 @@ impl Chunkify for Chunk { } fn is_air(&self, x: usize, y: usize, z: usize) -> bool { - if x >= self.width || y >= self.height || z >= self.depth { - true + if let Some(index) = self.index(x, y, z) { + self.values[index] == 0 } else { - self.values[self.index(x, y, z)] == 0 + true } } fn get(&self, x: usize, y: usize, z: usize) -> u8 { - self.values[self.index(x, y, z)] + if let Some(index) = self.index(x, y, z) { + self.values[index] + } else { + 0 + } } fn set(&mut self, x: usize, y: usize, z: usize, value: u8) { @@ -98,6 +105,17 @@ impl Chunkify for Chunk { } } -pub fn get_index_from(x: usize, y: usize, z: usize, width: usize, height: usize) -> usize { - x + y * width + z * width * height +pub fn get_index_from( + x: usize, + y: usize, + z: usize, + width: usize, + height: usize, + deepth: usize, +) -> Option { + if x < width && y < height && z < deepth { + Some(x + y * width + z * width * height) + } else { + None + } } diff --git a/rustfmt.toml b/rustfmt.toml index b196eaa..f111339 100755 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ +max_length = 120 tab_spaces = 2