Generate Procedural Mesh

Hi,

I use some of the content shared in this thread to make DynamicMesh as a plane.
I succeed in getting the UV setup properly, but when using the VertextColor, I have a strange issue:
I have detailled it here:
https://answers.unrealengine.com/questions/192218/472-vertex-color-material-issue.html

but in a few words, I put the 4 corner vertex to Green and all the other to Red.
here is the result

34202-vertexcolor.png

Anyone faced this issue?

Thanks,

I had the same problem when implementing vertex colors and as far as i remember the problem was, that the vertex colors weren’t passed correctly to CreateSceneProxy() for some strange reason.
Have you tried to log the color of the vertices in CreateSceneProxy()?

In fact, everything is good.

I didn’t take into account the triangle edge position. So the colorization is correct, it’s not what I was looking at the beginning.
The first triangle edge is Between TopLeft and Center, this is why the color gradient is until the center position.

Is there a way to put a weight on vertex to try to fix this ? I would like to have the same result as on Top Right corner.

Thanks,

Hey guys, great work on getting procedural stuff up and going, i’m loving playing around with stuff, but I have a problem and i’m hoping someone can give me some help. (nothing makes me feel more stupid than a basic issue like this) I’m trying to make a procedural sphere, pretty simple right? Well, I’ve looked at many samples of how people do it but when it comes time to make it in-game I get something that looks like this:cbde0f50454b09ade41eb3cee9e48df2256200d9.jpeg

Sorry if its a huge pic, And my code to make this failure is



void SolidSphere(float radius, unsigned int numRings, unsigned int numSectors, TArray<FProceduralMeshTriangle>& Triangles)
{
	const double M_Pi = 3.1415926535897932384626433f;

	float const R = 1.f / (float)(numRings - 1);
	float const S = 1.f / (float)(numSectors - 1);
	unsigned int r, s;
	int counter = 0;

	FProceduralMeshTriangle t1;

	for (r = 0; r < numRings; r++)
	{

		for (s = 0; s < numSectors; s++)
		{
			float  y = FMath::Sin((-M_Pi / 2) + M_Pi * r * R) * radius;
			float  x = FMath::Cos(2 * M_Pi * s * S) * FMath::Sin(M_Pi * r * R) * radius;
			float  z = FMath::Sin(2 * M_Pi * s * S) * FMath::Sin(M_Pi * r * R) * radius;

			if (counter % 3 == 0)
			{
				t1.Vertex0.Position = FVector(x, y, z);
				t1.Vertex0.U = s*S;
				t1.Vertex0.V = r*R;
			}
			if (counter % 3 == 1)
			{
				t1.Vertex1.Position = FVector(x, y, z);
				t1.Vertex1.U = s*S;
				t1.Vertex1.V = r*R;
			}
			if (counter % 3 == 2)
			{
				t1.Vertex2.Position = FVector(x, y, z);
				t1.Vertex2.U = s*S;
				t1.Vertex2.V = r*R;
				Triangles.Add(t1);
			}
			counter++;
		}
	}

}


I’m not too worried about the UV stuff right now, and i’m pretty sure that i’m going wrong with the counter modulo, but its confusing to me to put together 3 points on a sphere that aren’t right next to each other. Can someone point me in the right direction? My next step after this sphere is to do a tessellated cube and normalize the vertices into a sphere, but if this is stumping me… maybe I should go take a basic math class again or something.

Hi! Guys.

I want to draw 2D color vertex. Not use material.

Is it possible in UE4?

How can i do that?

I tried in GeneratedMeshComponent Source, i can’t find solution.

color_vertex.JPG

I want to thank who contributed to this thread, it helped me immensely. I wouldn’t have had a clue where to start with procedural mesh generation in UE4.

Here is a short demo of my current progress with smooth voxel terrain:

https://.com/watch?v=R97nV74nZYo

Hi Meessen,

That is a really cool video. It’s exciting to see someone working on improving the terrain tools, and to see the terrain being modified like this in-game is a real treat. Is this something you are planning on making available as a , or are you just doing this for your own project? Either way, it looks awesome!

[= ;257430]
Hi Meessen,

That is a really cool video. It’s exciting to see someone working on improving the terrain tools, and to see the terrain being modified like this in-game is a real treat. Is this something you are planning on making available as a , or are you just doing this for your own project? Either way, it looks awesome!

