Generate Procedural Mesh

Here’s my current working classes for procedural meshes that calculate tangents correctly. It is currently highly inefficient since it stores an extra copy of all of the triangles in the mesh. If I get some free time I’ll make a tutorial on how I make the procedural doors.


// UE4 Procedural Mesh Generation from the Epic Wiki (https://wiki.unrealengine.com/Procedural_Mesh_Generation)
//
// forked from "Engine/Plugins/Runtime/CustomMeshComponent/Source/CustomMeshComponent/Classes/CustomMeshComponent.h"

#pragma once

#include "ProceduralMeshComponent.generated.h"

USTRUCT(BlueprintType)
struct FProceduralMeshVertex
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category = Vertex)
	FVector Position;

	UPROPERTY(EditAnywhere, Category = Vertex)
		FColor Color;

	UPROPERTY(EditAnywhere, Category = Vertex)
		float U;

	UPROPERTY(EditAnywhere, Category = Vertex)
		float V;

	UPROPERTY(EditAnywhere, Category = Vertex)
		FVector vNormal = FVector(0.f, 1.f, 0.f);

	UPROPERTY(EditAnywhere, Category = Vertex)
		FVector vTangent;

	UPROPERTY(EditAnywhere, Category = Vertex)
		FVector vBitangent;

	UPROPERTY(EditAnywhere, Category = Vertex)
		bool manualNormals = false;
};

USTRUCT(BlueprintType)
struct FProceduralMeshTriangle
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category = Triangle)
		FProceduralMeshVertex Vertex0;

	UPROPERTY(EditAnywhere, Category = Triangle)
		FProceduralMeshVertex Vertex1;

	UPROPERTY(EditAnywhere, Category = Triangle)
		FProceduralMeshVertex Vertex2;

	UPROPERTY(EditAnywhere, Category = Triangle)
		FVector faceNormal = FVector(0.f, 1.f, 0.f);

	UPROPERTY(EditAnywhere, Category = Triangle)
		int32 SmoothingGroup = 0;

	//used to keep track of the triangles intended position in the array
	UPROPERTY(EditAnywhere, Category = Triangle)
		int32 triangleIndexNumber;

	UPROPERTY(EditAnywhere, Category = Triangle)
		TArray<int32> attachedTriangles;

	UPROPERTY(EditAnywhere, Category = Triangle)
		bool normalsHaveBeenCalculated = false;
};

USTRUCT(BlueprintType)
struct FProceduralMeshTriangleSmoothingGroup
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category = SmoothingGroup)
	TArray<FProceduralMeshTriangle> SGTriangle;

	UPROPERTY(EditAnywhere, Category = SmoothingGroup)
		int32 SGIndexNumber;

	UPROPERTY(EditAnywhere, Category = SmoothingGroup)
		float SmoothingTolernace;
};

/** Component that allows you to specify custom triangle mesh geometry */
UCLASS(editinlinenew, meta = (BlueprintSpawnableComponent), ClassGroup = Rendering)
class UProceduralMeshComponent : public UMeshComponent, public IInterface_CollisionDataProvider
{
	GENERATED_UCLASS_BODY()

public:
	/** Set the geometry to use on this triangle mesh */
	UFUNCTION(BlueprintCallable, Category = "Components|ProceduralMesh")
		bool SetProceduralMeshTriangles(const TArray<FProceduralMeshTriangle>& Triangles);

	/** Description of collision */
	UPROPERTY(BlueprintReadOnly, Category = "Collision")
	class UBodySetup* ModelBodySetup;

	// Begin Interface_CollisionDataProvider Interface
	virtual bool GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) override;
	virtual bool ContainsPhysicsTriMeshData(bool InUseAllTriData) const override;
	virtual bool WantsNegXTriMesh() override{ return false; }
	// End Interface_CollisionDataProvider Interface

	// Begin UPrimitiveComponent interface.
	virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
	virtual class UBodySetup* GetBodySetup() override;
	// End UPrimitiveComponent interface.

	// Begin UMeshComponent interface.
	virtual int32 GetNumMaterials() const override;
	// End UMeshComponent interface.

	void UpdateBodySetup();
	void UpdateCollision();

	//calcualte our normals, tangents, and bitangents
	FProceduralMeshTriangle GetNormals(TArray<FProceduralMeshTriangle>& Triangles);

private:
	// Begin USceneComponent interface.
	virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override;
	// Begin USceneComponent interface.

	/** */
	TArray<FProceduralMeshTriangle> ProceduralMeshTris;

	friend class FProceduralMeshSceneProxy;
};



/** Scene proxy */
class FProceduralMeshSceneProxy : public FPrimitiveSceneProxy
{
public:

