{"record":{"id":"6b0a7e7887956bc2","repo":"stride3d/stride","slug":"mesh-seems-to-have-no-volume-triangle-rasterization-occupied","errorCode":null,"errorMessage":"Mesh seems to have no volume; triangle rasterization occupied no cells.","messagePattern":"Mesh seems to have no volume; triangle rasterization occupied no cells\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.BepuPhysics/Stride.BepuPhysics.Soft/Definitions/BepuThings.cs","lineNumber":395,"sourceCode":"                max = Vector3.Max(max, triangle.A);\n                max = Vector3.Max(max, triangle.B);\n                max = Vector3.Max(max, triangle.C);\n            }\n            //Add a little buffer.\n            var buffer = new Vector3(cellSize);\n            min -= buffer;\n\n            var cells = new CellSet(triangles.Length, pool);\n            for (int i = 0; i < triangles.Length; ++i)\n            {\n                ref var triangle = ref triangles[i];\n                //Rasterize each triangle onto the grid.\n                TriangleRasterizer.RasterizeTriangle(ref triangle.A, ref triangle.B, ref triangle.C, cellSize, ref min, pool, ref cells);\n\n            }\n\n            if (cells.Count == 0)\n                throw new ArgumentException(\"Mesh seems to have no volume; triangle rasterization occupied no cells.\");\n\n            VoxelizationBounds bounds;\n            Vector3 size = max - min;\n            float inverseCellSize = 1f / cellSize;\n            bounds.X = (int)(Math.Ceiling(inverseCellSize * size.X));\n            bounds.Y = (int)(Math.Ceiling(inverseCellSize * size.Y));\n            bounds.Z = (int)(Math.Ceiling(inverseCellSize * size.Z));\n            //Perform a flood fill on every surface vertex.\n            //We can use the cells set directly, since it behaves like a regular list with regard to element placement (always at the end).\n            var floodFilledCells = new CellSet(32, pool);\n            var cellsToVisit = new CellList(32, pool);\n            for (int i = cells.Count - 1; i >= 0; --i)\n            {\n                ref var cell = ref cells[i];\n                FloodFillAdjacentCells(cell, ref bounds, pool, ref cells, ref floodFilledCells, ref cellsToVisit);\n            }\n\n            //Build the vertex list and per-cell vertex index lists.","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics.Soft/Definitions/BepuThings.cs#L377-L413","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Check the triangle count and vertex positions are finite and non-degenerate before calling Tetrahedralize","Reduce cellSize so it is small relative to the mesh bounding box (aim for many cells along each axis, e.g. cellSize < bbox/16)","Verify mesh units match cellSize units","Ensure the mesh is a closed watertight surface with actual volume, not a flat plane or line","Dump the computed min/max bounds and cells.Count to diagnose empty rasterization"],"exampleFix":"// before\nvar mesh = softMesh; // possibly empty / unit mismatch\nsoftBody = BepuThings.Tetrahedralize(mesh.Triangles, cellSize: 1f);\n// after\nif (mesh.Triangles == null || mesh.Triangles.Count == 0)\n    throw new InvalidOperationException(\"Cannot tetrahedralize an empty mesh.\");\nvar bb = ComputeBounds(mesh.Triangles);\nif (!bb.IsValid || bb.Extents.Length() < 4f * cellSize)\n    cellSize = bb.Extents.Length() / 16f;\nsoftBody = BepuThings.Tetrahedralize(mesh.Triangles, cellSize);","handlingStrategy":"validation","validationCode":"bool CanTetrahedralize(IReadOnlyList<Triangle> tris, float cellSize)\n{\n    if (tris == null || tris.Count == 0) return false;\n    var min = new Vector3(float.MaxValue); var max = new Vector3(float.MinValue);\n    foreach (var t in tris)\n    {\n        if (!IsFinite(t.A) || !IsFinite(t.B) || !IsFinite(t.C)) return false;\n        min = Vector3.Min(min, Vector3.Min(t.A, Vector3.Min(t.B, t.C)));\n        max = Vector3.Max(max, Vector3.Max(t.A, Vector3.Max(t.B, t.C)));\n    }\n    return Vector3.Divide(max - min, cellSize).Length() > 8f; // mesh big enough vs cellSize\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    softBody = BepuThings.Tetrahedralize(triangles, cellSize);\n}\ncatch (ArgumentException ex) when (ex.Message.Contains(\"no volume\"))\n{\n    Logger.Error($\"Mesh cannot be voxelized: {ex.Message}\");\n    softBody = null; // fall back to convex hull collider\n}","preventionTips":["Sanitize imported meshes: drop degenerate and NaN triangles","Match cellSize units to mesh units; scale cellSize down for small meshes","Ensure meshes are watertight closed surfaces with real volume","Log bounding-box size vs cellSize before tetrahedralizing"],"tags":["physics","bepu","mesh","voxelization","softbody"],"backgroundTag":"empty-result-set","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}