[/]

Hello :slight_smile:

I am developing this as my graduation project. My original plan was indeed to release it as a plug-in, but that will have to wait till the end graduation because the main features have priority.
I have never worked with Unreal Engine before and it seems great so far, but I still need to figure out a lot.

The next thing on my list is creating a database to store the voxel data. I hope to add online support to this to allow for collaborative editing.

Regards,

Meessen

Very nice! I worked on voxel terrain on a (sadly cancelled) project. Beyond just making mesh geometry, there’s lots of interesting challenges to tackle to make it look great. Do you plan to work on some sort of vertex painting so you can control the material used through more than just the normal? That gets complicated fast. :slight_smile:

I have thought about vertex painting a bit and it is indeed complicated if you want smooth blending between materials. Hard transitions on triangle edges is simpler but I really don’t like how it looks.

The only system I could think of (for now) with smooth blending is using the vertex color attribute to store a material weight and each index of the color (r,g,b,a) is a locked material for the entire terrain (i.e.: r is always grass, g always rock, etc.). The smooth transition would be handled via the automatic interpolation between the weights into the fragment shader. But this would mean that only a limited amount of materials can be supported due to storage and performance and that every material needs to be present for every voxel, even when its weight is zero.

I have it on the optional features list, but I don’t know yet if I will get to it.

[=Meessen;257625]
I have thought about vertex painting a bit and it is indeed complicated if you want smooth blending between materials. Hard transitions on triangle edges is simpler but I really don’t like how it looks.

The only system I could think of (for now) with smooth blending is using the vertex color attribute to store a material weight and each index of the color (r,g,b,a) is a locked material for the entire terrain (i.e.: r is always grass, g always rock, etc.). The smooth transition would be handled via the automatic interpolation between the weights into the fragment shader. But this would mean that only a limited amount of materials can be supported due to storage and performance and that every material needs to be present for every voxel, even when its weight is zero.

I have it on the optional features list, but I don’t know yet if I will get to it.
[/]

Yeah I wrote a voxel that supports both smooth and blocky terrain and I use vertex color to handle the materials. I personally just keep a master material with each texture in it and blend between vertices, I calculate the normals in the material itself so I use the Tangents to control the weights and the vertex color to control the material ids.

Works pretty well so far although I have some changes I will end up making (like per chunk texture lists assigned through texture parameters).

[=mordentral;258216]
Yeah I wrote a voxel that supports both smooth and blocky terrain and I use vertex color to handle the materials. I personally just keep a master material with each texture in it and blend between vertices, I calculate the normals in the material itself so I use the Tangents to control the weights and the vertex color to control the material ids.

Works pretty well so far although I have some changes I will end up making (like per chunk texture lists assigned through texture parameters).
[/]

Interesting, do you have a video of it?
How do you generate the normals? Currently I calculate them during generation and store them in the voxels. This is a big relief on the mesh extraction complexity (MC), but has an negative impact the generation time and storage requirement.
Also doesn’t the color perform automatic interpolation, messing up the stored IDs?

Edit: or do you pass the density formula to the shader? in which case: how do you combine it with (a lot of) edits?

In areas where I need the actual vertex material ID I round it in the material so it ends up being the value of the closest vertex, haven’t had any real issues with it so far. The side of it is way behind my cubical volume implementation though so I don’t have any pretty images of MC. I got really sidetracked adding Voroni fracturing, and physics particles / gravity to the volume and let the lag behind a bit (it breaks with a lot of the new features, haven’t gone back to fix it yet). I did things a little differently with the texturing but I can’t remember off the top of my head what it was.

Normal generation is DDX / DDY and I have a post in this thread somewhere where I have the node layout for it (also an example of combining the result with material normal maps). This also means that I don’t need to duplicate vertices across triangles where they meet since the vertices don’t need normals/uv maps at all this way, texturing is done with triplaner mapping (as you are using).

Thanks, but DDX / DDY results in flat shading, which is good for cubical terrain but not for smooth terrain. Guess I will have to stick to my current system for now.

:o You are correct, I only use that for non smooth lighting, when I extract for smooth I actually calculate the normals as well.

Sorry, long day at work :stuck_out_tongue:

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);
		}
	}


