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.
The generator concept
Section titled “The generator concept”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
USTRUCTderiving fromFRealtimeMeshProviderConfig. This is the generator’s static setup (grid resolution, UV scale, …) and doubles as the editor surface: anFInstancedStructproperty whose type dropdown is filtered to provider configs. - The descriptor — an
FInstancedStructcarrying 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;};Driving it from C++
Section titled “Driving it from C++”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.
Picking a provider in the editor
Section titled “Picking a provider in the editor”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.
Writing your own provider
Section titled “Writing your own provider”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.