reckfellENGINE

CSG: a blockout wall with a doorway

Combine geometry with Boolean operations, enable collision and obtain separate mesh and shape resources.

On this page

What CSG solves

CSG combines and subtracts volumes to produce one result from simple shapes. Use it to test room dimensions, passageways and sight lines before final models are ready. It does not replace modeling, UV unwrapping or asset preparation.

The white sample wall has a real doorway: a ray through the middle passes through, while a ray through the side hits collision. The blue blocks nearby are a separate GridMap, allowing comparison of level-building approaches.

White CSG doorway and blue modular GridMap walls

This is an actual frame from the current local 1.0.0 build, Compatibility/OpenGL, 640×420. It shows game output, not the editor interface or large-level performance.

Run the complete example

Use a Reckfell.NET.Sdk 1.0.0 C# project targeting net10.0 and a .rscene level rooted in Entity3D. Save both files below, run dotnet build, attach BlockoutLab3D.cs to the root and run the level. Code creates the camera and resources; no external models, textures or archives are required.

BlockoutLibrary.cs supplies both CSG materials and the blue-block library. BlockoutLab3D.cs creates both compositions. There is no character controller; native physics rays separately verify doorway collision.

BlockoutLibrary.cs
C#
using Reckfell;

public static class BlockoutLibrary
{
    public const int Floor = 0;
    public const int Wall = 1;

    public static StandardMaterial3D Material(Color color) => new()
    {
        AlbedoColor = color,
        ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded
    };

    public static MeshLibrary Create()
    {
        var library = new MeshLibrary();
        AddBox(library, Floor, "Floor", new Vector3(2, 0.2f, 2),
            new Vector3(0, -0.1f, 0), new Color(0.15f, 0.18f, 0.23f));
        AddBox(library, Wall, "Wall", new Vector3(2, 3, 0.4f),
            new Vector3(0, 1.5f, 0), new Color(0.08f, 0.25f, 1));
        return library;
    }

    private static void AddBox(MeshLibrary library, int id, string name,
        Vector3 size, Vector3 offset, Color color)
    {
        library.CreateItem(id);
        library.SetItemName(id, name);
        library.SetItemMesh(id, new BoxMesh { Size = size, Material = Material(color) });
        var transform = new Transform3D(Basis.Identity, offset);
        library.SetItemMeshTransform(id, transform);
        library.SetItemShapes(id, new Reckfell.Collections.Array
        {
            new BoxShape3D { Size = size }, transform
        });
    }
}
BlockoutLab3D.cs
C#
using Reckfell;

public partial class BlockoutLab3D : Entity3D
{
    public CsgCombiner3D Solid { get; private set; } = null!;
    public CsgBox3D Wall { get; private set; } = null!;
    public CsgBox3D DoorCut { get; private set; } = null!;
    public GridMap Grid { get; private set; } = null!;

    public override void _Ready()
    {
        Solid = new CsgCombiner3D
        {
            Position = new Vector3(-4, 1.5f, 0),
            UseCollision = true,
            CollisionLayer = 1,
            CollisionMask = 1
        };
        AddChild(Solid);
        Wall = new CsgBox3D
        {
            Size = new Vector3(5, 3, 0.6f),
            Material = BlockoutLibrary.Material(Colors.White)
        };
        DoorCut = new CsgBox3D
        {
            Size = new Vector3(1.6f, 2.2f, 1.2f),
            Position = new Vector3(0, -0.4f, 0),
            Operation = CsgShape3D.OperationEnum.Subtraction
        };
        Solid.AddChild(Wall);
        Solid.AddChild(DoorCut);

        Grid = new GridMap
        {
            Position = new Vector3(4, 0, 0),
            CellSize = new Vector3(2, 2, 2),
            CellCenterX = true,
            CellCenterY = false,
            CellCenterZ = true,
            MeshLibrary = BlockoutLibrary.Create(),
            CollisionLayer = 1,
            CollisionMask = 1
        };
        AddChild(Grid);
        for (int x = -2; x <= 2; x++)
        for (int z = -1; z <= 1; z++)
            Grid.SetCellItem(new Vector3I(x, 0, z), BlockoutLibrary.Floor);
        foreach (int x in new[] { -2, -1, 1, 2 })
            Grid.SetCellItem(new Vector3I(x, 0, 1), BlockoutLibrary.Wall);

        AddChild(new MeshInstance3D
        {
            Mesh = new PlaneMesh { Size = new Vector2(24, 16) },
            Position = new Vector3(0, -0.21f, 0),
            MaterialOverride = BlockoutLibrary.Material(new Color(0.06f, 0.07f, 0.09f))
        });
        var camera = new Camera3D { Current = true, Position = new Vector3(12, 9, 17), Fov = 55 };
        AddChild(camera);
        camera.LookAt(new Vector3(0, 1, 1));
    }
}