After days and days, I (and with 's help) fixed the tangents! :smiley:
this is my code if someone wants to check :slight_smile:

ProceduralMeshComponent.h



USTRUCT(BlueprintType)
struct FProceduralMeshVertex
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category = Triangle)
	FVector Position;

	UPROPERTY(EditAnywhere, Category = Triangle)
		FColor Color;

	UPROPERTY(EditAnywhere, Category = Triangle)
		float U;

	UPROPERTY(EditAnywhere, Category = Triangle)
		float V; 
};

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;
};

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

public:
	UProceduralMeshComponent(const FObjectInitializer& ObjectInitializer);

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

	/** Set the shadow smoothness */
	UPROPERTY(EditAnywhere, Category = Triangle)
		float SmoothingsGroups = 2;

	/** Add to the geometry to use on this triangle mesh.  This may cause an allocation.  Use SetCustomMeshTriangles() instead when possible to reduce allocations. */
	UFUNCTION(BlueprintCallable, Category = "Components|ProceduralMesh")
		void AddProceduralMeshTriangles(const TArray<FProceduralMeshTriangle>& Triangles);

	/** Removes all geometry from this triangle mesh.  Does not deallocate memory, allowing new geometry to reuse the existing allocation. */
	UFUNCTION(BlueprintCallable, Category = "Components|ProceduralMesh")
		void ClearProceduralMeshTriangles();

	/** 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();

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

	/** */
	TArray<FProceduralMeshTriangle> ProceduralMeshTris;

	friend class FProceduralMeshSceneProxy;
};


