bevy-voxel-pipeline
Skill29 starsChanged 12 days agoMITView on GitHub
Use when meshing voxel chunks with `block-mesh-rs` (`greedy_quads` / `visible_block_faces`), translating a `GreedyQuadsBuffer` into a Bevy 0.20 `Mesh` via `try_insert_attribute`, choosing between greedy and simple meshing, or scheduling chunk meshing on `AsyncComputeTaskPool` with `poll_once` so the main thread doesn't stall. Covers Bevy 0.20 voxel meshing.
In this repository34 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- bevy-a11ySkill
- bevy-animationSkill
- bevy-assetsSkill
- bevy-audioSkill
- bevy-camerasSkill
- bevy-captureSkill
- bevy-cargo-featuresSkill
- bevy-core-conceptsSkill
- bevy-custom-assetsSkill
- bevy-diagnostics-profilingSkill
- bevy-ecs-componentsSkill
- bevy-ecs-queriesSkill
- bevy-ecs-systemsSkill
- bevy-fluentSkill
- bevy-input-actionsSkill
- bevy-migration-0-17-to-0-18Skill
- bevy-migration-0-18-to-0-19Skill
- bevy-migration-0-19-to-0-20Skill
- bevy-pbr-materialsSkill
- bevy-physicsSkill
- bevy-portingSkill
- bevy-renderingSkill
- bevy-save-loadSkill
- bevySkill
- bevy-testingSkill
- bevy-uiSkill
- bevy-vfxSkill
- bevy-voxel-dataSkill
- bevy-voxel-runtimeSkill
- bevy-wasm-webgpuSkill
- similarity-rsSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 137
Dropping a
Taskcancels it on web (0.19): If you drop MeshTask before polling it (chunk unloaded mid-mesh), call task.0.detach() first …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Bevy 0.20 — Voxel meshing pipeline
When to use this skill
- Building a Minecraft-style voxel world with chunked meshing.
- Choosing between
greedy_quads(fewer, larger quads — best for static terrain) andvisible_block_faces(one quad per face — best for fast remesh on edits). - Wiring
block-mesh-rsoutput into a Bevy 0.20Mesh. - Avoiding main-thread stalls on remesh by spawning the work on
AsyncComputeTaskPool.
Canonical pattern
use bevy::asset::RenderAssetUsages;
use bevy::mesh::{Indices, PrimitiveTopology};
use bevy::prelude::*;
use block_mesh::ndshape::{ConstShape, ConstShape3u32};
use block_mesh::{
greedy_quads, GreedyQuadsBuffer, MergeVoxel, Voxel, VoxelVisibility,
RIGHT_HANDED_Y_UP_CONFIG,
};
// 18^3 — the standard "chunk plus padding" block-mesh expects.
// Each axis gets 1 cell of padding on each side so neighbour lookups are
// in-bounds. The user-visible chunk is 16^3.
type ChunkShape = ConstShape3u32<18, 18, 18>;
#[derive(Clone, Copy, Eq, PartialEq, Default)]
pub struct BlockId(pub u16);
impl Voxel for BlockId {
fn get_visibility(&self) -> VoxelVisibility {
if self.0 == 0 {
VoxelVisibility::Empty
} else {
VoxelVisibility::Opaque
}
}
}
impl MergeVoxel for BlockId {
type MergeValue = u16;
fn merge_value(&self) -> Self::MergeValue { self.0 }
}
/// Build a Bevy 0.20 Mesh from a padded chunk of blocks.
/// Returns `None` for an entirely empty chunk so callers can skip spawning.
pub fn mesh_chunk(blocks: &[BlockId]) -> Option<Mesh> {
assert_eq!(blocks.len(), ChunkShape::SIZE as usize);
let mut buffer = GreedyQuadsBuffer::new(blocks.len());
greedy_quads(
blocks,
&ChunkShape {},
[0; 3],
[17, 17, 17],
&RIGHT_HANDED_Y_UP_CONFIG.faces,
&mut buffer,
);
if buffer.quads.num_quads() == 0 {
return None;
}
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.iter().zip(RIGHT_HANDED_Y_UP_CONFIG.faces.iter()) {
for quad in group.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 mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
mesh.try_insert_attribute(Mesh::ATTRIBUTE_POSITION, positions).ok()?;
mesh.try_insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals).ok()?;
mesh.insert_indices(Indices::U32(indices));
Some(mesh)
}Threading: get it off the main thread
use bevy::prelude::*;
use bevy::tasks::{block_on, poll_once, AsyncComputeTaskPool, Task};
#[derive(Component)]
struct MeshTask(Task<Option<Mesh>>);
fn kick(mut commands: Commands) {
let pool = AsyncComputeTaskPool::get();
let blocks: Vec<BlockId> = Vec::new(); // load from chunk store
let task = pool.spawn(async move { mesh_chunk(&blocks) });
commands.spawn(MeshTask(task));
}
fn poll(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut q: Query<(Entity, &mut MeshTask)>) {
for (entity, mut task) in &mut q {
if let Some(maybe_mesh) = block_on(poll_once(&mut task.0)) {
commands.entity(entity).remove::<MeshTask>();
if let Some(mesh) = maybe_mesh {
let handle = meshes.add(mesh);
commands.entity(entity).insert(Mesh3d(handle));
}
}
}
}block_on and poll_once are re-exported from bevy::tasks; no futures_lite dependency is needed.
Gotchas
- Padding is non-optional.
block-meshneeds one cell of padding on each axis so cross-chunk neighbour visibility computes correctly. A "16-cube" chunk is stored as 18×18×18; edits in a chunk also touch the padding of its six neighbours. - Greedy vs simple.
greedy_quadsproduces 1/3 the quads ofvisible_block_facesbut takes 3× longer. Use simple for chunks players are editing right now, greedy for chunks that have been stable for a while. - UVs are not free.
block-meshdoesn't emit UVs — you compute them yourself from quad face direction and block ID. A texture atlas + per-quad offset is the standard approach (seebevy-voxel-data). block-mesh0.2.0 (crateblock-mesh, repoblock-mesh-rs) is Bevy-independent (last published 2022-04). It depends onilatticeandndshape, both pure-Rust. It compiles with Bevy 0.20 because it never imports Bevy; only theMesh-building glue above is Bevy code.- Don't reach for
par_iterinside a single chunk mesh — block-mesh is already fast. The parallelism wins are across chunks, not within one. Spawn N chunk-mesh tasks onAsyncComputeTaskPool. try_insert_attributestill returnsResult<(), MeshAccessError>in 0.20 — the error case is "mesh already extracted to render world", which won't happen for a freshly-new-d mesh. Still, handle theResultso the API doesn't regress on you.RenderAssetUsageslives inbevy::asset.bevy::mesh::{Indices, PrimitiveTopology}is the mesh path;bevy::render::mesh::*is stale 0.15-era output.- Dropping a
Taskcancels it on web (0.19). If you dropMeshTaskbefore polling it (chunk unloaded mid-mesh), calltask.0.detach()first only if you still want the work to finish; otherwise the cancel is what you want. MeshAabb::compute_aabbisget_aabb(0.20). Stale snippets that compute a chunk bounding box viamesh.compute_aabb()fail to compile; usemesh.get_aabb()withuse bevy::camera::primitives::MeshAabb;(the trait is not inbevy::mesh).MeshTag(x)isMeshTag::new(x)(0.20) and the value istag.value, nottag.0. Useful if you tag chunk meshes for a custom shader.- Shapes moved to
bevy_shape(0.20).Cuboidand friends are still inbevy::prelude; only directbevy::math::primitives::*imports break. - Lifecycle
ReplaceisDiscard(0.19). Hooks that reacted to aMesh3dswap on the chunk entity useOn<Discard<Mesh3d>>and#[component(on_discard = ...)]. Query::iter_manyyieldsResult(0.20). Batch-polling tasks by entity list needs.iter_many(ids).matched()for the old silent-skip behaviour.
See also
bevy-voxel-data— RON block definitions, palette, UV atlas baking.bevy-voxel-runtime— dirty/halo fan-out, revision tokens, bounded uploads aroundmesh_chunk.bevy-assets— loading chunk data and managing the resulting Mesh handles.bevy-migration-0-19-to-0-20—get_aabb,MeshTag::new,bevy_shape,iter_manychanges.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