Understand operation order

Solid is the root CsgCombiner3D. Its first child Wall adds a 5×3×0.6 box. The next child DoorCut uses Operation=Subtraction to remove a 1.6×2.2×1.2 box. The cutter is thicker than the wall and passes completely through it; its bottom aligns with the floor level.

Child CSG operationResult
UnionAdd volume to the accumulated geometry
SubtractionRemove the overlapping part of the accumulated volume
IntersectionKeep only shared volume

The runnable example verifies the initial wall union and doorway subtraction. Complex Intersection chains with imported meshes are not tested. Operand order matters: geometry must exist before another operand cuts it.

Operands are direct children of the CSG root. A separate nearby CSG root does not automatically subtract from another. Do not insert an ordinary Entity3D between these operands and Solid expecting the composition to remain unchanged.

Enable result physics

Solid sets UseCollision=true. Physics belongs to the root result; DoorCut does not become a separate invisible obstacle. CollisionLayer=1 places the wall on the first layer; the query or character must match that bit.

Rendering and collision update later. After changing dimensions or transforms, wait for updates before checking hits or retrieving a finished mesh. Do not rebuild a complex CSG composition every frame without profiling.

The native test casts through X/Y=(-4,1) and misses the doorway, then through (-5.5,1) and hits the side. After UseCollision=false, the side ray also misses while geometry remains visible. Visual visibility and physics collision are separate settings.

Obtain static resources

Call these on Solid after its result is ready:

  1. BakeStaticMesh() obtains an ArrayMesh. Check for null. It does not create a MeshInstance3D or save a file; this is the current root mesh resource, not a guaranteed independent deep copy.
  2. BakeCollisionShape() creates a separate ConcavePolygonShape3D. It works with UseCollision=false, but the resource alone does not add collision to the world.
  3. For a static replacement, assign the mesh to MeshInstance3D and the shape to CollisionShape3D inside StaticBody3D. Preserve the root transform. Avoid leaving two identical physics walls active.
  4. Save the mesh and shape separately through ResourceSaver.Save to .rdata paths and check Error.Ok. Reload and verify the data; a successful write does not prove the whole level is correct.

Native checks save both resources, reload without cache and compare geometry. A separate StaticBody3D using the baked shape preserves the wall and doorway. Full level-tree persistence and the editor baking command interface are not verified here.

Child Wall/DoorCut bake calls do not return an independent result: use the composition root. Use concave triangle geometry for static collision; choose primitive or convex shapes separately for moving physics bodies.

Diagnosis

SymptomCheck
Doorway is absentOperation, direct-child CSG order and cutter/wall overlap
Visible opening blocks a bodyOld wall collision, pending update or duplicate physics body
Character passes through the wallRoot UseCollision and matching physics layers/masks
BakeStaticMesh returns nullIs this the root? Is its deferred mesh ready?
Baked mesh has no collisionStaticBody3D and CollisionShape3D with the separate shape
Stretched texturesPrimitive scales and UVs; CSG does not perform full artistic UV unwrapping

CsgShape3D API · Build a modular level

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.