Brusher
Experimental engine agnostic 3D CSG library for game development written in Rust. Started as a port of csg.js to Rust.
Install / Use
/learn @BrianWiz/BrusherREADME
brusher
Experimental engine agnostic 3D CSG library for game development written in Rust. Started as a port of csg.js to Rust.
ultimate goal
My hope is that it can essentially provide an API that can be used to create an editor like Trenchbroom, GTKRadiant, Hammer, etc by providing easy to use public methods for creating and manipulating 3D "brushes" (solids) that can be used to create levels for games.
For things like curves, I'm considering adding curvo by @mattatz as a dependency to provide a way to create curves like pipes & arches.
features & todo
https://github.com/user-attachments/assets/c79d244f-47bc-4c98-81f9-dfb46ed5fb86
- [x] union
- [x] intersect
- [x] subtract
- [x] knife (WIP)
- [ ] handle maintaining materials per surface
- [ ] serialization
- [ ] extrude
- [ ] bevel
- technically already possible manually by using
knifebut just needs a helper function
- technically already possible manually by using
- [x] construct
BrushletfromVec<Polygons> - [x] construct
BrushletfromVec<Surface>- allows you to define a convex solid by defining its surfaces (planes)
- [ ] smooth normals with configurable angle tolerance
- [ ] editor API (WIP)
example (Bevy)
cargo run --release --features bevy --example realtime_basic
usage
use brusher::prelude::*;
// Helper enum to map materials to indices
enum MyMaterials {
ProtoGrey = 0,
ProtoGreen = 1,
}
impl From<MyMaterials> for usize {
fn from(material: MyMaterials) -> usize {
material as usize
}
}
// Create a brush that will combine two rooms
let mut brush = Brush::new("Rooms");
// Create a brushlet for the first room
brush.add_brushlet(Brushlet::from_cuboid(
brusher::primitives::Cuboid {
origin: DVec3::new(0.0, 0.0, 0.0),
width: 8.0,
height: 4.0,
depth: 8.0,
material_indices: CuboidMaterialIndices {
front: MyMaterials::ProtoGrey.into(),
back: MyMaterials::ProtoGreen.into(),
left: MyMaterials::ProtoGreen.into(),
right: MyMaterials::ProtoGrey.into(),
top: MyMaterials::ProtoGrey.into(),
bottom: MyMaterials::ProtoGrey.into(),
},
},
BrushletSettings {
name: "Room 1".to_string(),
operation: BooleanOp::Subtract,
// Cut the brushlet with a knife
knives: vec![Knife {
normal: DVec3::new(-1.0, -1.0, -1.0),
distance_from_origin: 4.0,
material_index: MyMaterials::ProtoGreen.into(),
}],
inverted: true,
},
));
// Create a brushlet for the second room
brush.add_brushlet(Brushlet::from_cuboid(
brusher::primitives::Cuboid {
origin: DVec3::new(4.0, 0.0, 4.0),
width: 8.0,
height: 4.0,
depth: 8.0,
material_indices: CuboidMaterialIndices {
front: MyMaterials::ProtoGreen.into(),
back: MyMaterials::ProtoGreen.into(),
left: MyMaterials::ProtoGreen.into(),
right: MyMaterials::ProtoGreen.into(),
top: MyMaterials::ProtoGreen.into(),
bottom: MyMaterials::ProtoGreen.into(),
},
},
BrushletSettings {
name: "Room 2".to_string(),
operation: BooleanOp::Union,
knives: vec![],
inverted: false,
},
));
// Cut at the brush level with a knife to cut both rooms at once
brush.settings.knives = vec![Knife {
normal: DVec3::new(1.0, 1.0, 0.0),
distance_from_origin: 4.0,
material_index: MyMaterials::ProtoGrey.into(),
}];
let mesh_data = brush.to_mesh_data();
construct meshes from a brush
This example uses bevy, but you should be able to adapt it to any engine that supports meshes.
// Helper enum to map materials to indices
enum MyMaterials {
ProtoGrey = 0,
ProtoGreen = 1,
}
impl From<MyMaterials> for usize {
fn from(material: MyMaterials) -> usize {
material as usize
}
}
// Create a brush (see above example)
// ...
// Define some materials
let material_proto_grey = materials.add(Color::rgb(0.5, 0.5, 0.5).into());
let material_proto_green = materials.add(Color::rgb(0.2, 0.8, 0.2).into());
// Get the mesh data from the brush and convert it to bevy meshes
let meshes_with_materials = brush.to_mesh_data().to_bevy_meshes();
// Spawn the meshes and assign materials based on the material index
for (mesh, material_index) in meshes_with_materials {
let material = match material_index {
MyMaterials::ProtoGrey => material_proto_grey.clone(),
MyMaterials::ProtoGreen => material_proto_green.clone(),
_ => material_proto_grey.clone(),
};
commands.spawn(PbrBundle {
mesh: meshes.add(mesh),
material,
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
..default()
});
}
special thanks
- Thank you to csg.js by @evanw for the original csg.js library! Your work has done some serious heavy lifting for this project and I am grateful for it.
- Thank you to shambler by @shfty for the inspiration to start this project.
Related Skills
node-connect
348.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
108.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
348.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
348.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
