Skip to content
PRODUCT GITHUB  ↗

RealtimeMesh Constructed

URealtimeMeshConstructed doesn’t hold a copy of your mesh. Instead it holds a generator plus a per-mesh descriptor and (re)generates its geometry on demand. The generator is a stateless object that answers one question — “given this descriptor, produce the requested aspects of a mesh” — so a single configured generator can drive many meshes and run concurrently on worker threads. This is the path RMC’s spatial streaming system builds on, and it’s the right choice when geometry is a pure function of some parameters rather than something you author and store.

Header: Source/RealtimeMeshExt/Public/Factory/RealtimeMeshConstructed.h. Like Dynamic, this type lives in the RealtimeMeshExt module — add "RealtimeMeshExt" to your module’s .Build.cs dependencies.


A generator derives from FRealtimeMeshGenerator and overrides only the aspects it produces. Two things travel alongside it rather than living inside it:

  • The config — a USTRUCT deriving from FRealtimeMeshProviderConfig. This is the generator’s static setup (grid resolution, UV scale, …) and doubles as the editor surface: an FInstancedStruct property whose type dropdown is filtered to provider configs.
  • The descriptor — an FInstancedStruct carrying the per-mesh identity/params (cell coordinates, LOD, seed, …). It is passed on each generation request, which is what lets one generator serve many meshes.

Because per-mesh state lives in the descriptor (a plain value) and the config is value-copied, the generation phases are safe to run off the game thread. The generator’s phase methods (GetStructure, GetSectionGroup, optional GetBounds / GetCollision / GetNanite) all run on its AllowedThread() — async by default.

RMC ships a reference generator, the sine-height plane (Source/RealtimeMeshExt/Public/Factory/RealtimeMeshGeneratorPlane.h):

// The config = editor/static setup, shared across every mesh this generator drives.
USTRUCT(BlueprintType)
struct FRealtimeMeshPlaneProviderConfig : public FRealtimeMeshProviderConfig
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, ...) FIntPoint GridSize = FIntPoint(128, 128);
UPROPERTY(EditAnywhere, ...) FVector2f UVScale = FVector2f(1.0f / 1.28f, 1.0f / 1.28f);
};
// The descriptor = per-mesh identity (which cell, what LOD, ...).
USTRUCT(BlueprintType)
struct FRealtimeMeshPlaneCellDescriptor
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, ...) FIntPoint CellLocation = FIntPoint(0, 0);
UPROPERTY(EditAnywhere, ...) int32 LOD = 0;
UPROPERTY(EditAnywhere, ...) FVector2f CellSize = FVector2f(64.0f, 64.0f);
UPROPERTY(EditAnywhere, ...) float CellHeight = 256.0f;
};

Initialize the mesh, resolve a generator for your config, bind it with a descriptor, and regenerate:

#include "Factory/RealtimeMeshConstructed.h"
#include "Factory/RealtimeMeshGeneratorPlane.h"
#include "Factory/RealtimeMeshProviderRegistry.h"
using namespace RealtimeMesh;
URealtimeMeshConstructed* Mesh = Component->InitializeRealtimeMesh<URealtimeMeshConstructed>();
// Pick a config and (optionally) a descriptor. In an actor these are usually FInstancedStruct
// UPROPERTYs edited in the details panel; here we set them up in code.
FInstancedStruct ProviderConfig;
ProviderConfig.InitializeAs<FRealtimeMeshPlaneProviderConfig>();
FInstancedStruct Descriptor;
Descriptor.InitializeAs<FRealtimeMeshPlaneCellDescriptor>();
// Resolve the C++ generator registered for that config struct.
const FRealtimeMeshGeneratorPtr Generator = FRealtimeMeshProviderRegistry::Get().CreateGenerator(ProviderConfig);
if (Generator.IsValid())
{
// Bind the generator + descriptor, then generate every aspect.
Mesh->SetGenerator(Generator.ToSharedRef(), Descriptor);
Mesh->Regenerate(FRealtimeMeshDirtyFlags::AllDirty());
}

SetGenerator stores the generator and descriptor on the mesh; Regenerate(DirtyFlags, CancellationToken) then (re)generates just the aspects whose dirty bit is set — so partial updates (e.g. geometry-only) are cheap. It returns a TFuture<ERealtimeMeshProxyUpdateStatus> and is a no-op if no generator is bound. Call Regenerate again any time the descriptor’s inputs change; the mesh keeps the generator, so there’s no need to re-resolve it.

The ARealtimeMeshConstructedActor example actor (Source/RealtimeMeshExamples/Public/RealtimeMeshConstructedActor.h, Source/RealtimeMeshExamples/Private/RealtimeMeshConstructedActor.cpp) does exactly this.


The example actor exposes the config and descriptor as FInstancedStruct properties, so a designer picks and tunes the generator without code:

// Type dropdown filtered to registered provider configs; defaults to the plane provider.
UPROPERTY(EditAnywhere, Category = "RealtimeMesh",
meta = (BaseStruct = "/Script/RealtimeMeshExt.RealtimeMeshProviderConfig", ExcludeBaseStruct))
FInstancedStruct ProviderConfig;
// Per-mesh descriptor handed to the generator (the plane's cell location / size / LOD).
UPROPERTY(EditAnywhere, Category = "RealtimeMesh")
FInstancedStruct Descriptor;

The meta = (BaseStruct = "...RealtimeMeshProviderConfig", ExcludeBaseStruct) filter is what limits the dropdown to valid provider configs. At construction the actor resolves the chosen config to its generator through FRealtimeMeshProviderRegistry and binds it, just like the C++ snippet above. Point the ProviderConfig dropdown at a registered config, edit its fields, and the matching generator produces the geometry.


To add a provider you write a config USTRUCT (deriving from FRealtimeMeshProviderConfig), a generator deriving from FRealtimeMeshGenerator (or the convenience base TRealtimeMeshProvider<TConfig>, which stores a typed value-copied config), and register the pair so the registry can resolve it. Keep the phase methods thread-safe and const — one generator instance serves many meshes concurrently.

That’s the full custom-generator workflow, covered in Custom Providers.