Recreating Slice procedural mesh component UE5.5

Hello,

There is a crash when we do slice procedural mesh component too many time, see :

So i’m recreating a function that do the slice, so the assertion crash does not happens. However, when i’m recreating the the face where the plane has been sliced, i always have 1 or 2 missing triangle, at least visually.

Missing triangle example

the code

static void SliceSectionsByPlane(
    TArray<FProcMeshVertex>& Vertices,
    const FVector& PlaneLocation,
    const FVector& PlaneNormal,
    const TArray<uint32>& Triangles,
    TArray<uint32>& MainHalf,
    TArray<uint32>& OtherHalf
)
{
    TSet<uint32> UniqueCapVertices;

    /**
     * Each triangle are stored in an array of uint32,
     * Each uint32 is an index withing the Vertices array :
     * [Triangles] = { index vertex 1, index vertex 2, index vertex 3, index vertex 1, ... }
     * So a triangle is defined by 3 consecutive elements
     * 
     * that's why 'i += 3'
     */
    for (int32 i = 0; i + 2 < Triangles.Num(); i += 3)
    {
        uint32 p0 = Triangles[i];
        uint32 p1 = Triangles[i + 1];
        uint32 p2 = Triangles[i + 2];

        if (!Vertices.IsValidIndex(p0) || !Vertices.IsValidIndex(p1) || !Vertices.IsValidIndex(p2))
            continue;

        const FProcMeshVertex& V0 = Vertices[p0];
        const FProcMeshVertex& V1 = Vertices[p1];
        const FProcMeshVertex& V2 = Vertices[p2];

        // Check if each vertex is in the part that needs to be kept
        bool v0 = IsPointValid(V0, PlaneNormal, PlaneLocation);
        bool v1 = IsPointValid(V1, PlaneNormal, PlaneLocation);
        bool v2 = IsPointValid(V2, PlaneNormal, PlaneLocation);

        // triangle needs to be entirely  kept
        if (v0 && v1 && v2) {
            MainHalf.Append({ p0, p1, p2 });
            continue;
        }
        // the triangle needs to be totally erased
        if (!v0 && !v1 && !v2) {
            OtherHalf.Append({ p0, p1, p2 });
            continue;
        }
        /**
         * @brief Computes the intersection vertex between an edge and the slicing plane.
         *
         * Given two vertex indices defining an edge, this function computes the
         * intersection point with the slicing plane, interpolates the vertex attributes
         * (UVs and normals), and appends the resulting vertex to the mesh.
         *
         * A second copy of the vertex is also created with its normal aligned to the
         * plane normal. This duplicate is stored in the cap vertex list and is later
         * used to generate the closing cap geometry.
         *
         * @param A Index of the first vertex of the edge.
         * @param B Index of the second vertex of the edge.
         *
         * @return The index of the newly created intersection vertex in the mesh vertex array.
         *
         * @note The cap vertex created internally is added to UniqueCapVertices but its
         * index is not returned.
         */
        auto Intersect = [&](uint32 A, uint32 B) {
            const FProcMeshVertex& VA = Vertices[A];
            const FProcMeshVertex& VB = Vertices[B];

            FVector P = ReplacePointOnPlaneLocal(VA.Position, VB.Position, PlaneLocation, PlaneNormal);

            float EdgeLength = (VB.Position - VA.Position).Size();
            float Alpha = 0.f;

            if (EdgeLength > KINDA_SMALL_NUMBER)
                Alpha = (P - VA.Position).Size() / EdgeLength;

            FProcMeshVertex VNew;
            VNew.Position = P;
            VNew.UV0 = FMath::Lerp(VA.UV0, VB.UV0, Alpha);
            VNew.Normal = FMath::Lerp(VA.Normal, VB.Normal, Alpha).GetSafeNormal();

            uint32 MeshIndex = Vertices.Add(VNew);

            FProcMeshVertex CapVertex = VNew;
            CapVertex.Normal = PlaneNormal;
            uint32 CapIndex = Vertices.Add(CapVertex);
            UniqueCapVertices.Add(CapIndex);

            return MeshIndex;
        };

        // configuration : 1 vertex is in the good side
        if (v0 && !v1 && !v2) {
            uint32 i01 = Intersect(p0, p1);
            uint32 i02 = Intersect(p0, p2);

            MainHalf.Append({ p0, i01, i02 });

            OtherHalf.Append({ p1, p2, i02 });
            OtherHalf.Append({ p1, i02, i01 });
        }
        else if (!v0 && v1 && !v2) {
            uint32 i10 = Intersect(p1, p0);
            uint32 i12 = Intersect(p1, p2);

            MainHalf.Append({ p1, i12, i10 });

            OtherHalf.Append({ p0, i10, p2 });
            OtherHalf.Append({ p2, i10, i12 });
        }
        else if (!v0 && !v1 && v2) {
            uint32 i20 = Intersect(p2, p0);
            uint32 i21 = Intersect(p2, p1);

            MainHalf.Append({ p2, i20, i21 });

            OtherHalf.Append({ p0, p1, i21 });
            OtherHalf.Append({ p0, i21, i20 });
        }

        // configuration inverse : 2 vertices are in the good side
        else if (!v0 && v1 && v2) {
            uint32 i01 = Intersect(p0, p1);
            uint32 i02 = Intersect(p0, p2);

            OtherHalf.Append({ p0, i01, i02 });

            MainHalf.Append({ p1, p2, i02 });
            MainHalf.Append({ p1, i02, i01 });
        }
        else if (v0 && !v1 && v2) {
            uint32 i10 = Intersect(p1, p0);
            uint32 i12 = Intersect(p1, p2);

            OtherHalf.Append({ p1, i12, i10 });

            MainHalf.Append({ p0, i10, p2 });
            MainHalf.Append({ p2, i10, i12 });
        }
        else if (v0 && v1 && !v2)
        {
            uint32 i20 = Intersect(p2, p0);
            uint32 i21 = Intersect(p2, p1);

            OtherHalf.Append({ p2, i20, i21 });

            MainHalf.Append({ p0, p1, i21 });
            MainHalf.Append({ p0, i21, i20 });
        }
    }
    
    // This recreate the face where the plane sliced the mesh
    if (UniqueCapVertices.Num() >= 3) {
        TArray<uint32> CapVertices = UniqueCapVertices.Array();
        
        // Compute center location
        FVector CenterPos = FVector::ZeroVector;
        for (uint32 Index : CapVertices) {
            CenterPos += Vertices[Index].Position;
        }
        CenterPos /= CapVertices.Num();

        // Creating the center vertex
        FProcMeshVertex CenterVertex;
        CenterVertex.Position = CenterPos;
        CenterVertex.Normal = PlaneNormal;
        CenterVertex.UV0 = FVector2D(0.5f, 0.5f);
        uint32 Center = Vertices.Add(CenterVertex);
        for (int32 i = 1; i < CapVertices.Num(); ++i) {
            uint32 A = CapVertices[i % CapVertices.Num()];
            uint32 B = CapVertices[(i - 1) % CapVertices.Num()];
            MainHalf.Append({ Center, B, A });
            OtherHalf.Append({ Center, A, B });
        }
    }
}

