Generate voxel block meshes in Rust.

Overview

block-mesh

Fast algorithms for generating voxel block meshes.

Mesh Examples

Two algorithms are included:

  • visible_block_faces: very fast but suboptimal meshes
  • greedy_quads: not quite as fast, but far fewer triangles are generated

Benchmarks show that visible_block_faces generates about 40 million quads per second on a single core of a 2.5 GHz Intel Core i7. Assuming spherical input data, greedy_quads can generate a more optimal version of the same mesh with 1/3 of the quads, but it takes about 3 times longer. To run the benchmarks yourself, cd bench/ && cargo bench.

Example Code

use block_mesh::ndshape::{ConstShape, ConstShape3u32};
use block_mesh::{greedy_quads, GreedyQuadsBuffer, MergeVoxel, Voxel, RIGHT_HANDED_Y_UP_CONFIG};

#[derive(Clone, Copy, Eq, PartialEq)]
struct BoolVoxel(bool);

const EMPTY: BoolVoxel = BoolVoxel(false);
const FULL: BoolVoxel = BoolVoxel(true);

impl Voxel for BoolVoxel {
    fn is_empty(&self) -> bool {
        *self == EMPTY
    }

    fn is_opaque(&self) -> bool {
        true
    }
}

impl MergeVoxel for BoolVoxel {
    type MergeValue = Self;

    fn merge_value(&self) -> Self::MergeValue {
        *self
    }
}

// A 16^3 chunk with 1-voxel boundary padding.
type ChunkShape = ConstShape3u32<18, 18, 18>;

// This chunk will cover just a single octant of a sphere SDF (radius 15).
let mut voxels = [EMPTY; ChunkShape::SIZE as usize];
for i in 0..ChunkShape::SIZE {
    let [x, y, z] = ChunkShape::delinearize(i);
    voxels[i as usize] = if ((x * x + y * y + z * z) as f32).sqrt() < 15.0 {
        FULL
    } else {
        EMPTY
    };
}

let mut buffer = GreedyQuadsBuffer::new(voxels.len());
greedy_quads(
    &voxels,
    &ChunkShape {},
    [0; 3],
    [17; 3],
    &RIGHT_HANDED_Y_UP_CONFIG.faces,
    &mut buffer
);

