├── .gitignore ├── examples-crate ├── assets │ └── uv_checker.png ├── render │ ├── mesh_examples.png │ └── main.rs ├── README.md ├── Cargo.toml └── uv_mapping │ └── main.rs ├── bench ├── Cargo.toml ├── .github │ └── workflows │ │ └── ci.yml └── src │ └── bench.rs ├── Cargo.toml ├── LICENSE.MIT ├── src ├── bounds.rs ├── geometry │ ├── quad.rs │ ├── axis.rs │ └── face.rs ├── buffer.rs ├── lib.rs ├── simple.rs ├── greedy │ └── merge_strategy.rs ├── geometry.rs └── greedy.rs ├── README.md └── LICENSE.Apache-2.0 /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples-crate/assets/uv_checker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonsairobo/block-mesh-rs/HEAD/examples-crate/assets/uv_checker.png -------------------------------------------------------------------------------- /examples-crate/render/mesh_examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bonsairobo/block-mesh-rs/HEAD/examples-crate/render/mesh_examples.png -------------------------------------------------------------------------------- /bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "block-mesh-bench" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | block-mesh = { path = ".." } 8 | 9 | [dev-dependencies] 10 | criterion = "0.3" 11 | 12 | [profile.bench] 13 | lto = "thin" 14 | 15 | [[bench]] 16 | name = "bench" 17 | path = "src/bench.rs" 18 | harness = false 19 | -------------------------------------------------------------------------------- /examples-crate/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Git LFS 4 | 5 | Example assets are tracked using [Git LFS](https://git-lfs.github.com/), so make 6 | sure you have that installed and then run: 7 | 8 | ``` 9 | $ git lfs fetch 10 | ``` 11 | 12 | ## Compiling and running 13 | 14 | ``` 15 | $ cd examples-crate 16 | $ cargo run --example render 17 | ``` -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "block-mesh" 3 | description = "Fast algorithms for generating voxel block meshes from arrays." 4 | version = "0.2.0" 5 | repository = "https://github.com/bonsairobo/block-mesh-rs" 6 | keywords = ["graphics", "mesh", "voxel"] 7 | edition = "2021" 8 | license = "MIT OR Apache-2.0" 9 | 10 | [dependencies] 11 | ilattice = "0.1" 12 | ndshape = "0.3" 13 | ndcopy = "0.3" 14 | -------------------------------------------------------------------------------- /bench/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | bench: 7 | name: Bench 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: bench 19 | args: --manifest-path ./bench/Cargo.toml 20 | -------------------------------------------------------------------------------- /examples-crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "block-mesh-examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies.bevy] 7 | version = "0.13" 8 | default-features = false 9 | features = [ 10 | "bevy_asset", 11 | "bevy_core_pipeline", 12 | "bevy_pbr", 13 | "bevy_render", 14 | "bevy_winit", 15 | "png", 16 | "ktx2", 17 | "tonemapping_luts", 18 | "zstd", 19 | ] 20 | 21 | [target.'cfg(target_os = "linux")'.dev-dependencies.bevy] 22 | version = "0.13" 23 | default-features = false 24 | features = [ 25 | "bevy_asset", 26 | "bevy_core_pipeline", 27 | "bevy_pbr", 28 | "bevy_render", 29 | "bevy_winit", 30 | "png", 31 | "ktx2", 32 | "tonemapping_luts", 33 | "x11", 34 | "zstd", 35 | ] 36 | 37 | [dependencies.block-mesh] 38 | path = ".." 39 | 40 | [[example]] 41 | name = "render" 42 | path = "render/main.rs" 43 | 44 | [[example]] 45 | name = "uv_mapping" 46 | path = "uv_mapping/main.rs" 47 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 bonsairobo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/bounds.rs: -------------------------------------------------------------------------------- 1 | use ilattice::glam::UVec3; 2 | use ilattice::prelude::Extent; 3 | use ndshape::Shape; 4 | 5 | pub fn assert_in_bounds(voxels: &[T], voxels_shape: &S, min: [u32; 3], max: [u32; 3]) 6 | where 7 | S: Shape<3, Coord = u32>, 8 | { 9 | assert!( 10 | voxels_shape.size() as usize <= voxels.len(), 11 | "voxel buffer size {:?} is less than the shape size {:?}; would cause access out of bounds", 12 | voxels.len(), 13 | voxels_shape.size() 14 | ); 15 | let shape = voxels_shape.as_array(); 16 | let local_extent = Extent::from_min_and_shape(UVec3::ZERO, UVec3::from(shape)); 17 | local_extent 18 | .check_positive_shape() 19 | .unwrap_or_else(|| panic!("Invalid shape={shape:?}")); 20 | let query_extent = Extent::from_min_and_max(UVec3::from(min), UVec3::from(max)); 21 | query_extent.check_positive_shape().unwrap_or_else(|| { 22 | panic!("Invalid extent min={min:?} max={max:?}; has non-positive shape") 23 | }); 24 | assert!( 25 | query_extent.is_subset_of(&local_extent), 26 | "min={min:?} max={max:?} would access out of bounds" 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/geometry/quad.rs: -------------------------------------------------------------------------------- 1 | /// The minimum voxel and size of a quad, without an orientation. To get the 2 | /// actual corners of the quad, combine with an [`OrientedBlockFace`]. 3 | /// 4 | /// When using these values for materials and lighting, you can access them 5 | /// using either the quad's minimum voxel coordinates or the vertex coordinates 6 | /// given by `OrientedBlockFace::quad_corners`. 7 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 8 | pub struct UnorientedQuad { 9 | /// The minimum voxel in the quad. 10 | pub minimum: [u32; 3], 11 | /// Width of the quad. 12 | pub width: u32, 13 | /// Height of the quad. 14 | pub height: u32, 15 | } 16 | 17 | impl From for UnorientedQuad { 18 | #[inline] 19 | fn from(unit: UnorientedUnitQuad) -> Self { 20 | Self { 21 | minimum: unit.minimum, 22 | width: 1, 23 | height: 1, 24 | } 25 | } 26 | } 27 | 28 | /// A quad covering a single voxel (just a single block face), without an 29 | /// orientation. To get the actual corners of the quad, combine with an 30 | /// [`OrientedBlockFace`]. 31 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 32 | pub struct UnorientedUnitQuad { 33 | /// The minimum voxel in the quad. 34 | pub minimum: [u32; 3], 35 | } 36 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::{UnorientedQuad, UnorientedUnitQuad}; 2 | 3 | #[derive(Default)] 4 | pub struct QuadBuffer { 5 | /// A group of quads for each block face. We rely on [`OrientedBlockFace`] 6 | /// metadata to interpret them. 7 | pub groups: [Vec; 6], 8 | } 9 | 10 | impl QuadBuffer { 11 | pub fn new() -> Self { 12 | const EMPTY: Vec = Vec::new(); 13 | Self { groups: [EMPTY; 6] } 14 | } 15 | 16 | pub fn reset(&mut self) { 17 | for group in self.groups.iter_mut() { 18 | group.clear(); 19 | } 20 | } 21 | 22 | /// Returns the total count of quads across all groups. 23 | pub fn num_quads(&self) -> usize { 24 | let mut sum = 0; 25 | for group in self.groups.iter() { 26 | sum += group.len(); 27 | } 28 | sum 29 | } 30 | } 31 | 32 | #[derive(Default)] 33 | pub struct UnitQuadBuffer { 34 | /// A group of quads for each block face. We rely on [`OrientedBlockFace`] 35 | /// metadata to interpret them. 36 | /// 37 | /// When using these values for materials and lighting, you can access them 38 | /// using either the quad's minimum voxel coordinates or the vertex 39 | /// coordinates given by [`OrientedBlockFace::quad_corners`]. 40 | pub groups: [Vec; 6], 41 | } 42 | 43 | impl UnitQuadBuffer { 44 | pub fn new() -> Self { 45 | const EMPTY: Vec = Vec::new(); 46 | Self { groups: [EMPTY; 6] } 47 | } 48 | 49 | /// Clears the buffer. 50 | pub fn reset(&mut self) { 51 | for group in self.groups.iter_mut() { 52 | group.clear(); 53 | } 54 | } 55 | 56 | /// Returns the total count of quads across all groups. 57 | pub fn num_quads(&self) -> usize { 58 | let mut sum = 0; 59 | for group in self.groups.iter() { 60 | sum += group.len(); 61 | } 62 | sum 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # block-mesh 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/block-mesh.svg)](https://crates.io/crates/block-mesh) 4 | [![Docs.rs](https://docs.rs/block-mesh/badge.svg)](https://docs.rs/block-mesh) 5 | 6 | Fast algorithms for generating voxel block meshes. 7 | 8 | ![Mesh Examples](https://raw.githubusercontent.com/bonsairobo/block-mesh-rs/main/examples-crate/render/mesh_examples.png) 9 | 10 | Two algorithms are included: 11 | - [`visible_block_faces`](crate::visible_block_faces): very fast but suboptimal meshes 12 | - [`greedy_quads`](crate::greedy_quads): not quite as fast, but far fewer triangles are generated 13 | 14 | Benchmarks show that [`visible_block_faces`](crate::visible_block_faces) generates about 40 million quads per second on a 15 | single core of a 2.5 GHz Intel Core i7. Assuming spherical input data, [`greedy_quads`](crate::greedy_quads) can generate a 16 | more optimal version of the same mesh with 1/3 of the quads, but it takes about 3 times longer. To run the benchmarks 17 | yourself, `cd bench/ && cargo bench`. 18 | 19 | ## Example Code 20 | 21 | ```rust 22 | use block_mesh::ndshape::{ConstShape, ConstShape3u32}; 23 | use block_mesh::{greedy_quads, GreedyQuadsBuffer, MergeVoxel, Voxel, VoxelVisibility, RIGHT_HANDED_Y_UP_CONFIG}; 24 | 25 | #[derive(Clone, Copy, Eq, PartialEq)] 26 | struct BoolVoxel(bool); 27 | 28 | const EMPTY: BoolVoxel = BoolVoxel(false); 29 | const FULL: BoolVoxel = BoolVoxel(true); 30 | 31 | impl Voxel for BoolVoxel { 32 | fn get_visibility(&self) -> VoxelVisibility { 33 | if *self == EMPTY { 34 | VoxelVisibility::Empty 35 | } else { 36 | VoxelVisibility::Opaque 37 | } 38 | } 39 | } 40 | 41 | impl MergeVoxel for BoolVoxel { 42 | type MergeValue = Self; 43 | type MergeValueFacingNeighbour = Self; 44 | 45 | fn merge_value(&self) -> Self::MergeValue { 46 | *self 47 | } 48 | 49 | fn merge_value_facing_neighbour(&self) -> Self::MergeValueFacingNeighbour { 50 | *self 51 | } 52 | } 53 | 54 | // A 16^3 chunk with 1-voxel boundary padding. 55 | type ChunkShape = ConstShape3u32<18, 18, 18>; 56 | 57 | // This chunk will cover just a single octant of a sphere SDF (radius 15). 58 | let mut voxels = [EMPTY; ChunkShape::SIZE as usize]; 59 | for i in 0..ChunkShape::SIZE { 60 | let [x, y, z] = ChunkShape::delinearize(i); 61 | voxels[i as usize] = if ((x * x + y * y + z * z) as f32).sqrt() < 15.0 { 62 | FULL 63 | } else { 64 | EMPTY 65 | }; 66 | } 67 | 68 | let mut buffer = GreedyQuadsBuffer::new(voxels.len()); 69 | greedy_quads( 70 | &voxels, 71 | &ChunkShape {}, 72 | [0; 3], 73 | [17; 3], 74 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 75 | &mut buffer 76 | ); 77 | 78 | // Some quads were generated. 79 | assert!(buffer.quads.num_quads() > 0); 80 | ``` 81 | 82 | License: MIT OR Apache-2.0 83 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![Crates.io](https://img.shields.io/crates/v/block-mesh.svg)](https://crates.io/crates/block-mesh) 2 | //! [![Docs.rs](https://docs.rs/block-mesh/badge.svg)](https://docs.rs/block-mesh) 3 | //! 4 | //! Fast algorithms for generating voxel block meshes. 5 | //! 6 | //! ![Mesh Examples](https://raw.githubusercontent.com/bonsairobo/block-mesh-rs/main/examples-crate/render/mesh_examples.png) 7 | //! 8 | //! Two algorithms are included: 9 | //! - [`visible_block_faces`](crate::visible_block_faces): very fast but suboptimal meshes 10 | //! - [`greedy_quads`](crate::greedy_quads): not quite as fast, but far fewer triangles are generated 11 | //! 12 | //! Benchmarks show that [`visible_block_faces`](crate::visible_block_faces) generates about 40 million quads per second on a 13 | //! single core of a 2.5 GHz Intel Core i7. Assuming spherical input data, [`greedy_quads`](crate::greedy_quads) can generate a 14 | //! more optimal version of the same mesh with 1/3 of the quads, but it takes about 3 times longer. To run the benchmarks 15 | //! yourself, `cd bench/ && cargo bench`. 16 | //! 17 | //! # Example Code 18 | //! 19 | //! ``` 20 | //! use block_mesh::ndshape::{ConstShape, ConstShape3u32}; 21 | //! use block_mesh::{greedy_quads, GreedyQuadsBuffer, MergeVoxel, Voxel, VoxelVisibility, RIGHT_HANDED_Y_UP_CONFIG}; 22 | //! 23 | //! #[derive(Clone, Copy, Eq, PartialEq)] 24 | //! struct BoolVoxel(bool); 25 | //! 26 | //! const EMPTY: BoolVoxel = BoolVoxel(false); 27 | //! const FULL: BoolVoxel = BoolVoxel(true); 28 | //! 29 | //! impl Voxel for BoolVoxel { 30 | //! fn get_visibility(&self) -> VoxelVisibility { 31 | //! if *self == EMPTY { 32 | //! VoxelVisibility::Empty 33 | //! } else { 34 | //! VoxelVisibility::Opaque 35 | //! } 36 | //! } 37 | //! } 38 | //! 39 | //! impl MergeVoxel for BoolVoxel { 40 | //! type MergeValue = Self; 41 | //! type MergeValueFacingNeighbour = Self; 42 | //! 43 | //! fn merge_value(&self) -> Self::MergeValue { 44 | //! *self 45 | //! } 46 | //! 47 | //! fn merge_value_facing_neighbour(&self) -> Self::MergeValueFacingNeighbour { 48 | //! *self 49 | //! } 50 | //! } 51 | //! 52 | //! // A 16^3 chunk with 1-voxel boundary padding. 53 | //! type ChunkShape = ConstShape3u32<18, 18, 18>; 54 | //! 55 | //! // This chunk will cover just a single octant of a sphere SDF (radius 15). 56 | //! let mut voxels = [EMPTY; ChunkShape::SIZE as usize]; 57 | //! for i in 0..ChunkShape::SIZE { 58 | //! let [x, y, z] = ChunkShape::delinearize(i); 59 | //! voxels[i as usize] = if ((x * x + y * y + z * z) as f32).sqrt() < 15.0 { 60 | //! FULL 61 | //! } else { 62 | //! EMPTY 63 | //! }; 64 | //! } 65 | //! 66 | //! let mut buffer = GreedyQuadsBuffer::new(voxels.len()); 67 | //! greedy_quads( 68 | //! &voxels, 69 | //! &ChunkShape {}, 70 | //! [0; 3], 71 | //! [17; 3], 72 | //! &RIGHT_HANDED_Y_UP_CONFIG.faces, 73 | //! &mut buffer 74 | //! ); 75 | //! 76 | //! // Some quads were generated. 77 | //! assert!(buffer.quads.num_quads() > 0); 78 | //! ``` 79 | 80 | mod bounds; 81 | mod buffer; 82 | pub mod geometry; 83 | mod greedy; 84 | mod simple; 85 | 86 | pub use buffer::*; 87 | #[doc(inline)] 88 | pub use geometry::*; 89 | pub use greedy::*; 90 | pub use simple::*; 91 | 92 | pub use ilattice; 93 | pub use ndshape; 94 | 95 | /// Describes how this voxel influences mesh generation. 96 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 97 | pub enum VoxelVisibility { 98 | /// This voxel should not produce any geometry. 99 | Empty, 100 | /// Should produce geometry, and also light can pass through. 101 | Translucent, 102 | /// Light cannot pass through this voxel. 103 | Opaque, 104 | } 105 | 106 | /// Implement on your voxel types to inform the library 107 | /// how to generate geometry for this voxel. 108 | pub trait Voxel { 109 | fn get_visibility(&self) -> VoxelVisibility; 110 | } 111 | 112 | /// Used as a dummy for functions that must wrap a voxel 113 | /// but don't want to change the original's properties. 114 | struct IdentityVoxel<'a, T: Voxel>(&'a T); 115 | 116 | impl<'a, T: Voxel> Voxel for IdentityVoxel<'a, T> { 117 | #[inline] 118 | fn get_visibility(&self) -> VoxelVisibility { 119 | self.0.get_visibility() 120 | } 121 | } 122 | 123 | impl<'a, T: Voxel> From<&'a T> for IdentityVoxel<'a, T> { 124 | fn from(voxel: &'a T) -> Self { 125 | Self(voxel) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/simple.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bounds::assert_in_bounds, IdentityVoxel, OrientedBlockFace, UnitQuadBuffer, UnorientedUnitQuad, Voxel, VoxelVisibility, 3 | }; 4 | 5 | use ilattice::glam::UVec3; 6 | use ilattice::prelude::Extent; 7 | use ndshape::Shape; 8 | 9 | 10 | /// A fast and simple meshing algorithm that produces a single quad for every visible face of a block. 11 | /// 12 | /// This is faster than [`greedy_quads`](crate::greedy_quads) but it produces many more quads. 13 | pub fn visible_block_faces( 14 | voxels: &[T], 15 | voxels_shape: &S, 16 | min: [u32; 3], 17 | max: [u32; 3], 18 | faces: &[OrientedBlockFace; 6], 19 | output: &mut UnitQuadBuffer, 20 | ) where 21 | T: Voxel, 22 | S: Shape<3, Coord = u32>, 23 | { 24 | visible_block_faces_with_voxel_view::<_, IdentityVoxel, _>( 25 | voxels, 26 | voxels_shape, 27 | min, 28 | max, 29 | faces, 30 | output, 31 | ) 32 | } 33 | 34 | /// Same as [`visible_block_faces`](visible_block_faces), 35 | /// with the additional ability to interpret the array as some other type. 36 | /// Use this if you want to mesh the same array multiple times 37 | /// with different sets of voxels being visible. 38 | pub fn visible_block_faces_with_voxel_view<'a, T, V, S>( 39 | voxels: &'a [T], 40 | voxels_shape: &S, 41 | min: [u32; 3], 42 | max: [u32; 3], 43 | faces: &[OrientedBlockFace; 6], 44 | output: &mut UnitQuadBuffer, 45 | ) where 46 | V: Voxel + From<&'a T>, 47 | S: Shape<3, Coord = u32>, 48 | { 49 | assert_in_bounds(voxels, voxels_shape, min, max); 50 | 51 | let min = UVec3::from(min).as_ivec3(); 52 | let max = UVec3::from(max).as_ivec3(); 53 | let extent = Extent::from_min_and_max(min, max); 54 | let interior = extent.padded(-1); // Avoid accessing out of bounds with a 3x3x3 kernel. 55 | let interior = 56 | Extent::from_min_and_shape(interior.minimum.as_uvec3(), interior.shape.as_uvec3()); 57 | 58 | let kernel_strides = 59 | faces.map(|face| voxels_shape.linearize(face.signed_normal().as_uvec3().to_array())); 60 | 61 | for p in interior.iter3() { 62 | let p_array = p.to_array(); 63 | let p_index = voxels_shape.linearize(p_array); 64 | let p_voxel = V::from(unsafe { voxels.get_unchecked(p_index as usize) }); 65 | 66 | if let VoxelVisibility::Empty = p_voxel.get_visibility() { 67 | continue; 68 | } 69 | 70 | for (face_index, face_stride) in kernel_strides.into_iter().enumerate() { 71 | let neighbor_index = p_index.wrapping_add(face_stride); 72 | let neighbor_voxel = V::from(unsafe { voxels.get_unchecked(neighbor_index as usize) }); 73 | 74 | // TODO: If the face lies between two transparent voxels, we choose not to mesh it. We might need to extend the 75 | // IsOpaque trait with different levels of transparency to support this. 76 | let face_needs_mesh = match neighbor_voxel.get_visibility() { 77 | VoxelVisibility::Empty => true, 78 | VoxelVisibility::Translucent => p_voxel.get_visibility() == VoxelVisibility::Opaque, 79 | VoxelVisibility::Opaque => false, 80 | }; 81 | 82 | if face_needs_mesh { 83 | output.groups[face_index].push(UnorientedUnitQuad { minimum: p_array }); 84 | } 85 | } 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | use super::*; 92 | use crate::RIGHT_HANDED_Y_UP_CONFIG; 93 | use ndshape::{ConstShape, ConstShape3u32}; 94 | 95 | #[test] 96 | #[should_panic] 97 | fn panics_with_max_out_of_bounds_access() { 98 | let samples = [EMPTY; SampleShape::SIZE as usize]; 99 | let mut buffer = UnitQuadBuffer::new(); 100 | visible_block_faces( 101 | &samples, 102 | &SampleShape {}, 103 | [0; 3], 104 | [34, 33, 33], 105 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 106 | &mut buffer, 107 | ); 108 | } 109 | 110 | #[test] 111 | #[should_panic] 112 | fn panics_with_min_out_of_bounds_access() { 113 | let samples = [EMPTY; SampleShape::SIZE as usize]; 114 | let mut buffer = UnitQuadBuffer::new(); 115 | visible_block_faces( 116 | &samples, 117 | &SampleShape {}, 118 | [0, 34, 0], 119 | [33; 3], 120 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 121 | &mut buffer, 122 | ); 123 | } 124 | 125 | type SampleShape = ConstShape3u32<34, 34, 34>; 126 | 127 | /// Basic voxel type with one byte of texture layers 128 | #[derive(Default, Clone, Copy, Eq, PartialEq)] 129 | struct BoolVoxel(bool); 130 | 131 | const EMPTY: BoolVoxel = BoolVoxel(false); 132 | 133 | impl Voxel for BoolVoxel { 134 | fn get_visibility(&self) -> VoxelVisibility { 135 | if *self == EMPTY { 136 | VoxelVisibility::Empty 137 | } else { 138 | VoxelVisibility::Opaque 139 | } 140 | } 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/greedy/merge_strategy.rs: -------------------------------------------------------------------------------- 1 | use crate::greedy::face_needs_mesh; 2 | use crate::Voxel; 3 | 4 | use super::MergeVoxel; 5 | 6 | // TODO: implement a MergeStrategy for voxels with an ambient occlusion value at each vertex 7 | 8 | /// A strategy for merging cube faces into quads. 9 | pub trait MergeStrategy { 10 | type Voxel; 11 | 12 | /// Return the width and height of the quad that should be constructed. 13 | /// 14 | /// `min_index`: The linear index for the minimum voxel in this quad. 15 | /// 16 | /// `max_width`: The maximum possible width for the quad to be constructed. 17 | /// 18 | /// `max_height`: The maximum possible height for the quad to be constructed. 19 | /// 20 | /// `face_strides`: Strides to help with indexing in the necessary directions for this cube face. 21 | /// 22 | /// `voxels`: The entire array of voxel data. 23 | /// 24 | /// `visited`: The bitmask of which voxels have already been meshed. A quad's extent will be marked as visited (`true`) 25 | /// after `find_quad` returns. 26 | /// 27 | /// # Safety 28 | /// 29 | /// Some implementations may use unchecked indexing of `voxels` for performance. If this trait is not invoked with correct 30 | /// arguments, access out of bounds may cause undefined behavior. 31 | unsafe fn find_quad( 32 | min_index: u32, 33 | max_width: u32, 34 | max_height: u32, 35 | face_strides: &FaceStrides, 36 | voxels: &[Self::Voxel], 37 | visited: &[bool], 38 | ) -> (u32, u32) 39 | where 40 | Self::Voxel: Voxel; 41 | } 42 | 43 | pub struct FaceStrides { 44 | pub n_stride: u32, 45 | pub u_stride: u32, 46 | pub v_stride: u32, 47 | pub visibility_offset: u32, 48 | } 49 | 50 | pub struct VoxelMerger { 51 | marker: std::marker::PhantomData, 52 | } 53 | 54 | impl MergeStrategy for VoxelMerger 55 | where 56 | T: MergeVoxel, 57 | { 58 | type Voxel = T; 59 | 60 | unsafe fn find_quad( 61 | min_index: u32, 62 | max_width: u32, 63 | max_height: u32, 64 | face_strides: &FaceStrides, 65 | voxels: &[T], 66 | visited: &[bool], 67 | ) -> (u32, u32) { 68 | // Greedily search for the biggest visible quad where all merge values are the same. 69 | let quad_value = voxels.get_unchecked(min_index as usize).merge_value(); 70 | let quad_neighbour_value = voxels 71 | .get_unchecked(min_index.wrapping_add(face_strides.visibility_offset) as usize) 72 | .merge_value_facing_neighbour(); 73 | 74 | // Start by finding the widest quad in the U direction. 75 | let mut row_start_stride = min_index; 76 | let quad_width = Self::get_row_width( 77 | voxels, 78 | visited, 79 | &quad_value, 80 | &quad_neighbour_value, 81 | face_strides.visibility_offset, 82 | row_start_stride, 83 | face_strides.u_stride, 84 | max_width, 85 | ); 86 | 87 | // Now see how tall we can make the quad in the V direction without changing the width. 88 | row_start_stride += face_strides.v_stride; 89 | let mut quad_height = 1; 90 | while quad_height < max_height { 91 | let row_width = Self::get_row_width( 92 | voxels, 93 | visited, 94 | &quad_value, 95 | &quad_neighbour_value, 96 | face_strides.visibility_offset, 97 | row_start_stride, 98 | face_strides.u_stride, 99 | quad_width, 100 | ); 101 | if row_width < quad_width { 102 | break; 103 | } 104 | quad_height += 1; 105 | row_start_stride = row_start_stride.wrapping_add(face_strides.v_stride); 106 | } 107 | 108 | (quad_width, quad_height) 109 | } 110 | } 111 | 112 | impl VoxelMerger { 113 | unsafe fn get_row_width( 114 | voxels: &[T], 115 | visited: &[bool], 116 | quad_merge_voxel_value: &T::MergeValue, 117 | quad_merge_voxel_value_facing_neighbour: &T::MergeValueFacingNeighbour, 118 | visibility_offset: u32, 119 | start_stride: u32, 120 | delta_stride: u32, 121 | max_width: u32, 122 | ) -> u32 123 | where 124 | T: MergeVoxel, 125 | { 126 | let mut quad_width = 0; 127 | let mut row_stride = start_stride; 128 | while quad_width < max_width { 129 | let voxel = voxels.get_unchecked(row_stride as usize); 130 | let neighbour = 131 | voxels.get_unchecked(row_stride.wrapping_add(visibility_offset) as usize); 132 | 133 | if !face_needs_mesh(voxel, row_stride, visibility_offset, voxels, visited) { 134 | break; 135 | } 136 | 137 | if !voxel.merge_value().eq(quad_merge_voxel_value) 138 | || !neighbour 139 | .merge_value_facing_neighbour() 140 | .eq(quad_merge_voxel_value_facing_neighbour) 141 | { 142 | // Voxel needs to be non-empty and match the quad merge value. 143 | break; 144 | } 145 | 146 | quad_width += 1; 147 | row_stride += delta_stride; 148 | } 149 | 150 | quad_width 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/geometry/axis.rs: -------------------------------------------------------------------------------- 1 | use ilattice::glam::{IVec3, UVec3}; 2 | 3 | /// Either the X, Y, or Z axis. 4 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 5 | #[repr(u8)] 6 | pub enum Axis { 7 | X = 0, 8 | Y = 1, 9 | Z = 2, 10 | } 11 | 12 | impl Axis { 13 | /// The index for a point's component on this axis. 14 | #[inline] 15 | pub fn index(&self) -> usize { 16 | *self as usize 17 | } 18 | 19 | #[inline] 20 | pub const fn get_unit_vector(&self) -> UVec3 { 21 | match self { 22 | Axis::X => UVec3::X, 23 | Axis::Y => UVec3::Y, 24 | Axis::Z => UVec3::Z, 25 | } 26 | } 27 | } 28 | 29 | /// One of the six possible `{N, U, V}` --> `{X, Y, Z}` mappings. 30 | /// 31 | /// This can be combined with a `-1` or `+1` sign for the **N**ormal axis to 32 | /// make an [`OrientedBlockFace`][crate::OrientedBlockFace]. 33 | /// 34 | /// See the [`geometry` module documentation][crate::geometry] for more 35 | /// information on `{N, U, V}` space. 36 | /// 37 | /// # Even and Odd 38 | /// 39 | /// Even permutations: 40 | /// - [AxisPermutation::Xyz] 41 | /// - [AxisPermutation::Zxy] 42 | /// - [AxisPermutation::Yzx] 43 | /// 44 | /// Odd permutations: 45 | /// - [AxisPermutation::Zyx] 46 | /// - [AxisPermutation::Xzy] 47 | /// - [AxisPermutation::Yxz] 48 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 49 | pub enum AxisPermutation { 50 | // Even permutations 51 | Xyz, 52 | Zxy, 53 | Yzx, 54 | // Odd permutations 55 | Zyx, 56 | Xzy, 57 | Yxz, 58 | } 59 | 60 | impl AxisPermutation { 61 | #[inline] 62 | pub const fn even_with_normal_axis(axis: Axis) -> Self { 63 | match axis { 64 | Axis::X => AxisPermutation::Xyz, 65 | Axis::Y => AxisPermutation::Yzx, 66 | Axis::Z => AxisPermutation::Zxy, 67 | } 68 | } 69 | 70 | #[inline] 71 | pub const fn odd_with_normal_axis(axis: Axis) -> Self { 72 | match axis { 73 | Axis::X => AxisPermutation::Xzy, 74 | Axis::Y => AxisPermutation::Yxz, 75 | Axis::Z => AxisPermutation::Zyx, 76 | } 77 | } 78 | 79 | #[inline] 80 | pub const fn sign(&self) -> i32 { 81 | match self { 82 | AxisPermutation::Xyz => 1, 83 | AxisPermutation::Zxy => 1, 84 | AxisPermutation::Yzx => 1, 85 | AxisPermutation::Zyx => -1, 86 | AxisPermutation::Xzy => -1, 87 | AxisPermutation::Yxz => -1, 88 | } 89 | } 90 | 91 | /// Returns the [`Axes`] in the order specified by the permutation. 92 | /// 93 | /// # Example 94 | /// 95 | /// ``` 96 | /// # use block_mesh::*; 97 | /// let xyz = AxisPermutation::Xyz; 98 | /// assert_eq!(xyz.axes(), [Axis::X, Axis::Y, Axis::Z]); 99 | /// ``` 100 | /// 101 | /// [`Axes`]: Axis 102 | #[inline] 103 | pub const fn axes(&self) -> [Axis; 3] { 104 | match self { 105 | AxisPermutation::Xyz => [Axis::X, Axis::Y, Axis::Z], 106 | AxisPermutation::Zxy => [Axis::Z, Axis::X, Axis::Y], 107 | AxisPermutation::Yzx => [Axis::Y, Axis::Z, Axis::X], 108 | AxisPermutation::Zyx => [Axis::Z, Axis::Y, Axis::X], 109 | AxisPermutation::Xzy => [Axis::X, Axis::Z, Axis::Y], 110 | AxisPermutation::Yxz => [Axis::Y, Axis::X, Axis::Z], 111 | } 112 | } 113 | } 114 | 115 | /// Either the -X, +X, -Y, +Y, -Z, or +Z axis. 116 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 117 | #[repr(u8)] 118 | pub enum SignedAxis { 119 | NegX = 0, 120 | PosX = 1, 121 | NegY = 2, 122 | PosY = 3, 123 | NegZ = 4, 124 | PosZ = 5, 125 | } 126 | 127 | impl SignedAxis { 128 | #[inline] 129 | pub fn new(sign: i32, axis: Axis) -> Self { 130 | assert!(sign != 0); 131 | 132 | match (sign > 0, axis) { 133 | (false, Axis::X) => Self::NegX, 134 | (false, Axis::Y) => Self::NegY, 135 | (false, Axis::Z) => Self::NegZ, 136 | (true, Axis::X) => Self::PosX, 137 | (true, Axis::Y) => Self::PosY, 138 | (true, Axis::Z) => Self::PosZ, 139 | } 140 | } 141 | 142 | #[inline] 143 | pub fn unsigned_axis(&self) -> Axis { 144 | match self { 145 | Self::NegX => Axis::X, 146 | Self::NegY => Axis::Y, 147 | Self::NegZ => Axis::Z, 148 | Self::PosX => Axis::X, 149 | Self::PosY => Axis::Y, 150 | Self::PosZ => Axis::Z, 151 | } 152 | } 153 | 154 | #[inline] 155 | pub fn signum(&self) -> i32 { 156 | match self { 157 | Self::NegX => -1, 158 | Self::NegY => -1, 159 | Self::NegZ => -1, 160 | Self::PosX => 1, 161 | Self::PosY => 1, 162 | Self::PosZ => 1, 163 | } 164 | } 165 | 166 | #[inline] 167 | pub fn get_unit_vector(&self) -> IVec3 { 168 | match self { 169 | Self::NegX => -IVec3::X, 170 | Self::NegY => -IVec3::Y, 171 | Self::NegZ => -IVec3::Z, 172 | Self::PosX => IVec3::X, 173 | Self::PosY => IVec3::Y, 174 | Self::PosZ => IVec3::Z, 175 | } 176 | } 177 | 178 | #[inline] 179 | pub fn from_vector(v: IVec3) -> Option { 180 | match v.to_array() { 181 | [x, 0, 0] => Some(SignedAxis::new(x, Axis::X)), 182 | [0, y, 0] => Some(SignedAxis::new(y, Axis::Y)), 183 | [0, 0, z] => Some(SignedAxis::new(z, Axis::Z)), 184 | _ => None, 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /bench/src/bench.rs: -------------------------------------------------------------------------------- 1 | use block_mesh::ndshape::{ConstShape, ConstShape3u32}; 2 | use block_mesh::{ 3 | greedy_quads, visible_block_faces, GreedyQuadsBuffer, MergeVoxel, UnitQuadBuffer, Voxel, 4 | VoxelVisibility, RIGHT_HANDED_Y_UP_CONFIG, 5 | }; 6 | 7 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 8 | 9 | type SampleShape = ConstShape3u32<18, 18, 18>; 10 | 11 | fn bench_empty_space_greedy(c: &mut Criterion) { 12 | let mut group = c.benchmark_group("bench_empty_space_greedy"); 13 | let samples = [EMPTY; SampleShape::SIZE as usize]; 14 | 15 | // Do a single run first to allocate the buffer to the right size. 16 | let mut buffer = GreedyQuadsBuffer::new(samples.len()); 17 | greedy_quads( 18 | &samples, 19 | &SampleShape {}, 20 | [0; 3], 21 | [17; 3], 22 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 23 | &mut buffer, 24 | ); 25 | 26 | group.bench_with_input( 27 | BenchmarkId::from_parameter(format!("quads={}", buffer.quads.num_quads())), 28 | &(), 29 | |b, _| { 30 | b.iter(|| { 31 | greedy_quads( 32 | &samples, 33 | &SampleShape {}, 34 | [0; 3], 35 | [17; 3], 36 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 37 | &mut buffer, 38 | ) 39 | }); 40 | }, 41 | ); 42 | group.finish(); 43 | } 44 | 45 | fn bench_sphere_greedy(c: &mut Criterion) { 46 | let mut group = c.benchmark_group("bench_sphere_greedy"); 47 | let mut samples = [EMPTY; SampleShape::SIZE as usize]; 48 | for i in 0u32..(SampleShape::SIZE) { 49 | let p = into_domain(16, SampleShape::delinearize(i)); 50 | samples[i as usize] = sphere_voxel(p); 51 | } 52 | 53 | // Do a single run first to allocate the buffer to the right size. 54 | let mut buffer = GreedyQuadsBuffer::new(samples.len()); 55 | greedy_quads( 56 | &samples, 57 | &SampleShape {}, 58 | [0; 3], 59 | [17; 3], 60 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 61 | &mut buffer, 62 | ); 63 | 64 | group.bench_with_input( 65 | BenchmarkId::from_parameter(format!("quads={}", buffer.quads.num_quads())), 66 | &(), 67 | |b, _| { 68 | b.iter(|| { 69 | greedy_quads( 70 | &samples, 71 | &SampleShape {}, 72 | [0; 3], 73 | [17; 3], 74 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 75 | &mut buffer, 76 | ) 77 | }); 78 | }, 79 | ); 80 | group.finish(); 81 | } 82 | 83 | fn bench_empty_space_simple(c: &mut Criterion) { 84 | let mut group = c.benchmark_group("bench_empty_space_simple"); 85 | let samples = [EMPTY; SampleShape::SIZE as usize]; 86 | 87 | let mut buffer = UnitQuadBuffer::new(); 88 | visible_block_faces( 89 | &samples, 90 | &SampleShape {}, 91 | [0; 3], 92 | [17; 3], 93 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 94 | &mut buffer, 95 | ); 96 | 97 | group.bench_with_input( 98 | BenchmarkId::from_parameter(format!("quads={}", buffer.num_quads())), 99 | &(), 100 | |b, _| { 101 | b.iter(|| { 102 | visible_block_faces( 103 | &samples, 104 | &SampleShape {}, 105 | [0; 3], 106 | [17; 3], 107 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 108 | &mut buffer, 109 | ) 110 | }); 111 | }, 112 | ); 113 | group.finish(); 114 | } 115 | 116 | fn bench_sphere_simple(c: &mut Criterion) { 117 | let mut group = c.benchmark_group("bench_sphere_simple"); 118 | let mut samples = [EMPTY; SampleShape::SIZE as usize]; 119 | for i in 0u32..(SampleShape::SIZE) { 120 | let p = into_domain(16, SampleShape::delinearize(i)); 121 | samples[i as usize] = sphere_voxel(p); 122 | } 123 | 124 | let mut buffer = UnitQuadBuffer::new(); 125 | visible_block_faces( 126 | &samples, 127 | &SampleShape {}, 128 | [0; 3], 129 | [17; 3], 130 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 131 | &mut buffer, 132 | ); 133 | 134 | group.bench_with_input( 135 | BenchmarkId::from_parameter(format!("quads={}", buffer.num_quads())), 136 | &(), 137 | |b, _| { 138 | b.iter(|| { 139 | visible_block_faces( 140 | &samples, 141 | &SampleShape {}, 142 | [0; 3], 143 | [17; 3], 144 | &RIGHT_HANDED_Y_UP_CONFIG.faces, 145 | &mut buffer, 146 | ) 147 | }); 148 | }, 149 | ); 150 | group.finish(); 151 | } 152 | 153 | criterion_group!( 154 | benches, 155 | bench_sphere_simple, 156 | bench_sphere_greedy, 157 | bench_empty_space_simple, 158 | bench_empty_space_greedy 159 | ); 160 | criterion_main!(benches); 161 | 162 | #[derive(Clone, Copy, Eq, PartialEq)] 163 | struct BoolVoxel(bool); 164 | 165 | const EMPTY: BoolVoxel = BoolVoxel(false); 166 | const FULL: BoolVoxel = BoolVoxel(true); 167 | 168 | impl Voxel for BoolVoxel { 169 | fn get_visibility(&self) -> VoxelVisibility { 170 | if *self == EMPTY { 171 | VoxelVisibility::Empty 172 | } else { 173 | VoxelVisibility::Opaque 174 | } 175 | } 176 | } 177 | 178 | impl MergeVoxel for BoolVoxel { 179 | type MergeValue = Self; 180 | 181 | fn merge_value(&self) -> Self::MergeValue { 182 | *self 183 | } 184 | } 185 | 186 | fn sphere_voxel([x, y, z]: [f32; 3]) -> BoolVoxel { 187 | let d = x * x + y * y + z * z; 188 | 189 | if d > 0.9 { 190 | EMPTY 191 | } else { 192 | FULL 193 | } 194 | } 195 | 196 | fn into_domain(array_dim: u32, [x, y, z]: [u32; 3]) -> [f32; 3] { 197 | [ 198 | (2.0 * x as f32 / array_dim as f32) - 1.0, 199 | (2.0 * y as f32 / array_dim as f32) - 1.0, 200 | (2.0 * z as f32 / array_dim as f32) - 1.0, 201 | ] 202 | } 203 | -------------------------------------------------------------------------------- /src/geometry/face.rs: -------------------------------------------------------------------------------- 1 | use crate::{Axis, AxisPermutation, SignedAxis, UnorientedQuad}; 2 | 3 | use ilattice::glam::{IVec3, UVec3}; 4 | 5 | /// Metadata that's used to aid in the geometric calculations for one of the 6 possible cube faces. 6 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 7 | pub struct OrientedBlockFace { 8 | /// Determines the orientation of the plane. 9 | pub(crate) n_sign: i32, 10 | 11 | /// Determines the {N, U, V} <--> {X, Y, Z} relation. 12 | pub(crate) permutation: AxisPermutation, 13 | 14 | /// First in the `permutation` of +X, +Y, and +Z. 15 | pub(crate) n: UVec3, 16 | /// Second in the `permutation` of +X, +Y, and +Z. 17 | pub(crate) u: UVec3, 18 | /// Third in the `permutation` of +X, +Y, and +Z. 19 | pub(crate) v: UVec3, 20 | } 21 | 22 | impl OrientedBlockFace { 23 | pub const fn new(n_sign: i32, permutation: AxisPermutation) -> Self { 24 | let [n_axis, u_axis, v_axis] = permutation.axes(); 25 | 26 | Self { 27 | n_sign, 28 | permutation, 29 | n: n_axis.get_unit_vector(), 30 | u: u_axis.get_unit_vector(), 31 | v: v_axis.get_unit_vector(), 32 | } 33 | } 34 | 35 | /// A cube face, using axes with an even permutation. 36 | pub fn canonical(normal: SignedAxis) -> Self { 37 | Self::new( 38 | normal.signum(), 39 | AxisPermutation::even_with_normal_axis(normal.unsigned_axis()), 40 | ) 41 | } 42 | 43 | #[inline] 44 | pub fn n_sign(&self) -> i32 { 45 | self.n_sign 46 | } 47 | 48 | #[inline] 49 | pub fn permutation(&self) -> AxisPermutation { 50 | self.permutation 51 | } 52 | 53 | #[inline] 54 | pub fn signed_normal(&self) -> IVec3 { 55 | self.n.as_ivec3() * self.n_sign 56 | } 57 | 58 | /// Returns the 4 corners of the quad in this order: 59 | /// 60 | /// ```text 61 | /// 2 ----> 3 62 | /// ^ 63 | /// ^ \ 64 | /// | \ 65 | /// +V | 0 ----> 1 66 | /// | 67 | /// --------> 68 | /// +U 69 | /// 70 | /// (+N pointing out of the screen) 71 | /// ``` 72 | /// 73 | /// Note that this is natural when UV coordinates have (0,0) at the bottom 74 | /// left, but when (0,0) is at the top left, V must be flipped. 75 | #[inline] 76 | pub fn quad_corners(&self, quad: &UnorientedQuad) -> [UVec3; 4] { 77 | let w_vec = self.u * quad.width; 78 | let h_vec = self.v * quad.height; 79 | 80 | let minu_minv = if self.n_sign > 0 { 81 | UVec3::from(quad.minimum) + self.n 82 | } else { 83 | UVec3::from(quad.minimum) 84 | }; 85 | let maxu_minv = minu_minv + w_vec; 86 | let minu_maxv = minu_minv + h_vec; 87 | let maxu_maxv = minu_minv + w_vec + h_vec; 88 | 89 | [minu_minv, maxu_minv, minu_maxv, maxu_maxv] 90 | } 91 | 92 | #[inline] 93 | pub fn quad_mesh_positions(&self, quad: &UnorientedQuad, voxel_size: f32) -> [[f32; 3]; 4] { 94 | self.quad_corners(quad) 95 | .map(|c| (voxel_size * c.as_vec3()).to_array()) 96 | } 97 | 98 | #[inline] 99 | pub fn quad_mesh_normals(&self) -> [[f32; 3]; 4] { 100 | [self.signed_normal().as_vec3().to_array(); 4] 101 | } 102 | 103 | /// Returns the 6 vertex indices for the quad in order to make two triangles 104 | /// in a mesh. Winding order depends on both the sign of the surface normal 105 | /// and the permutation of the UVs. 106 | /// 107 | /// Front faces will be wound counterclockwise, and back faces clockwise, as 108 | /// per convention. 109 | #[inline] 110 | pub fn quad_mesh_indices(&self, start: u32) -> [u32; 6] { 111 | quad_indices(start, self.n_sign * self.permutation.sign() > 0) 112 | } 113 | 114 | /// Returns the UV coordinates of the 4 corners of the quad. Returns 115 | /// vertices in the same order as [`OrientedBlockFace::quad_corners`]. 116 | /// 117 | /// `u_flip_face` should correspond to the field on 118 | /// [`QuadCoordinateConfig`](crate::QuadCoordinateConfig). See the docs 119 | /// there for more info. 120 | /// 121 | /// This is just one way of assigning UVs to voxel quads. It assumes that 122 | /// each material has a single tile texture with wrapping coordinates, and 123 | /// each voxel face should show the entire texture. It also assumes a 124 | /// particular orientation for the texture. This should be sufficient for 125 | /// minecraft-style meshing. 126 | /// 127 | /// If you need to use a texture atlas, you must calculate your own 128 | /// coordinates from the `Quad`. 129 | #[inline] 130 | pub fn tex_coords( 131 | &self, 132 | u_flip_face: Axis, 133 | flip_v: bool, 134 | quad: &UnorientedQuad, 135 | ) -> [[f32; 2]; 4] { 136 | let face_normal_axis = self.permutation.axes()[0]; 137 | let flip_u = if self.n_sign < 0 { 138 | u_flip_face != face_normal_axis 139 | } else { 140 | u_flip_face == face_normal_axis 141 | }; 142 | 143 | match (flip_u, flip_v) { 144 | (false, false) => [ 145 | [0.0, 0.0], 146 | [quad.width as f32, 0.0], 147 | [0.0, quad.height as f32], 148 | [quad.width as f32, quad.height as f32], 149 | ], 150 | (true, false) => [ 151 | [quad.width as f32, 0.0], 152 | [0.0, 0.0], 153 | [quad.width as f32, quad.height as f32], 154 | [0.0, quad.height as f32], 155 | ], 156 | (false, true) => [ 157 | [0.0, quad.height as f32], 158 | [quad.width as f32, quad.height as f32], 159 | [0.0, 0.0], 160 | [quad.width as f32, 0.0], 161 | ], 162 | (true, true) => [ 163 | [quad.width as f32, quad.height as f32], 164 | [0.0, quad.height as f32], 165 | [quad.width as f32, 0.0], 166 | [0.0, 0.0], 167 | ], 168 | } 169 | } 170 | } 171 | 172 | /// Returns the vertex indices for a single quad (two triangles). The triangles 173 | /// may have either clockwise or counter-clockwise winding. `start` is the first 174 | /// index. 175 | fn quad_indices(start: u32, counter_clockwise: bool) -> [u32; 6] { 176 | if counter_clockwise { 177 | [start, start + 1, start + 2, start + 1, start + 3, start + 2] 178 | } else { 179 | [start, start + 2, start + 1, start + 1, start + 2, start + 3] 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /examples-crate/uv_mapping/main.rs: -------------------------------------------------------------------------------- 1 | use bevy::asset::LoadState; 2 | use bevy::prelude::*; 3 | use bevy::render::mesh::Indices; 4 | use bevy::render::render_asset::RenderAssetUsages; 5 | use bevy::render::render_resource::PrimitiveTopology; 6 | use bevy::render::texture::{ImageAddressMode, ImageSampler, ImageSamplerDescriptor}; 7 | use block_mesh::ndshape::{ConstShape, ConstShape3u32}; 8 | use block_mesh::{ 9 | greedy_quads, GreedyQuadsBuffer, MergeVoxel, Voxel, VoxelVisibility, RIGHT_HANDED_Y_UP_CONFIG, 10 | }; 11 | 12 | #[derive(Default, Clone, Copy, Debug, Eq, Hash, PartialEq, States)] 13 | enum AppState { 14 | #[default] 15 | Loading, 16 | Run, 17 | } 18 | 19 | const UV_SCALE: f32 = 1.0 / 16.0; 20 | 21 | #[derive(Resource)] 22 | struct Loading(Handle); 23 | 24 | fn main() { 25 | App::new() 26 | .add_plugins(DefaultPlugins) 27 | .init_state::() 28 | .add_systems(OnEnter(AppState::Loading), load_assets) 29 | .add_systems(Update, check_loaded.run_if(in_state(AppState::Loading))) 30 | .add_systems(OnEnter(AppState::Run), setup) 31 | .add_systems(Update, rotation_system) 32 | .run(); 33 | } 34 | 35 | fn load_assets(mut commands: Commands, asset_server: Res) { 36 | debug!("load"); 37 | let handle = asset_server.load("uv_checker.png"); 38 | commands.insert_resource(Loading(handle)); 39 | } 40 | 41 | /// Make sure that our texture is loaded so we can change some settings on it later 42 | fn check_loaded( 43 | mut next_state: ResMut>, 44 | handle: Res, 45 | asset_server: Res, 46 | ) { 47 | debug!("check loaded"); 48 | if let Some(LoadState::Loaded) = asset_server.get_load_state(&handle.0) { 49 | debug!("uv_checker.png loaded!"); 50 | next_state.set(AppState::Run); 51 | } 52 | } 53 | 54 | /// Basic voxel type with one byte of texture layers 55 | #[derive(Default, Clone, Copy)] 56 | struct BoolVoxel(bool); 57 | 58 | impl MergeVoxel for BoolVoxel { 59 | type MergeValue = bool; 60 | type MergeValueFacingNeighbour = bool; 61 | 62 | fn merge_value(&self) -> Self::MergeValue { 63 | self.0 64 | } 65 | 66 | fn merge_value_facing_neighbour(&self) -> Self::MergeValueFacingNeighbour { 67 | self.0 68 | } 69 | } 70 | 71 | impl Voxel for BoolVoxel { 72 | fn get_visibility(&self) -> VoxelVisibility { 73 | if self.0 { 74 | VoxelVisibility::Opaque 75 | } else { 76 | VoxelVisibility::Empty 77 | } 78 | } 79 | } 80 | 81 | fn setup( 82 | mut commands: Commands, 83 | texture_handle: Res, 84 | mut meshes: ResMut>, 85 | mut materials: ResMut>, 86 | mut textures: ResMut>, 87 | ) { 88 | debug!("setup"); 89 | let texture = textures.get_mut(&texture_handle.0).unwrap(); 90 | 91 | // Set the texture to tile over the entire quad 92 | texture.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor { 93 | address_mode_u: ImageAddressMode::Repeat, 94 | address_mode_v: ImageAddressMode::Repeat, 95 | ..Default::default() 96 | }); 97 | 98 | type SampleShape = ConstShape3u32<22, 22, 22>; 99 | 100 | // Just a solid cube of voxels. We only fill the interior since we need some empty voxels to form a boundary for the mesh. 101 | let mut voxels = [BoolVoxel(false); SampleShape::SIZE as usize]; 102 | for z in 1..21 { 103 | for y in 1..21 { 104 | for x in 1..21 { 105 | let i = SampleShape::linearize([x, y, z]); 106 | voxels[i as usize] = BoolVoxel(true); 107 | } 108 | } 109 | } 110 | 111 | let faces = RIGHT_HANDED_Y_UP_CONFIG.faces; 112 | 113 | let mut buffer = GreedyQuadsBuffer::new(voxels.len()); 114 | greedy_quads( 115 | &voxels, 116 | &SampleShape {}, 117 | [0; 3], 118 | [21; 3], 119 | &faces, 120 | &mut buffer, 121 | ); 122 | let num_indices = buffer.quads.num_quads() * 6; 123 | let num_vertices = buffer.quads.num_quads() * 4; 124 | let mut indices = Vec::with_capacity(num_indices); 125 | let mut positions = Vec::with_capacity(num_vertices); 126 | let mut normals = Vec::with_capacity(num_vertices); 127 | let mut tex_coords = Vec::with_capacity(num_vertices); 128 | for (group, face) in buffer.quads.groups.into_iter().zip(faces.into_iter()) { 129 | for quad in group.into_iter() { 130 | indices.extend_from_slice(&face.quad_mesh_indices(positions.len() as u32)); 131 | positions.extend_from_slice(&face.quad_mesh_positions(&quad, 1.0)); 132 | normals.extend_from_slice(&face.quad_mesh_normals()); 133 | tex_coords.extend_from_slice(&face.tex_coords( 134 | RIGHT_HANDED_Y_UP_CONFIG.u_flip_face, 135 | true, 136 | &quad, 137 | )); 138 | } 139 | } 140 | // Center the mesh. 141 | for p in &mut positions { 142 | *p = (Vec3::from(*p) - Vec3::splat(10.0)).into(); 143 | } 144 | 145 | let mut render_mesh = Mesh::new( 146 | PrimitiveTopology::TriangleList, 147 | RenderAssetUsages::RENDER_WORLD, 148 | ); 149 | 150 | for uv in tex_coords.iter_mut() { 151 | for c in uv.iter_mut() { 152 | *c *= UV_SCALE; 153 | } 154 | } 155 | 156 | render_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); 157 | render_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); 158 | render_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, tex_coords); 159 | render_mesh.insert_indices(Indices::U32(indices)); 160 | 161 | commands.spawn(( 162 | PbrBundle { 163 | mesh: meshes.add(render_mesh), 164 | material: materials.add(texture_handle.0.clone()), 165 | ..Default::default() 166 | }, 167 | Rotate, 168 | )); 169 | 170 | commands.insert_resource(AmbientLight { 171 | color: Color::WHITE, 172 | brightness: 1_000.0, 173 | }); 174 | 175 | commands.spawn(Camera3dBundle { 176 | transform: Transform::from_translation(Vec3::new(50.0, 15.0, 50.0)) 177 | .looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y), 178 | ..Default::default() 179 | }); 180 | } 181 | 182 | #[derive(Component)] 183 | struct Rotate; 184 | 185 | fn rotation_system(time: Res