RMC Usage

Building your own provider

This tutorial will show how to make a multithreadable provider, so that once it is done, the only thing you’ll need to care about are the arguments given to it, and no longer the mesh.

Creating the class

You can start by copying one of the builtin providers, such as the cube or sphere, as these will come with all of the most important functions, whereas generating the class will give you no functions, leaving you to paste them.

Provider setup

When the provider is first initialized, void Initialize() { } will be called. This is where you’ll need to declare all needed materials, LODs and sections, declared in this order preferably. For the materials, you don’t actually set them up, instead you setup a slot to receive a material and then you attach section to use it, using void SetupMaterialSlot(int32 MaterialSlot, FName SlotName, UMaterialInterface* InMaterial). For the LODs, you might declare at which point they no longer render, expressed in screen percentage, after which the LOD with a higher index will render instead, or if it doesn’t exist, it won’t render at all; using void ConfigureLODs(const TArray<FRuntimeMeshLODProperties>& InLODs). For the sections, you can specify a lot of things, most important being to use either 16bit or 32bit indices and the update frequency. This is needed when you have more than 2^16 (65536) vertices. Use void CreateSection(int32 LODIndex, int32 SectionId, const FRuntimeMeshSectionProperties& SectionProperties). Note : You can set if 32bit indices are needed later, when starting to generate the mesh, by recreating the triangle stream. Note : Update frequency will change the time taken to render and the time taken to make the mesh, Frequent taking more time to make the mesh but less time to render, and Infrequent taking less time to make the mesh but more time to render. No testing has been done on the real improvements given by these different render path, feel free to try it and tell us the results ! Ping me on the discord, @Moddingear#8281

Updating the parameters

You’ll need to create a getter and a setter for each parameter, if you wish to be able to modify them individually. You’ll also need to have a class-wide scope lock, in order to use multithreading. Here’s a example, using the box provider :

RuntimeMeshProviderBox.h

class RUNTIMEMESHCOMPONENT_API URuntimeMeshProviderBox : public URuntimeMeshProvider
{
	GENERATED_BODY()
private:
	mutable FCriticalSection PropertySyncRoot;
	FVector BoxRadius;
public:
	FVector GetBoxRadius() const;
	void SetBoxRadius(const FVector& InRadius);
	/*there's other stuff after but that's not the point*/
}

RuntimeMeshProviderBox.cpp

FVector URuntimeMeshProviderBox::GetBoxRadius() const
{
	FScopeLock Lock(&PropertySyncRoot);
	return BoxRadius;
}

void URuntimeMeshProviderBox::SetBoxRadius(const FVector& InRadius)
{
	FScopeLock Lock(&PropertySyncRoot);
	BoxRadius = InRadius;

	MarkAllLODsDirty();
	MarkCollisionDirty();
}

Note : You can call any setup function you like, and calling the setter before initialization will not cause any problems, as the provider is not connected to the RMC, so the setup calls will just be dropped. Note : void MarkCollisionDirty(); void MarkAllLODsDirty(); void MarkLODDirty(int32 LODIndex) are used to force an update on the RMC, in order to tell it that you want the mesh data to be fetched again.

Making a visible mesh

I’ll assume you’ve read the quickstart guide, if not, click here TODO ADD LINK. A mesh needs two things : Bounds, and renderable data. Bounds should fit your mesh tightly. If they are too big, you’ll waste resources drawing something invisible. If they are too small, the mesh will pop into existence. Try to find some simple way to compute them, then the RMC will call FBoxSphereBounds GetBounds() to get them. For the renderable data, the RMC will call bool GetSectionMeshForLOD(int32 LODIndex, int32 SectionId, FRuntimeMeshRenderableMeshData& MeshData) or bool GetAllSectionsMeshForLOD(int32 LODIndex, TMap<int32, FRuntimeMeshSectionData>& MeshDatas), depending on the LOD configuration. Unless there’s an error, you should return true. This call may be made out of the game thread, so you should always use the scope lock to get the needed parameters. Tbh you should always use the scope lock when getting the parameters, just in case. Note : Some functions may help you to make the mesh, for example the RuntimeMeshModifierNormals can compute the normals and tangents of the mesh.

Making a collidable provider

In Unreal, there are two types of collidables : simples and complex. Simples can be added to the provider when requested by the RMC, when it calls FRuntimeMeshCollisionSettings GetCollisionSettings(). Complexes need to be enabled by returning true to bool HasCollisionMesh() when called by the RMC, and then giving a mesh to bool GetCollisionMesh(FRuntimeMeshCollisionData& CollisionData). The mesh itself is slightly different in structure from the renderable one. Note : There’s an array called CollisionSources in the CollisionData, that’s for debugging purposes, to tell what the collision mesh was generated by. Note : RuntimeMeshProviderCollision can turn a renderable LOD/section into a collidable mesh, it’s a passthrough provider.

Using LODs

Same as with making a visible mesh, except you use the LODIndex given to you when the function is called by the RMC. That’s the beauty of providers.

Using it in the RuntimeMesh(Component)

Create the provider (it’s a UObject), give it the parameters you want, then call RMC->Initialize(Provider); You can store the pointer to the provider to update the parameters later if you need to

Using the RMC like the PMC (RuntimeMeshComponentStatic)

What is the RuntimeMeshComponentStatic ?