	FProceduralMeshSceneProxy(UProceduralMeshComponent* Component)
		: FPrimitiveSceneProxy(Component)
#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 5
		, MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel()))
#else
		, MaterialRelevance(Component->GetMaterialRelevance())
#endif
	{
		//Store an array of identical triangles
		TArray<FProceduralMeshTriangle> storedTriangles;
		for (int TriIdx = 0; TriIdx < Component->ProceduralMeshTris.Num(); TriIdx++)
		{
			storedTriangles.Add(Component->ProceduralMeshTris[TriIdx]);
			storedTriangles[TriIdx].triangleIndexNumber = TriIdx;

			//http://www.lighthouse3d.com/opengl/terrain/index.php3?normals
			const FVector Edge01 = (storedTriangles[TriIdx].Vertex1.Position - storedTriangles[TriIdx].Vertex0.Position);
			const FVector Edge02 = (storedTriangles[TriIdx].Vertex2.Position - storedTriangles[TriIdx].Vertex0.Position);
			FVector faceNormal = -(Edge01 ^ Edge02).GetSafeNormal();

			//If we don't have manually input vertex normals then calculate our own
			if (!storedTriangles[TriIdx].Vertex0.manualNormals || !storedTriangles[TriIdx].Vertex1.manualNormals || !storedTriangles[TriIdx].Vertex2.manualNormals)
			{
				storedTriangles[TriIdx].Vertex0.vNormal = faceNormal;
				storedTriangles[TriIdx].Vertex1.vNormal = faceNormal;
				storedTriangles[TriIdx].Vertex2.vNormal = faceNormal;
			}
	}

		
		//search predefined attached triangles for matching vertices and average tangents
		for (int i = 0; i < storedTriangles.Num() - 1; i++)
		{
			//store an array of all attached triangles so that they can have thier normals calculated
			TArray<FProceduralMeshTriangle> attachedTriangles;

			
			if (!storedTriangles*.normalsHaveBeenCalculated)
			{
				attachedTriangles.Add(storedTriangles*);
				storedTriangles*.normalsHaveBeenCalculated = true;

				//add all attached triangles to the array that gets bulk normals calculation
				//set normalsHaveBeenCalculated to true to prevent the values from getting overwritten
				for (int j = 0; j < storedTriangles*.attachedTriangles.Num(); j++)
				{
					attachedTriangles.Add(storedTriangles[storedTriangles*.attachedTriangles[j]]);
					storedTriangles[storedTriangles*.attachedTriangles[j]].normalsHaveBeenCalculated = true;
				}
				

				//Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/code/tangent.html

				for (long a = 0; a < attachedTriangles.Num(); a++)
				{
					//determine which way the normals are facing
					int8 handedness;


					FVector v1 = attachedTriangles[a].Vertex0.Position;
					FVector v2 = attachedTriangles[a].Vertex1.Position;
					FVector v3 = attachedTriangles[a].Vertex2.Position;

					FVector2D w1 = FVector2D(attachedTriangles[a].Vertex0.U, attachedTriangles[a].Vertex0.V);
					FVector2D w2 = FVector2D(attachedTriangles[a].Vertex1.U, attachedTriangles[a].Vertex1.V);
					FVector2D w3 = FVector2D(attachedTriangles[a].Vertex2.U, attachedTriangles[a].Vertex2.V);

					float x1 = v2.X - v1.X;
					float x2 = v3.X - v1.X;
					float y1 = v2.Y - v1.Y;
					float y2 = v3.Y - v1.Y;
					float z1 = v2.Z - v1.Z;
					float z2 = v3.Z - v1.Z;

					float s1 = w2.X - w1.X;
					float s2 = w3.X - w1.X;
					float t1 = w2.Y - w1.Y;
					float t2 = w3.Y - w1.Y;

					float r = 1.0F / (s1 * t2 - s2 * t1);
					FVector sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,
						(t2 * z1 - t1 * z2) * r);
					FVector tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r,
						(s1 * z2 - s2 * z1) * r);

					attachedTriangles[a].Vertex0.vTangent += sdir;
					attachedTriangles[a].Vertex1.vTangent += sdir;
					attachedTriangles[a].Vertex2.vTangent += sdir;

					attachedTriangles[a].Vertex0.vBitangent += tdir;
					attachedTriangles[a].Vertex1.vBitangent += tdir;
					attachedTriangles[a].Vertex2.vBitangent += tdir;


					//attachedTriangles[a].
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.vTangent = attachedTriangles[a].Vertex0.vTangent;
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.vBitangent = attachedTriangles[a].Vertex0.vBitangent;

					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.vTangent = attachedTriangles[a].Vertex1.vTangent;
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.vBitangent = attachedTriangles[a].Vertex1.vBitangent;

					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.vTangent = attachedTriangles[a].Vertex2.vTangent;
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.vBitangent = attachedTriangles[a].Vertex2.vBitangent;


					const FVector n = attachedTriangles[a].Vertex0.vNormal;
					const FVector t = attachedTriangles[a].Vertex0.vTangent;

					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.vNormal = storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.vNormal;

					//calculateHandedness
					handedness = (((n ^ t) | storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.vBitangent) < 0.0F) ? -1.0F : 1.0F;

					// Gram-Schmidt orthogonalize
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.vTangent = (t - n * (n | t)).GetSafeNormal();

					//calculate Bitangent
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex0.vBitangent = (n ^ t) * handedness;



					const FVector nn = attachedTriangles[a].Vertex1.vNormal;
					const FVector tt = attachedTriangles[a].Vertex1.vTangent;

					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.vNormal = storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.vNormal;

					//calculateHandedness
					handedness = (((nn ^ tt) | storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.vBitangent) < 0.0F) ? -1.0F : 1.0F;

					// Gram-Schmidt orthogonalize
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.vTangent = (tt - nn * (nn | tt)).GetSafeNormal();

					//calculate Bitangent
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex1.vBitangent = (nn ^ tt) * handedness;



					const FVector nnn = attachedTriangles[a].Vertex2.vNormal;
					const FVector ttt = attachedTriangles[a].Vertex2.vTangent;

					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.vNormal = storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.vNormal;

					//calculateHandedness
					handedness = (((nnn ^ ttt) | storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.vBitangent) < 0.0F) ? -1.0F : 1.0F;

					// Gram-Schmidt orthogonalize
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.vTangent = (ttt - nnn * (nnn | ttt)).GetSafeNormal();

					//calculate Bitangent
					storedTriangles[attachedTriangles[a].triangleIndexNumber].Vertex2.vBitangent = (nnn ^ ttt) * handedness;
				}

			}
		}
		
		// Add each triangle to the vertex/index buffer
		for (int TriIdx = 0; TriIdx<Component->ProceduralMeshTris.Num(); TriIdx++)
		{
			FProceduralMeshTriangle& Tri = Component->ProceduralMeshTris[TriIdx];

			FDynamicMeshVertex Vert0;
			Vert0.Position = Tri.Vertex0.Position;
			Vert0.Color = Tri.Vertex0.Color;
			Vert0.SetTangents(storedTriangles[TriIdx].Vertex0.vTangent,
				storedTriangles[TriIdx].Vertex0.vBitangent,
				storedTriangles[TriIdx].Vertex0.vNormal);

			Vert0.TextureCoordinate.Set(Tri.Vertex0.U, Tri.Vertex0.V);
			int32 VIndex = VertexBuffer.Vertices.Add(Vert0);
			IndexBuffer.Indices.Add(VIndex);

			FDynamicMeshVertex Vert1;
			Vert1.Position = Tri.Vertex1.Position;
			Vert1.Color = Tri.Vertex1.Color;
			Vert1.SetTangents(storedTriangles[TriIdx].Vertex1.vTangent,
				storedTriangles[TriIdx].Vertex1.vBitangent,
				storedTriangles[TriIdx].Vertex1.vNormal);

			Vert1.TextureCoordinate.Set(Tri.Vertex1.U, Tri.Vertex1.V);
			VIndex = VertexBuffer.Vertices.Add(Vert1);
			IndexBuffer.Indices.Add(VIndex);

			FDynamicMeshVertex Vert2;
			Vert2.Position = Tri.Vertex2.Position;
			Vert2.Color = Tri.Vertex2.Color;
			Vert2.SetTangents(storedTriangles[TriIdx].Vertex2.vTangent,
				storedTriangles[TriIdx].Vertex2.vBitangent,
				storedTriangles[TriIdx].Vertex2.vNormal);

			Vert2.TextureCoordinate.Set(Tri.Vertex2.U, Tri.Vertex2.V);
			VIndex = VertexBuffer.Vertices.Add(Vert2);
			IndexBuffer.Indices.Add(VIndex);

		}

		// Init vertex factory
		VertexFactory.Init(&VertexBuffer);

		// Enqueue initialization of render resource
		BeginInitResource(&VertexBuffer);
		BeginInitResource(&IndexBuffer);
		BeginInitResource(&VertexFactory);

		// Grab material
		Material = Component->GetMaterial(0);
		if (Material == NULL)
		{
			Material = UMaterial::GetDefaultMaterial(MD_Surface);
		}
	}