// Some quads were generated.
assert!(buffer.quads.num_quads() > 0);
Comments
  • Voxel Transparency Issue

    Voxel Transparency Issue

    Hi, I'm using VoxelVisibility::Translucent for voxels that have a transparent colour. However, none of the faces of the transparent voxel are returned after greedy meshing even though there are exposed sides.

    Is it supposed to only ensure that occluded faces of non-transparent voxels are visible and ignores the faces of the transparent voxel?

    opened by JAD3N 9
  • Getting a quad's color

    Getting a quad's color

    I'm writing a little Minecraft importer for Godot and I'm using this library to convert a voxel grid into a mesh. Everything seems to work fine but I have one little issue. It seems I can't figure out how to determine which color a quad should be. My code is the following (with some details removed):

    //after running greedy_mesh()...
    
    let num_indices = quads_buffer.quads.num_quads() * 6;
    let num_vertices = quads_buffer.quads.num_quads() * 4;
    let mut indices = Vec::with_capacity(num_indices);
    let mut positions = Vec::with_capacity(num_vertices);
    let mut normals = Vec::with_capacity(num_vertices);
    let mut uvcoords = Vec::with_capacity(num_vertices);
    let mut colors = Vec::with_capacity(num_vertices);
    
    for (quad_group, face) in quads_buffer.quads.groups.into_iter().zip(RIGHT_HANDED_Y_UP_CONFIG.faces) {
    	for quad in &quad_group {
    		indices.extend_from_slice(&face.quad_mesh_indices(positions.len() as u32));
    		positions.extend_from_slice(&face.quad_mesh_positions(&quad, 1.0));
    		normals.extend_from_slice(&face.quad_mesh_normals());
    		uvcoords.extend_from_slice(&face.tex_coords(RIGHT_HANDED_Y_UP_CONFIG.u_flip_face, true, &quad)); 
    	}
    }
    
    

    So I was wondering... how do you get the "material index" of a quad, which determines which color it should be? (e.g. grass should have green faces, water should have blue faces, etc)

    I tried something like this, using quad.minimum to get the minimum voxel a quad belongs to...

    		let [x, y, z] = quad.minimum; 
    		let palette_index = &blocks[(x, y, z)];
    		let color = palette[palette_index];
    
    		colors.extend_from_slice(&[color; 4]);
    

    But that seems to be incorrect, as you can see: (the entire river should be blue, but only parts of it are) image

    Is my code incorrect?

    opened by Bauxitedev 7
  • Voxel trait inflexible

    Voxel trait inflexible

    visible_block_faces accepts a pre-allocated buffer of V: Voxel. This is a limitation that becomes annoying when trying to render the same data in multiple ways.

    As an example, I have a voxel scene editor, and I want to have several renderings of the same data Chunk by different properties: all voxels, walls only, conduits only. If I use visible_block_faces, I need to create different types of voxels for rendering (VAll, VWall, VConduit), with 3 different Voxel implementations, one for each class of visibility. visible_block_faces needs an allocated slice of the rendering voxels, so I can't use a slice of Voxel directly, I need to convert it. But I can't feed it an iter like this: (&[V]).map(|v| VWall::from(v). I have to ::collect() any iterator before I can use it as a slice and feed it to visible_block_faces.

    I think that's unnecessarily complicated, and comes with a performance hit for a relatively simple operation.

    The same problem with MergeVoxel has been solved by greedy_quads_with_merge_strategy by adding a MergeStrategy trait (although that trait is overly complicated for this use case).

    1. I can see a simpler solution reusing the Voxel trait:
    pub fn visible_block_faces<T, S, V>(
    	    voxels: &[T],
    	    voxels_shape: &S,
    	    min: [u32; 3],
    	    max: [u32; 3],
    	    faces: &[OrientedBlockFace; 6],
    	    output: &mut UnitQuadBuffer,
    	) where
    	    S: Shape<u32, 3>,
                V: Voxel + From<T>,
    	{
    // processing example: just convert on the fly
        for t in voxels:
            let v: V = t.into();
            if v.is_opaque(): ...
    
    1. Another possibility would be to drop the &[T] slice in favour of a lazy Map<T, F> type, which translates on the fly.

    EDIT: 3. Accept A: Index<usize, Output=T>.

    In either of those cases, using a newtype would end up being a no-op, leading to (presumably) no performance loss.

    I'm ready to implement this, just need to consult which solution makes sense.

    opened by dcz-self 6
  • [HELP] Voxel Chunk not Rendering.

    [HELP] Voxel Chunk not Rendering.

    I am trying to create a voxel game. For now I would like to just generate a single 16x16x16 chunk. I've created a script very similar to this one, https://github.com/bonsairobo/block-mesh-rs/blob/main/examples-crate/render/main.rs and it compiles but it doesn't render.

    Here's some of the code:

    fn main() -> () {
        let mut app = App::new();
        app.insert_resource(Msaa {samples : 4});
        app.insert_resource(WindowDescriptor {
            title  : String::from("Voxel Test"),
            width  : 1024.0,
            height : 600.0,
            ..Default::default()
        });
        app.add_plugins(DefaultPlugins);
        app.add_startup_system(game::update_chunks);
        app.run();
    }
    
    pub fn update_chunks(
        mut commands  : Commands,
        mut meshes    : ResMut<Assets<Mesh>>,
        mut materials : ResMut<Assets<StandardMaterial>>
    ) -> () {
        let mesh = generate_chunk([0, 0, 0]);
        commands.spawn_bundle(PbrBundle {
            mesh      : meshes.add(mesh),
            material  : materials.add(Color::rgb(1.0, 0.9, 0.9).into()),
            transform : Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
            ..Default::default()
        });
    }
    
    type ChunkSize = block_mesh::ndshape::ConstShape3u32<16, 16, 16>;
    
    pub fn generate_chunk(position : [i32; 3]) -> Mesh {
        let generator = FlatGenerator {};
        let size        = ChunkSize::ARRAY;
        let chunk_array = generator.generate_chunk(position, [
            position[0] * (size[0] as i32),
            position[1] * (size[1] as i32),
            position[2] * (size[2] as i32)
        ]); // Returns a `ChunkArray<'l>` object I made
        let mesh        = chunk_array.as_mesh();
        return mesh;
    
    }
    
    impl<'l> ChunkArray<'l> {
        pub fn as_mesh(&self) -> Mesh {
            let mut samples = [voxels::EMPTY; ChunkSize::SIZE as usize];
            for i in 0..ChunkSize::SIZE {
                let [x, y, z] = ChunkSize::delinearize(i);
                samples[i as usize] = self.array[x as usize][y as usize][z as usize].clone();
            }
            let faces = RIGHT_HANDED_Y_UP_CONFIG.faces;
            let mut buffer = GreedyQuadsBuffer::new(samples.len());
            greedy_quads(
                &samples,
                &ChunkSize {},
                [0; 3],
                [ChunkSize::ARRAY[0], ChunkSize::ARRAY[1], ChunkSize::ARRAY[2]],
                &faces,
                &mut buffer
            );
            let     num_indices  = buffer.quads.num_quads() * 6;
            let     num_vertices = buffer.quads.num_quads() * 4;
            let mut indices      = Vec::with_capacity(num_indices);
            let mut positions    = Vec::with_capacity(num_vertices);
            let mut normals      = Vec::with_capacity(num_vertices);
            for (group, face) in buffer.quads.groups.into_iter().zip(faces.into_iter()) {
                for quad in group.into_iter() {
                    indices   .extend_from_slice( &face.quad_mesh_indices   (positions.len() as u32) );
                    positions .extend_from_slice( &face.quad_mesh_positions (&quad, 1.0)             );
                    normals   .extend_from_slice( &face.quad_mesh_normals   ()                       );
                }
            }
            let mut render_mesh = Mesh::new(PrimitiveTopology::TriangleList);
            render_mesh.set_attribute(
                "Vertex_Position",
                VertexAttributeValues::Float32x3(positions),
            );
            render_mesh.set_attribute("Vertex_Normal", VertexAttributeValues::Float32x3(normals));
            render_mesh.set_attribute(
                "Vertex_Uv",
                VertexAttributeValues::Float32x2(vec![[0.0; 2]; num_vertices]),
            );
            render_mesh.set_indices(Some(Indices::U32(indices.clone())));
            return render_mesh;
        }
    
    
    opened by Totobird-Creations 5
  • Compute Task Pool panicked at attempt to add with overflow - github build

    Compute Task Pool panicked at attempt to add with overflow - github build

    Hello!

    I was using the block mesh library directly from github using block-mesh = {git = "https://github.com/bonsairobo/block-mesh-rs"} in my Cargo.toml file.

    Problem: i get the error thread 'Compute Task Pool (5)' panicked at 'attempt to add with overflow', /home/yairama/.cargo/git/checkouts/block-mesh-rs-d08695c2c9849c77/71aa5de/src/greedy/merge_strategy.rs:71:28 (the compute task pool number varies for each build)

    the error is generated by the greedy_quads funcion:

    greedy_quads( &voxels, &ChunkShape {}, [0; 3], [CHUNK_SIZE_U32-1; 3], &faces, &mut buffer, );

    (ChunkSize is 32 and ChunkShape is ConstShape3u32<32,32,32>

    ** The error only apperas with cargo run command, when i use cargo run --release it runs perfectly ** Also when i use the crate io build ( block-mesh = "0.2.0" in my Cargo.toml file ) it runs perfectly.

    opened by Yairama 2
  • Voxel trait easily confused

    Voxel trait easily confused

    The Voxel trait contains 2 similar functions: is_empty and is_opaque with close but opposite meanings. Most of the times I was implementing them, I wanted to copy-paste them, but that would cause one to have the completely wrong meaning. How about turning is_opaque to is_translucent?

    I'm also not quite sure how is_opaque influences mesh generation, but if it means "does not prevent inner quads from showing", then "is_empty" is strictly a subset of "is_translucent", and the 3 would have been better served by an enum:

    enum VoxelVisibility {
       Empty,
       Translucent,
       Opaque,
    }
    

    How does that sound?

    good first issue 
    opened by dcz-self 2
  • Add the ability to interpret the same buffer with different Voxel traits

    Add the ability to interpret the same buffer with different Voxel traits

    Fixes https://github.com/bonsairobo/block-mesh-rs/issues/3 .

    I don't really like it a lot, with the references' lifetimes messing up the newtypes. At least in practice when voxels mostly impllement Copy, this only means a lifetimed From is needed (see example).

    opened by dcz-self 2
  • Improve documentation and code organization.

    Improve documentation and code organization.

    I took some time to clarify some things in writing (and with pictures!) that confused me for a long while when I started using block-mesh.

    Please take a careful read through the module-level docs in geometry and check me for factual incorrectness.

    opened by BGR360 1
  • Benchmark build failing

    Benchmark build failing

    Environment: M1 Max MBP w/ MacOS 12.3.1

    active toolchain
    ----------------
    
    stable-aarch64-apple-darwin (default)
    rustc 1.60.0 (7737e0b5c 2022-04-04)
    

    Repro steps:

    git clone https://github.com/bonsairobo/block-mesh-rs
    cd block-mesh-rs
    cargo build  # builds successfully
    cd bench
    cargo bench  # build fails
    

    Errors: CleanShot 2022-04-13 at 14 09 54@2x

    opened by sargunv 0
  • Update crates package

    Update crates package

    Hello!

    The package dowloaded from https://crates.io/crates/block-mesh through cargo.toml have a different MergeVoxel trait (doesnt have MergeValueFacingNeighbour type and function).

    Is this trait is critic?

    thats all!

    opened by Yairama 1
  • Requiring an `[x+1, y+1, z+1]`-sized buffer is inconvenient.

    Requiring an `[x+1, y+1, z+1]`-sized buffer is inconvenient.

    It's a little unfortunate that if you have a clean 4k page of chunk data (16 * 16 * 16), you have to copy that into a slightly larger buffer before calling into the functions in this crate.

    For my particular use case, I don't think this will end up being an issue, because I will likely end up storing chunk data in a compressed format, so I will have to iterate and fill a buffer anyway. But others may want to just pass in their buffers without copying them into larger ones.

    I understand how supporting this could be difficult though. Have you given this any thought?

    opened by BGR360 7
Owner
Duncan
Duncan
Tools for managing GitHub block lists

GitHub block list management Octocrabby is a small set of command-line tools and Octocrab extensions that are focused on managing block lists on GitHu

Travis Brown 97 Nov 3, 2022
Generate rust structs & query functions from diesel schema files

dsync A utility to generate database structs and querying code from diesel schema files. Primarily built for create-rust-app. Currently, it's more adv

Haris 20 Feb 12, 2023
A Matrix bot which can generate "This Week in X" like blog posts

hebbot A Matrix bot which can help to generate periodic / recurrent summary blog posts (also known as "This Week in X"). The bot was inspired by twim-

Häcker Felix 43 Dec 17, 2022
This crate allows to generate a flat binary with the memory representation of an ELF.

flatelf Library This crate allows to generate a flat binary with the memory representation of an ELF. It also allows to generate a FLATELF with the fo

Roi Martin 3 Sep 29, 2022
Generate SUMMARY.md files based on your book's file structure

mdbook-autosummary Generate a SUMMARY.md for your mdBook based on your folder structure! Warning The implementation is hacky and has several limitatio

Hyper 3 Sep 30, 2023
A procedural macro to generate a new function implementation for your struct.

Impl New ?? A procedural macro to generate a new function implementation for your struct. ?? Add to your project Add this to your Cargo.toml: [depende

Mohammed Alotaibi 4 Sep 8, 2023
Leetcode Solutions in Rust, Advent of Code Solutions in Rust and more

RUST GYM Rust Solutions Leetcode Solutions in Rust AdventOfCode Solutions in Rust This project demostrates how to create Data Structures and to implem

Larry Fantasy 635 Jan 3, 2023
Simple autoclicker written in Rust, to learn the Rust language.

RClicker is an autoclicker written in Rust, written to learn more about the Rust programming language. RClicker was was written by me to learn more ab

null 7 Nov 15, 2022
Rust programs written entirely in Rust

mustang Programs written entirely in Rust Mustang is a system for building programs built entirely in Rust, meaning they do not depend on any part of

Dan Gohman 561 Dec 26, 2022
Rust 核心库和标准库的源码级中文翻译,可作为 IDE 工具的智能提示 (Rust core library and standard library translation. can be used as IntelliSense for IDE tools)

Rust 标准库中文版 这是翻译 Rust 库 的地方, 相关源代码来自于 https://github.com/rust-lang/rust。 如果您不会说英语,那么拥有使用中文的文档至关重要,即使您会说英语,使用母语也仍然能让您感到愉快。Rust 标准库是高质量的,不管是新手还是老手,都可以从中

wtklbm 493 Jan 4, 2023
A library for extracting #[no_mangle] pub extern "C" functions (https://docs.rust-embedded.org/book/interoperability/rust-with-c.html#no_mangle)

A library for extracting #[no_mangle] pub extern "C" functions In order to expose a function with C binary interface for interoperability with other p

Dmitrii - Demenev 0 Feb 17, 2022
clone of grep cli written in Rust. From Chapter 12 of the Rust Programming Language book

minigrep is a clone of the grep cli in rust Minigrep will find a query string in a file. To test it out, clone the project and run cargo run body poem

Raunak Singh 1 Dec 14, 2021
Rust-blog - Educational blog posts for Rust beginners

pretzelhammer's Rust blog ?? I write educational content for Rust beginners and Rust advanced beginners. My posts are listed below in reverse chronolo

kirill 5.2k Jan 1, 2023
The ray tracer challenge in rust - Repository to follow my development of "The Raytracer Challenge" book by Jamis Buck in the language Rust

The Ray Tracer Challenge This repository contains all the code written, while step by implementing Ray Tracer, based on the book "The Ray Tracer Chall

Jakob Westhoff 54 Dec 25, 2022
Learn-rust-the-hard-way - "Learn C The Hard Way" by Zed Shaw Converted to Rust

Learn Rust The Hard Way This is an implementation of Zed Shaw's Learn X The Hard Way for the Rust Programming Language. Installing Rust TODO: Instruct

Ryan Levick 309 Dec 8, 2022
Learn to write Rust procedural macros [Rust Latam conference, Montevideo Uruguay, March 2019]

Rust Latam: procedural macros workshop This repo contains a selection of projects designed to learn to write Rust procedural macros — Rust code that g

David Tolnay 2.5k Dec 29, 2022
The Rust Compiler Collection is a collection of compilers for various languages, written with The Rust Programming Language.

rcc The Rust Compiler Collection is a collection of compilers for various languages, written with The Rust Programming Language. Compilers Language Co

null 2 Jan 17, 2022
Integra8 rust integration test framework Rust with a focus on productivity, extensibility, and speed.

integra8 Integra8 rust integration test framework Rust with a focus on productivity, extensibility, and speed. | This repo is in a "work in progress"

exceptional 3 Sep 26, 2022
Neofetch but in Rust (rust-toml-fetch)

rtfetch Configuration Recompile each time you change the config file logo = "arch.logo" # in src/assets. info = [ "", "", "<yellow>{host_n

Paolo Bettelini 6 Jun 6, 2022