The RuntimeMeshComponentStatic, or RMCS or RMCStatic, is a component made to replace the ProceduralMeshComponent (PMC) by providing the same kind of functions as the PMC while giving the advantages of the RMC. For instance, it allows LODs, mesh sharing, lower level access to collision and different mesh cooking modes. It is named static because it directly exposes the functions found inside the StaticProvider, and creates the provider automatically for you. Note : The RMCS can only share it’s mesh data with other RMCS (or RMC, as long as they don’t mind having the same provider structure); when sharing, you only need to give the mesh data to one component, the other will be updated automatically; giving it multiple times will only waste resources. Note : RMCs is RuntimeMeshComponents, plural.

Adding it to your code

Add #include "Components/RuntimeMeshComponentStatic.h to the header or .cpp of the class you want to use it in. Then, you can create it like any static mesh component or the likes.

Giving it mesh data

If you’re using the RMCS from Blueprint, you have two choices : Eiher give the mesh from it’s components using Create Section From Components, or give it using Create Section. The later requires you to create a Runtime Mesh Renderable Mesh Data, for that use Create Renderable Mesh Data, and then you can access all of it’s streams using Get <name of the stream> Stream. See Source/RuntimeMeshComponent/Public/RuntimeMeshBlueprintFunctions.h for more details. If you want to change he mesh data instead, you can call the functions with the same name but Create is replaced with Update. You can remove a section by calling Clear Section.

If you use the RMCS from C++, the functions are the same, but you have more variants :

void CreateSection_Blueprint(int32 LODIndex, int32 SectionId, const FRuntimeMeshSectionProperties& SectionProperties, const FRuntimeMeshRenderableMeshData& SectionData);
void CreateSectionFromComponents(int32 LODIndex, int32 SectionIndex, int32 MaterialSlot, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
	const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FVector2D>& UV2, const TArray<FVector2D>& UV3, const TArray<FLinearColor>& VertexColors,
	const TArray<FRuntimeMeshTangent>& Tangents, ERuntimeMeshUpdateFrequency UpdateFrequency = ERuntimeMeshUpdateFrequency::Infrequent, bool bCreateCollision = true);
void CreateSectionFromComponents(int32 LODIndex, int32 SectionIndex, int32 MaterialSlot, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
	const TArray<FVector2D>& UV0, const TArray<FVector2D>& UV1, const TArray<FVector2D>& UV2, const TArray<FVector2D>& UV3, const TArray<FColor>& VertexColors,
	const TArray<FRuntimeMeshTangent>& Tangents, ERuntimeMeshUpdateFrequency UpdateFrequency = ERuntimeMeshUpdateFrequency::Infrequent, bool bCreateCollision = true);
void CreateSectionFromComponents(int32 LODIndex, int32 SectionIndex, int32 MaterialSlot, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
	const TArray<FVector2D>& UV0, const TArray<FLinearColor>& VertexColors, const TArray<FRuntimeMeshTangent>& Tangents,
	ERuntimeMeshUpdateFrequency UpdateFrequency = ERuntimeMeshUpdateFrequency::Infrequent, bool bCreateCollision = true);
void CreateSectionFromComponents(int32 LODIndex, int32 SectionIndex, int32 MaterialSlot, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals,
	const TArray<FVector2D>& UV0, const TArray<FColor>& VertexColors, const TArray<FRuntimeMeshTangent>& Tangents,
	ERuntimeMeshUpdateFrequency UpdateFrequency = ERuntimeMeshUpdateFrequency::Infrequent, bool bCreateCollision = true); Same is true for updating. Note : When updating, it doesn't need setup data, so the parameters are slightly different.

Note : FRuntimeMeshRenderableMeshData is the way the RMC handles meshes internally, so it is very inconvenient to use without functions to add vertices or triangles. Avoid using it directly from BP as each function call has a fixed time cost. Best prqctice would be to make your mesh in C++ using a library of some sort.

Giving it collision

For simple collision, all you need is to call void SetCollisionSettings(const FRuntimeMeshCollisionSettings& NewCollisionSettings); (Search for Set Collision Settings in BP). If you want complex collision, you can get the complex collision mesh from a renderable LOD, by using void SetRenderableLODForCollision(int32 LODIndex); and you can add or remove sections that should be included in the complex collision using void SetRenderableSectionAffectsCollision(int32 SectionId, bool bCollisionEnabled); You can also choose to handcraft the complex collision mesh and give it to the RMCS using void SetCollisionMesh(const FRuntimeMeshCollisionData& NewCollisionMesh);, the way you build that collision mesh is fairly similar to building a visible mesh, except it doesn’t some of the data.

Extra features

The bounds for the mesh will be computed automatically from the mesh. You can choose to have normals and tangents computed for you using :

UFUNCTION(BlueprintCallable, Category = "RuntimeMeshStatic|Normals")
void EnableNormalTangentGeneration();

UFUNCTION(BlueprintCallable, Category = "RuntimeMeshStatic|Normals")
void DisableNormalTangentGeneration();

UFUNCTION(BlueprintCallable, Category = "RuntimeMeshStatic|Normals")
bool HasNormalTangentGenerationEnabled() const;

If you need tesselation, you can generate the needed triangles using :

UFUNCTION(BlueprintCallable, Category = "RuntimeMeshStatic|Tessellation")
void EnabledTessellationTrianglesGeneration();

UFUNCTION(BlueprintCallable, Category = "RuntimeMeshStatic|Tessellation")
void DisableTessellationTrianglesGeneration();

UFUNCTION(BlueprintCallable, Category = "RuntimeMeshStatic|Tessellation")
bool HasTessellationTriangleGenerationEnabled() const;