FProceduralMeshSceneProxy



	FProceduralMeshSceneProxy(UProceduralMeshComponent* Component)
		: FPrimitiveSceneProxy(Component)
		, MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel()))
	{
		TArray<FVector> Positions;
		TArray<FVector> tan1;
		TArray<FVector> tan2;
		TArray<FVector> arr_Normals;
		TArray<FVector> arr_SmthNormals;
		//TArray<FVector> arr_TanY;
		//TArray<FVector> arr_TanZ;
		// Add each triangle to the vertex/index buffer
		for (int TriIdx = 0; TriIdx<Component->ProceduralMeshTris.Num(); TriIdx++)
		{
			int32 VIndex;
			FProceduralMeshTriangle& Tri = Component->ProceduralMeshTris[TriIdx];

			const FVector Edge01 = (Tri.Vertex1.Position - Tri.Vertex0.Position);
			const FVector Edge02 = (Tri.Vertex2.Position - Tri.Vertex0.Position);

			const FVector Normals = -FVector::CrossProduct(Edge01, Edge02).GetSafeNormal();

			FVector v1 = Tri.Vertex0.Position;
			FVector v2 = Tri.Vertex1.Position;
			FVector v3 = Tri.Vertex2.Position;

			FVector2D w1 = FVector2D(Tri.Vertex0.U, Tri.Vertex0.V);
			FVector2D w2 = FVector2D(Tri.Vertex1.U, Tri.Vertex1.V);
			FVector2D w3 = FVector2D(Tri.Vertex2.U, Tri.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);

			FDynamicMeshVertex Vert0;
			Vert0.Position = Tri.Vertex0.Position;
			Vert0.Color = Tri.Vertex0.Color;
			Vert0.TextureCoordinate.Set(Tri.Vertex0.U, Tri.Vertex0.V);

			if (!Positions.Contains(Vert0.Position))
			{
				Positions.Add(Vert0.Position);
				arr_Normals.Add(Normals);
				arr_SmthNormals.Add(Normals);
				tan1.Add(sdir);
				tan2.Add(tdir);
				VIndex = VertexBuffer.Vertices.Add(Vert0);
				IndexBuffer.Indices.Add(VIndex);
			}
			else
			{
				VIndex = VertexBuffer.Vertices.Add(Vert0);
				IndexBuffer.Indices.Add(VIndex);
				arr_Normals.Add(Normals);
				arr_SmthNormals[Positions.Find(Vert0.Position)] += Normals;
				tan1[Positions.Find(Vert0.Position)] += sdir;
				tan2[Positions.Find(Vert0.Position)] += tdir;
			}

			FDynamicMeshVertex Vert1;
			Vert1.Position = Tri.Vertex1.Position;
			Vert1.Color = Tri.Vertex1.Color;
			Vert1.TextureCoordinate.Set(Tri.Vertex1.U, Tri.Vertex1.V);

			if (!Positions.Contains(Vert1.Position))
			{
				Positions.Add(Vert1.Position);
				arr_Normals.Add(Normals);
				arr_SmthNormals.Add(Normals);
				tan1.Add(sdir);
				tan2.Add(tdir);
				VIndex = VertexBuffer.Vertices.Add(Vert1);
				IndexBuffer.Indices.Add(VIndex);
			}
			else
			{
				VIndex = VertexBuffer.Vertices.Add(Vert1);
				IndexBuffer.Indices.Add(VIndex);
				arr_Normals.Add(Normals);
				arr_SmthNormals[Positions.Find(Vert1.Position)] += Normals;
				tan1[Positions.Find(Vert1.Position)] += sdir;
				tan2[Positions.Find(Vert1.Position)] += tdir;
			}

			FDynamicMeshVertex Vert2;
			Vert2.Position = Tri.Vertex2.Position;
			Vert2.Color = Tri.Vertex2.Color;
			Vert2.TextureCoordinate.Set(Tri.Vertex2.U, Tri.Vertex2.V);

			if (!Positions.Contains(Vert2.Position))
			{
				Positions.Add(Vert2.Position);
				arr_Normals.Add(Normals);
				arr_SmthNormals.Add(Normals);
				tan1.Add(sdir);
				tan2.Add(tdir);
				VIndex = VertexBuffer.Vertices.Add(Vert2);
				IndexBuffer.Indices.Add(VIndex);
			}
			else
			{
				VIndex = VertexBuffer.Vertices.Add(Vert2);
				IndexBuffer.Indices.Add(VIndex);
				arr_Normals.Add(Normals);
				arr_SmthNormals[Positions.Find(Vert2.Position)] += Normals;
				tan1[Positions.Find(Vert2.Position)] += sdir;
				tan2[Positions.Find(Vert2.Position)] += tdir;
			}
		}

		if (Component->SmoothingsGroups > 0)
		{
			for (int i = 0; i < VertexBuffer.Vertices.Num(); i++)
			{
				int8 handedness;
				FVector t1, t2;
				FDynamicMeshVertex vert = VertexBuffer.Vertices*;
				const FVector n = (arr_Normals* + (arr_SmthNormals[Positions.Find(vert.Position)] / Component->SmoothingsGroups)).GetSafeNormal();
				t1 = tan1[Positions.Find(vert.Position)].GetSafeNormal();
				t2 = tan2[Positions.Find(vert.Position)].GetSafeNormal();
				handedness = (((n ^ t1) | t2) < 0.0F) ? -1.0F : 1.0F;

				VertexBuffer.Vertices*.SetTangents((t1 - n * (n | t1)).GetSafeNormal(),
					(n ^ t1) * handedness,
					n);
			}
		}
		else
		{
			for (int i = 0; i < VertexBuffer.Vertices.Num(); i++)
			{
				int8 handedness;
				FVector t1, t2;
				FDynamicMeshVertex vert = VertexBuffer.Vertices*;
				const FVector n = (arr_Normals*).GetSafeNormal();
				t1 = tan1[Positions.Find(vert.Position)].GetSafeNormal();
				t2 = tan2[Positions.Find(vert.Position)].GetSafeNormal();
				handedness = (((n ^ t1) | t2) < 0.0F) ? 1.0F : -1.0F;;

				VertexBuffer.Vertices*.SetTangents((t1 - n * (n | t1)).GetSafeNormal(),
					(n ^ t1) * handedness,
					n);
			}
		}

		// 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);
		}
	}


I made some normal average system in my code, to get the default normal just change SmoothingsGroups to zero.

Hi,

I may have missed something but when placing my DynamicMesh component is my level, if I light build it, I have an issue:

check(!IsInRenderingThread()); in FDynamicMeshVertexFactory::Init() will stop the process.

I’m sure I miss some settings, but I have no clue of which one.

thanks,

Hi , you must not try to build static lighting with dynamic components, they are incompatible by nature. This had been stated at the beginning of this thread by an UE engineer.

But we have made a modification to the code (see the wiki or my copy of it) so that it does not crash anymore (it still does not participate in static lighting)

I clearly don’t want to have static ligth on it.
It just that I don’t know how it works and how elements are put into the static light process or not. I was thinking of a parameter to set to avoid the process to take it into account.

I will check the updated code for that.

Thanks,