egametang/ET · error · Exception

buildPolyDetail: Could not triangulate polygon ({nverts}) ve

Error message

buildPolyDetail: Could not triangulate polygon ({nverts}) verts).

What it means

buildPolyDetail triangulates the polygon hull via TriangulateHull; if the resulting triangle list is empty, the polygon could not be triangulated at all. Rather than emit empty/garbage detail data, it throws so the caller knows the detail mesh is incomplete.

Source

Thrown at Packages/cn.etetet.recast/Scripts/Core/Share/Recast/RecastMeshDetail.cs:1000

            }

            // If the polygon minimum extent is small (sliver or small triangle), do not try to add internal points.
            if (minExtent < sampleDist * 2)
            {
                TriangulateHull(nverts, verts, nhull, hull, nin, tris);
                return nverts;
            }

            // Tessellate the base mesh.
            // We're using the triangulateHull instead of delaunayHull as it tends to
            // create a bit better triangulation for long thin triangles when there
            // are no internal points.
            TriangulateHull(nverts, verts, nhull, hull, nin, tris);

            if (tris.Count == 0)
            {
                // Could not triangulate the poly, make sure there is some valid data there.
                throw new Exception("buildPolyDetail: Could not triangulate polygon (" + nverts + ") verts).");
            }

            if (sampleDist > 0)
            {
                // Create sample locations in a grid.
                RcVec3f bmin = new RcVec3f();
                RcVec3f bmax = new RcVec3f();
                RcVec3f.Copy(ref bmin, @in, 0);
                RcVec3f.Copy(ref bmax, @in, 0);
                for (int i = 1; i < nin; ++i)
                {
                    bmin.Min(@in, i * 3);
                    bmax.Max(@in, i * 3);
                }

                int x0 = (int)Math.Floor(bmin.x / sampleDist);
                int x1 = (int)Math.Ceiling(bmax.x / sampleDist);
                int z0 = (int)Math.Floor(bmin.z / sampleDist);

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Sanitize input geometry before baking: remove duplicate/colinear vertices and zero-area polygons.
  2. Increase contour simplification tolerance only if geometry tolerates it; lower it if slivers appear.
  3. Check the upstream polymesh for degenerate polygons (nverts < 3) and skip them before detail build.
  4. Scale the world so coordinates aren't extremely small (avoids FP degeneracy in triangulation).
Defensive patterns

Strategy: validation

Validate before calling

// Skip degenerate polygons before detail build
foreach (var poly in mesh.polys) {
    int uniqueVerts = CountUniqueVerts(poly, nvp);
    if (uniqueVerts < 3 || IsDegenerate(poly, nvp)) {
        Log.Warning("Skipping degenerate polygon in detail build");
        continue;
    }
}

Try / catch

// Wrap per-polygon detail build so one bad poly doesn't kill the whole mesh
foreach (var poly in polys) {
    try { rcBuildPolyMeshDetail(...); }
    catch (Exception e) when (e.Message.Contains("Could not triangulate")) {
        Log.Warning($"Skipping un-triangulatable poly: {e.Message}");
        continue;
    }
}

Prevention

When it happens

Trigger: TriangulateHull(nverts, verts, nhull, hull, nin, tris) leaves tris.Count == 0. Happens with degenerate input polygons: zero-area hulls, collinear vertices, nverts < 3 after hull construction, or malformed contour winding.

Common situations: Corrupted or degenerate input contour passed to rcBuildPolyMeshDetail; very thin sliver polygons from aggressive simplification; near-zero-area regions produced by bad input geometry (duplicate vertices, T-junctions); floating-point edge cases at extremely small scales.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/cc934ad3c0a739f6. Report an issue: GitHub.