Skip to content

Latest commit

 

History

History
158 lines (135 loc) · 5.47 KB

README.md

File metadata and controls

158 lines (135 loc) · 5.47 KB

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

2024-08-14.10-05-27.mov
  • union
  • intersect
  • subtract
  • knife (WIP)
    • handle maintaining materials per surface
  • serialization
  • extrude
  • bevel
    • technically already possible manually by using knife but just needs a helper function
  • construct Brushlet from Vec<Polygons>
  • construct Brushlet from Vec<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.