Generate Procedural Mesh

[=ACCA software S.p.A.;40176]
Is there a way to create multiple instances of the same CustomMeshComponent? Like InstancedStaticMeshComponent?
[/]

No, you would just need to add multiple components. Or create your own InstancedCustomMeshComponent :slight_smile:

, any hints on the best way to use procedural meshes with SkeletalMeshActors? I noticed you can either have SkeletalMeshComponents (that wrap SkeletalMeshes), or have any AActor derived class attach to a socket. Would the latter be a way of allowing procedural meshes to be connected to a skeletal mesh?

First up, I’m no C++ programmer but if I have no other way, I have to finally learn some… So here’s my question.
If I want to have a deformable Landscape in my game (think worms or scorched earth), would I need what is talked about here or is there an easier way in UE4?

[=JamesG;40270]
No, you would just need to add multiple components. Or create your own InstancedCustomMeshComponent :slight_smile:
[/]

Thanks for the reply.

On the other hand, is it possible to create a procedural StaticMesh at runtime? Can we use RawMesh?

Thanks.

[=ACCA software S.p.A.;44991]
Thanks for the reply.

On the other hand, is it possible to create a procedural StaticMesh at runtime? Can we use RawMesh?

Thanks.
[/]

Yes you can, that’s what most of these procedural mesh discussions have accomplished so far :slight_smile:

Here’s a pic of my own procedural mesh, made at runtime and uv-mapped via algorithm!

Notice that it has collision!

[=;45355]
Yes you can, that’s what most of these procedural mesh discussions have accomplished so far :slight_smile:

Here’s a pic of my own procedural mesh, made at runtime and uv-mapped via algorithm!

Notice that it has collision!

[/]

Your procedural mesh, if I’m not mistaken, was created with UCustomMeshComponent.
What I mean to do is a procedural creation of an UStaticMesh and assign it to an UStaticMeshComponent because, from what I have understood, UStaticMesh supports precomputed lighting, mesh Instancing and has better performance. Am I right?

[=ACCA software S.p.A.;46449]
Your procedural mesh, if I’m not mistaken, was created with UCustomMeshComponent.
What I mean to do is a procedural creation of an UStaticMesh and assign it to an UStaticMeshComponent because, from what I have understood, UStaticMesh supports precomputed lighting, mesh Instancing and has better performance. Am I right?
[/]

From what I understand, UStaticMesh requires an asset, so by definition it cannot be procedurally generated. You could generate an asset and save it (to be able to load), however. Not sure if you would need to reverse engineer how assets are created or reuse and ship some of the UEd code to import assets (there may be legal issues here).

Has anyone been able to get vertex colours working with this?

I’ve tried setting the Color variable for each vertex before it’s added to the buffer (while using a VertexColor node in the material), but it doesn’t seem to have any effect.

re: Vertex Color - make sure you include the underlined line in your VertexFactory, or the color data will never get copied:


			ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
				InitChunkVertexFactory,
				FChunkVertexFactory*, VertexFactory, this,
				const FChunkVertexBuffer*, VertexBuffer, VertexBuffer,
				{
				DataType NewData;
				NewData.PositionComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, Position, VET_Float3);
				NewData.TextureCoordinates.Add(FVertexStreamComponent(VertexBuffer, STRUCT_OFFSET(FDynamicMeshVertex, TextureCoordinate), sizeof(FDynamicMeshVertex), VET_Float2));
				NewData.TangentBasisComponents[0] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, TangentX, VET_PackedNormal);
				NewData.TangentBasisComponents[1] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, TangentZ, VET_PackedNormal);
**				NewData.ColorComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, Color, VET_Color);**
				VertexFactory->SetData(NewData);
			});

Check LocalVertexFactory.h for more info.

[=Ryvar;50833]
re: Vertex Color - make sure you include the underlined line in your VertexFactory, or the color data will never get copied:
[/]

Ah good catch, I’ll make that fix in the code now. Thanks!

[=Ryvar;50833]
re: Vertex Color - make sure you include the underlined line in your VertexFactory, or the color data will never get copied:
[/]

Added the fix to the wiki article on procedural mesh generation.

