reckfellENGINE

GridMap: a modular 3D level with collision

Create a MeshLibrary, place and rotate blocks, align mesh and physics, and persist the library safely.

On this page

Data model

GridMap stores one MeshLibrary item ID and one orientation index per integer cell. MeshLibrary defines each block: a mesh, its transform and separate physics shapes. It does not create an individual entity for every block.

Use it for walls, floors and repeated modules with fixed spacing. Scripted doors, inventory and other interactive objects often fit separate entities/prefabs when they need individual state. The grid alone does not create ECS, world streaming or navigation from visible artwork.

Run the complete grid

Take BlockoutLab3D.cs from the CSG example and BlockoutLibrary.cs below. Use C# SDK 1.0.0, net10.0 and an Entity3D-rooted .rscene level. Run dotnet build, attach BlockoutLab3D.cs to the root and run.

Code creates 15 floor cells, then replaces four with blue walls. Each cell still has one item: setting a wall does not add a second floor layer in that cell. The shared dark plane below is a visual backdrop without a physics shape.

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
        });
    }
}

Blue modular GridMap walls beside the white CSG composition

Captured in the current local 1.0.0 runtime. Character controls and editor mouse painting are not part of this example.

Align mesh and collision

CreateItem adds an empty entry. SetItemMesh assigns visible geometry; SetItemShapes supplies a separate array of [Shape3D, Transform3D, …] pairs. The array must have an even number of elements. SetItemMeshTransform does not replace physics-shape transforms.

The sample wall measures (2,3,0.4), with its center raised by 1.5 so its bottom lies on Y=0. The same offset is explicitly assigned to the mesh and shape separately. The 0.2-thick floor is offset by -0.1, putting its top at Y=0.

If artwork aligns but collision appears beside it, compare those transforms first. Wait for a physics update after cell edits: GetCellItem can already report the new value while old physics is still pending replacement.

Distinguish dimensions and centering

SettingSampleEffect
CellSize(2,2,2)Spacing between cell positions
CellCenterX/ZtrueHalf-step offset along X/Z
CellCenterYfalseNo half-step vertical offset
CellScale1Scale of placed meshes and physics shapes
BoxMesh dimensionsPer blockResource geometry, unchanged by CellSize

These settings make MapToLocal((2,3,-1)) return (5,6,-1). LocalToMap((-0.01,-0.01,-0.01)) returns (-1,-1,-1), because it floors rather than truncates toward zero.

Convert world input through Grid.ToLocal before LocalToMap. Reverse with MapToLocal followed by ToGlobal. A ray hitting exactly on a cell boundary requires choosing the intended side using its normal and a small offset; blindly rounding a world position often chooses a neighboring cell. Coordinate conversion is verified here; a complete click-painting tool is not implemented.

CellSize=(4,2,2) increases X spacing without stretching the source wall. CellScale=0.5 shrinks both mesh and physics shape, preserving original spacing and MapToLocal. A grid setting alone cannot correct every model-scale error.

Rotate or erase a block

The third SetCellItem argument is an index among 24 orthogonal orientations, not degrees. For a 90° turn, construct a Basis around Vector3.Up with angle Mathf.Pi/2 and pass it to GetOrthogonalIndexFromBasis. Use the returned index in SetCellItem; read it through GetCellItemOrientation or GetCellItemBasis. Native checks confirm both mesh and physics-shape rotation.

SetCellItem(cell,-1) erases a cell. A negative item does not mean an invisible block. In the native check, GetCellItem immediately reported -1, and the physics ray stopped hitting the erased wall after an update.

A GridMap physics hit identifies the GridMap itself, not an individual cell entity. Store per-door or destructible-block state by cell coordinate, or use a separate prefab.

Validate IDs and persistence

Use contiguous IDs starting at 0. The current build has a confirmed .rdata defect: items 0 and 7 work in memory, ResourceSaver returns Error.Ok, but item 7 data is lost after reload. The published example uses 0 and 1; meshes, shapes and offsets survive saving and uncached loading.

Until fixed, do not rely on sparse-library persistence or remove source assets after one successful write. ID migration must update map cell references too, or they will point at different blocks. Full .rscene GridMap persistence and the MeshLibrary export-menu workflow are outside this test.

Two further limits matter:

  • An unknown ID can be stored and returned by GetCellItem without producing mesh or physics. Check the library GetItemList; the C# MeshLibrary has no HasItem method.
  • The cell item field is 16-bit. Valid nonnegative GridMap item IDs are 0..65535; the native check turns 65536 into 0. Source bounds require cell-coordinate magnitudes below 2^20 per axis. This is not an infinite map.

Diagnosis

SymptomCheck
Stored cell has no visible blockAssigned MeshLibrary, existing ID and item Mesh
Visible block has no collisionSetItemShapes pairs and physics layers/masks
Walls float or sinkCellCenterY, mesh offset and separate shape offset
Rotation does not match 90°Supply an orthogonal orientation index, not the number 90
Blocks disappear after reloadSparse IDs and actual reloaded library data
AI cannot find a floor routeNavigation data and registration require separate configuration

GridMap API · MeshLibrary API

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.