I tried sorting the cap vertices angularly around the center, but it made the face disappear entirely.

Are you generating the final triangle that connects the last cap vertex back to the first one?

I build the last triangle, i created a draw debug triangle function :

void DrawDebugFilledTriangle(
    const UWorld* World,
    const FVector& V0,
    const FVector& V1,
    const FVector& V2,
    const FColor& Color = FColor::Red,
    float Duration = 5.f)
{
    if (!World)
        return;

    TArray<FVector> Vertices = { V0, V1, V2 };
    TArray<int32> Indices = { 0, 1, 2 };

    DrawDebugMesh(World, Vertices, Indices, Color, false, Duration, 0);
}

here is the result :

and here, all the triangle are displayed !
Here is the modification in the original code :

    for (int32 i = 1; i < CapVertices.Num(); ++i) {
        uint32 A = CapVertices[i % CapVertices.Num()];
        uint32 B = CapVertices[(i - 1) % CapVertices.Num()];
        /*MainHalf.Append({ Center, B, A });
        OtherHalf.Append({ Center, A, B });*/
        DrawDebugFilledTriangle(World, 
            world_transform.TransformPosition(Vertices[Center].Position),
            world_transform.TransformPosition(Vertices[B].Position),
            world_transform.TransformPosition(Vertices[A].Position));
    }

I changed the draw debug triangle to display more information :

(i scaled up the mesh for testing)
The missing triangle are all on this weird triangle that is almost perpendicular to the other ones…

i made a fix that works at least on this test mesh :

        for (int32 i = 0; i < CapVertices.Num(); i += 2) {
            uint32 A = CapVertices[(i + 1) % CapVertices.Num()];
            uint32 B = CapVertices[(i) % CapVertices.Num()];
            MainHalf.Append({ Center, B, A });
            MainHalf.Append({ Center, A, B });
            OtherHalf.Append({ Center, A, B });
            OtherHalf.Append({ Center, B, A });
        }

i found some forum with the information that UE uses counter clock wise triangle, and the weirds triangle are only created when i is odd and when i is even, i have all the necessary triangle, but not always in the good rotation (clock wise/anti clock wise), so i add both triangle… and it seems to be working… even though i don’t know why it does.
If anyone has a “cleaner solution”, it would be great

the link i found about the direction clock-wise/anticlock-wise :

https://gamedev.net/forums/topic/712275-vertex-winding-order-clockwise-or-counter-clockwise/