EDIT: Anyone, feel free to update the code on that page. I haven’t included 's code on UVs and bounds.

[=dmacesic;52644]
Added the fix to the wiki article on procedural mesh generation.

EDIT: Anyone, feel free to update the code on that page. I haven’t included’s code on UVs and bounds.
[/]

The reason I haven’t updated the code for UVs is because the vertex struct that contains UVs is not UPROPERTY() compatible, so I can’t make it a blueprint type struct any more.


//UPROPERTY() //<----- does not compile
FDynamicMeshVertex MyUVFriendlyVertex;

So if I made my UV update, it would break the code for anyone who wants to use it in Blueprints :slight_smile:

Of course I could make my own USTRUCT that did not have that limitation but I figured at that point it was getting pretty project-specific :slight_smile:

I would like to add some code that some may find useful.

With a slight change to the code you can improve the rendering quality of the Generatedmesh.

If you change the FGeneratedMeshSceneProxy constructor from
Code:

[]
class FGeneratedMeshSceneProxy : public FPrimitiveSceneProxy
{
public:

FGeneratedMeshSceneProxy(UGeneratedMeshComponent* Component)
	: FPrimitiveSceneProxy(Component)
	, MaterialRelevance(Component-&gt;GetMaterialRelevance())
{
	const FColor VertexColor(255,255,255);

	// Add each triangle to the vertex/index buffer
	for(int TriIdx=0; TriIdx&lt;Component-&gt;GeneratedMeshTris.Num(); TriIdx++)
	{
		FGeneratedMeshTriangle& Tri = Component-&gt;GeneratedMeshTris[TriIdx];

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

		const FVector TangentX = Edge01.SafeNormal();
		const FVector TangentZ = (Edge02 ^ Edge01).SafeNormal();
		const FVector TangentY = (TangentX ^ TangentZ).SafeNormal();

		FDynamicMeshVertex Vert0;
		Vert0.Position = Tri.Vertex0;
		Vert0.Color = VertexColor;
		Vert0.SetTangents(TangentX, TangentY, TangentZ);
		int32 VIndex = VertexBuffer.Vertices.Add(Vert0);
		IndexBuffer.Indices.Add(VIndex);

		FDynamicMeshVertex Vert1;
		Vert1.Position = Tri.Vertex1;
		Vert1.Color = VertexColor;
		Vert1.SetTangents(TangentX, TangentY, TangentZ);
		VIndex = VertexBuffer.Vertices.Add(Vert1);
		IndexBuffer.Indices.Add(VIndex);

		FDynamicMeshVertex Vert2;
		Vert2.Position = Tri.Vertex2;
		Vert2.Color = VertexColor;
		Vert2.SetTangents(TangentX, TangentY, TangentZ);
		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-&gt;GetMaterial(0);
	if(Material == NULL)
	{
		Material = UMaterial::GetDefaultMaterial(MD_Surface);
	}
}

[/]

To
Code:

[]
FGeneratedMeshSceneProxy(UGeneratedMeshComponent* Component)
: FPrimitiveSceneProxy(Component)
, Material(NULL)
, MaterialRelevance(Component->GetMaterialRelevance())
{
const FColor VertexColor(255, 255, 255);
TArray<FVector> Positions;

	// Add each triangle to the vertex/index buffer
	for (int TriIdx = 0; TriIdx&lt;Component-&gt;GeneratedMeshTris.Num(); TriIdx++)
	{
		FGeneratedMeshTriangle& Tri = Component-&gt;GeneratedMeshTris[TriIdx];
		int32 VIndex;

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

		const FVector TangentX = Edge01.SafeNormal();
		const FVector TangentZ = (Edge02 ^ Edge01).SafeNormal();
		const FVector TangentY = (TangentX ^ TangentZ).SafeNormal();

		FDynamicMeshVertex Vert0;
		Vert0.Position = Tri.Vertex0;
		Vert0.Color = VertexColor;
		Vert0.SetTangents(TangentX, TangentY, TangentZ);

		**if (!Positions.Contains(Vert0.Position))
		{
			Positions.Add(Vert0.Position);
			VIndex = VertexBuffer.Vertices.Add(Vert0);
			IndexBuffer.Indices.Add(VIndex);
		}
		else
			IndexBuffer.Indices.Add(Positions.Find(Vert0.Position));

**
FDynamicMeshVertex Vert1;
Vert1.Position = Tri.Vertex1;
Vert1.Color = VertexColor;
Vert1.SetTangents(TangentX, TangentY, TangentZ);

		**if (!Positions.Contains(Vert1.Position))
		{
			Positions.Add(Vert1.Position);
			VIndex = VertexBuffer.Vertices.Add(Vert1);
			IndexBuffer.Indices.Add(VIndex);
		}
		else
			IndexBuffer.Indices.Add(Positions.Find(Vert1.Position));

**
FDynamicMeshVertex Vert2;
Vert2.Position = Tri.Vertex2;
Vert2.Color = VertexColor;
Vert2.SetTangents(TangentX, TangentY, TangentZ);

		**if (!Positions.Contains(Vert2.Position))
		{
			Positions.Add(Vert2.Position);
			VIndex = VertexBuffer.Vertices.Add(Vert2);
			IndexBuffer.Indices.Add(VIndex);
		}
		else
			IndexBuffer.Indices.Add(Positions.Find(Vert2.Position));**
	}

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

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

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

[/]

The change will keep you from adding redundant vertices to the VertexBuffer and will change this

To

If You want to allow the GeneratedMeshComponent to cast a shadow you will need to change the CalcBounds function from

Code:

[]

FBoxSphereBounds UGeneratedMeshComponent::CalcBounds(const FTransform & LocalToWorld) const
{
FBoxSphereBounds NewBounds;
NewBounds.Origin = FVector::ZeroVector;
NewBounds.BoxExtent = FVector(HALF_WORLD_MAX,HALF_WORLD_MAX,HALF_WORLD_MAX);
NewBounds.SphereRadius = FMath::Sqrt(3.0f * FMath::Square(HALF_WORLD_MAX));
return NewBounds;
}

[/]

To
Code:

[]
FBoxSphereBounds UGeneratedMeshComponent::CalcBounds(const FTransform & LocalToWorld) const
{
FVector BoxPoint = FVector(10,10,10);
return FBoxSphereBounds(FVector(10,0,0), BoxPoint, BoxPoint.Size()).TransformBy(LocalToWorld);

}
[/]

Keep in mind that the changes to CalcBounds reflect the actually size of the geometry and will need to be changed if the geometry changes.

One last thing.
If you want to be able to assign a material in the editor you will have to make a change in GameGeneratedActor.cpp from
Code:

[]
AGameGeneratedActor::AGameGeneratedActor(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{

**TSubobjectPtr&lt;UGeneratedMeshComponent&gt; mesh = PCIP.CreateDefaultSubobject&lt;UGeneratedMeshComponent&gt;(this, TEXT("GeneratedMesh"));**

//Contains the points describing the polyline we are going to rotate
TArray&lt;FVector&gt; points;

points.Add(FVector(20, 5, 0));
points.Add(FVector(15, 6, 0));
points.Add(FVector(12, 7, 0));
points.Add(FVector(11, 8, 0));
points.Add(FVector(8, 7, 0));
points.Add(FVector(7, 6, 0));
points.Add(FVector(4, 5, 0));
points.Add(FVector(3, 4, 0));
points.Add(FVector(2, 3, 0));
points.Add(FVector(1, 4, 0));


TArray&lt;FGeneratedMeshTriangle&gt; triangles;
Lathe(points, triangles,128);
mesh-&gt;SetGeneratedMeshTriangles(triangles);

RootComponent = mesh;

}
[/]

To
Code:

[]
AGameGeneratedActor::AGameGeneratedActor(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{

 **mesh = PCIP.CreateDefaultSubobject&lt;UGeneratedMeshComponent&gt;(this, TEXT("GeneratedMesh"));**

//Contains the points describing the polyline we are going to rotate
TArray&lt;FVector&gt; points;

points.Add(FVector(20, 5, 0));
points.Add(FVector(15, 6, 0));
points.Add(FVector(12, 7, 0));
points.Add(FVector(11, 8, 0));
points.Add(FVector(8, 7, 0));
points.Add(FVector(7, 6, 0));
points.Add(FVector(4, 5, 0));
points.Add(FVector(3, 4, 0));
points.Add(FVector(2, 3, 0));
points.Add(FVector(1, 4, 0));


TArray&lt;FGeneratedMeshTriangle&gt; triangles;
Lathe(points, triangles,128);
mesh-&gt;SetGeneratedMeshTriangles(triangles);

RootComponent = mesh;

}
[/]

Then add to GameGeneratedActor.h
Code:

[]
class AGameGeneratedActor : public AActor
{
GENERATED_UCLASS_BODY()

**TSubobjectPtr&lt;class UGeneratedMeshComponent&gt; mesh;**

void Lathe(const TArray&lt;FVector&gt;& points, TArray&lt;FGeneratedMeshTriangle&gt;& triangles, int segments = 64);

};
[/]

Hope this helps out

[=DayOfWar;55809]
I would like to add some code that some may find useful.
(…)
Hope this helps out
[/]

Looks good! The actual code I am using has diverged from the wiki code quite a bit, since that was meant as an example. But what you provide is valuable, and when I have a bit of time I’ll test it out and update the wiki.

I’ve just copied the code from the wiki example into my project and can create the lathed object in my map. However, it shows up with the default grey material rather than the black which it should have. My code includes the ColorComponent by Ryvar above but it makes no difference. Has anyone else seen this or can anyone offer any advice?

Cheers.

[=;61045]
I’ve just copied the code from the wiki example into my project and can create the lathed object in my map. However, it shows up with the default grey material rather than the black which it should have. My code includes the ColorComponent by Ryvar above but it makes no difference. Has anyone else seen this or can anyone offer any advice?

Cheers.
[/]

In the Actor, define this Property:



	UPROPERTY(BlueprintReadWrite, Category = GeneratedActor)
	UMaterialInterface* Material;


Assign a material to the actor in a blue. In your actor, where you generate your mesh, use something like this:



       GeneratedMeshComponent->SetMaterial(0, Material);


In the code from the wiki, you would need to have a separate function in the actor for generating the mesh. You could then do spawn actor, set material, generate mesh, mesh->SetMaterial(0, Material);

HTH

Oh god, this is amazing!

I love how the Blueprints are very close to ICE inside Softimage, where you can manage data/arrays/geometry with visual data…sweet!!!

Keep going guys, this is awesome!

Dear Dmacesic,

I modified the wiki code to change this


//#if WITH_EDITOR
		ModelBodySetup->InvalidatePhysicsData();
//#endif

to this


		ModelBodySetup->ClearPhysicsMeshes();
		ModelBodySetup->BodySetupGuid = FGuid::NewGuid();


InvalidatePhysicsData would not package for me, causing package fail.

The above solution does package correctly :slight_smile:

And most importantly! We still have custom collision thanks to you research!

I hope you like my change!

Enjoy!

[=;73658]
Dear Dmacesic,

I modified the wiki code to change this


//#if WITH_EDITOR
		ModelBodySetup->InvalidatePhysicsData();
//#endif

to this


		ModelBodySetup->ClearPhysicsMeshes();
		ModelBodySetup->BodySetupGuid = FGuid::NewGuid();


InvalidatePhysicsData would not package for me, causing package fail.

The above solution does package correctly :slight_smile:

And most importantly! We still have custom collision thanks to you research!

I hope you like my change!

Enjoy!

[/]

Oh cool, they put in the cooking change. It’s not a problem, the wiki belongs to . And you are embarrassing me: the code is still Epic’s that I have just rehashed a bit… :wink:

EDIT: Hmm, it’s still in the Roadmap, funny… You still have collisions on a packaged game?

[=dmacesic;73704]
EDIT: Hmm, it’s still in the Roadmap, funny… You still have collisions on a packaged game?
[/]

Well I got past package compiling , but I’ve been sorting through various asset errors for the past 2-3 hours, so i can’t say for sure yet, but at least I can verify it compiles now!

Will let you know!

I’m taking a break from waiting 1 hour to find out 1 asset didn’t make it and then fix that 1 asset and wait 1 hour again :slight_smile:

:slight_smile: