This plugin makes it easy to generate and modify voxel terrains in Bevy. bevy_voxel_world
handles multithreaded meshing, chunk spawning/despawning, texture mapping and provides an easy to use API that can be accessed from any system.
$ cargo run -r --example noise_terrain
The world can be controlled in two main ways: through a terrain lookup function, and directly by set_voxel
and get_voxel
functions. The world has two "layers" of voxel information, one that is procedural and determined by the terrain lookup function, and one that is controlled by set_voxel
and persisted in a HashMap
. The persistent layer always overrides the procedural layer. This way, the world can be infinitely large, but we only need to store information about voxels that are deliberately changed. In the current implementation, the proceduaral layer is cached for spawned chunks, so it may still use a lot of memory if the spawning distance is large.
For an example on how to use a terrain lookup function, see this example.
The set_voxel
and get_voxel
access functions are easily reached from any normal Bevy system:
fn my_system(mut voxel_world: VoxelWorld) {
voxel_world.set_voxel(IVec3 { ... }, WorldVoxel::Ground(0));
}
This will update the voxel value at the given location in the persisting HashMap
, and cause bevy_voxel_world
to queue the affected chunk for re-meshing.
Voxels are keyed by their XYZ coordinate in the world, specified by an IVec3
. The type of voxel is specified by the WorldVoxel
type. A voxel can be Unset
, Air
or Ground
.
Ground
voxels holds a u8
material type value. Thus, a maximum of 256 material types are supported. Material types can easily be mapped to indexes in a 2d texture array though a mapping callback.
A custom array texture can be supplied when initializing the plugin:
VoxelWorldPlugin::default()
.with_voxel_texture("images/materials.png", 6)
This should be image with a size of W x (W * n)
, where n
is the number of indexes. So an array of 4 16x16 px textures would be 16x64 px in size. The number of indexes is specified in the second parameter (6 in the example above).
Then, to map out which indexes belong to which material type, you can supply a texture_index_mapper
callback:
commands.insert_resource(VoxelWorldConfiguration {
texture_index_mapper: Arc::new(|vox_mat: u8| {
match vox_mat {
// Grass
0 => [0, 1, 2],
// Dirt
1 => [2, 2, 2],
// Stone
2 => [3, 3, 3],
// Sand
3 => [4, 4, 4],
// Snow
4 => [5, 5, 5],
_ => [2, 2, 2],
}
}),
..Default::default()
});
The texture_index_mapper
callback is supplied with a material type and should return an array with three values. The values indicate which texture index maps to [top, sides, bottom]
of a voxel.
bevy_voxel_world
began as an internal part of a game that I'm working on, but I figured that it could be useful as a standalone plugin, for myself and perhaps for others, so I decided to break it out and make it public as a crate.
In its current state, there are still various hard-coded assumptions that works well enough for my usecase, but may not suit everyone. Over time, the aim is to generalize and make bevy_voxel_world
more configurable. There are also many potential performance optimizations that I have not prioritized yet at this point.
Currently only "blocky", Minecraft-like, voxels are supported, and there is no support for "half-slabs". Meshing is handled by block-mesh-rs, and only the "simple" algorithm is used (i.e, no greedy meshing.)
Feedback, issues and pull requests are welcomed!
bevy | bevy_voxel_world |
---|---|
0.12 | ^0.3.0 |
0.11 | 0.2.2 |