stride3d/stride · error · ArgumentException

Mesh seems to have no volume; triangle rasterization…

Error message

Mesh seems to have no volume; triangle rasterization occupied no cells.

What it means

Tetrahedralize in Stride.BepuPhysics voxelizes the mesh by rasterizing each triangle onto a uniform grid of cellSize cells. If no cell was occupied, the mesh has no enclosing volume at that resolution (empty, degenerate, or too small relative to cellSize) and the method throws ArgumentException instead of producing a meaningless tetrahedral mesh.

Solutions

  1. Check the triangle count and vertex positions are finite and non-degenerate before calling Tetrahedralize
  2. Reduce cellSize so it is small relative to the mesh bounding box (aim for many cells along each axis, e.g. cellSize < bbox/16)
  3. Verify mesh units match cellSize units
  4. Ensure the mesh is a closed watertight surface with actual volume, not a flat plane or line
  5. Dump the computed min/max bounds and cells.Count to diagnose empty rasterization

Example fix

// before
var mesh = softMesh; // possibly empty / unit mismatch
softBody = BepuThings.Tetrahedralize(mesh.Triangles, cellSize: 1f);
// after
if (mesh.Triangles == null || mesh.Triangles.Count == 0)
    throw new InvalidOperationException("Cannot tetrahedralize an empty mesh.");
var bb = ComputeBounds(mesh.Triangles);
if (!bb.IsValid || bb.Extents.Length() < 4f * cellSize)
    cellSize = bb.Extents.Length() / 16f;
softBody = BepuThings.Tetrahedralize(mesh.Triangles, cellSize);
Defensive patterns

Strategy: validation

Validate before calling

bool CanTetrahedralize(IReadOnlyList<Triangle> tris, float cellSize)
{
    if (tris == null || tris.Count == 0) return false;
    var min = new Vector3(float.MaxValue); var max = new Vector3(float.MinValue);
    foreach (var t in tris)
    {
        if (!IsFinite(t.A) || !IsFinite(t.B) || !IsFinite(t.C)) return false;
        min = Vector3.Min(min, Vector3.Min(t.A, Vector3.Min(t.B, t.C)));
        max = Vector3.Max(max, Vector3.Max(t.A, Vector3.Max(t.B, t.C)));
    }
    return Vector3.Divide(max - min, cellSize).Length() > 8f; // mesh big enough vs cellSize
}

Try / catch

try
{
    softBody = BepuThings.Tetrahedralize(triangles, cellSize);
}
catch (ArgumentException ex) when (ex.Message.Contains("no volume"))
{
    Logger.Error($"Mesh cannot be voxelized: {ex.Message}");
    softBody = null; // fall back to convex hull collider
}

Prevention

When it happens

Trigger: Calling Tetrahedralize with an empty triangle list, triangles that are degenerate (zero-area/NaN vertices), or a cellSize far larger than the mesh so the rasterization grid misses all triangles.

Common situations: Imported models with zero-scale vertices or non-closed geometry; unit mismatch where mesh is in centimeters but cellSize chosen in meters (mesh microscopic relative to cells); forgetting to load geometry before tetrahedralizing; all triangles on a single plane (zero thickness).

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/6b0a7e7887956bc2. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.BepuPhysics/Stride.BepuPhysics.Soft/Definitions/BepuThings.cs:395

                max = Vector3.Max(max, triangle.A);
                max = Vector3.Max(max, triangle.B);
                max = Vector3.Max(max, triangle.C);
            }
            //Add a little buffer.
            var buffer = new Vector3(cellSize);
            min -= buffer;

            var cells = new CellSet(triangles.Length, pool);
            for (int i = 0; i < triangles.Length; ++i)
            {
                ref var triangle = ref triangles[i];
                //Rasterize each triangle onto the grid.
                TriangleRasterizer.RasterizeTriangle(ref triangle.A, ref triangle.B, ref triangle.C, cellSize, ref min, pool, ref cells);

            }

            if (cells.Count == 0)
                throw new ArgumentException("Mesh seems to have no volume; triangle rasterization occupied no cells.");

            VoxelizationBounds bounds;
            Vector3 size = max - min;
            float inverseCellSize = 1f / cellSize;
            bounds.X = (int)(Math.Ceiling(inverseCellSize * size.X));
            bounds.Y = (int)(Math.Ceiling(inverseCellSize * size.Y));
            bounds.Z = (int)(Math.Ceiling(inverseCellSize * size.Z));
            //Perform a flood fill on every surface vertex.
            //We can use the cells set directly, since it behaves like a regular list with regard to element placement (always at the end).
            var floodFilledCells = new CellSet(32, pool);
            var cellsToVisit = new CellList(32, pool);
            for (int i = cells.Count - 1; i >= 0; --i)
            {
                ref var cell = ref cells[i];
                FloodFillAdjacentCells(cell, ref bounds, pool, ref cells, ref floodFilledCells, ref cellsToVisit);
            }

            //Build the vertex list and per-cell vertex index lists.

View on GitHub (pinned to 96fad776d2)