Skip to content
PRODUCT GITHUB  ↗

Collision

A Realtime Mesh can drive both simple collision (analytic shapes — spheres, boxes, capsules, convex hulls) and complex collision (per-triangle geometry). The mesh owns the collision data and cooks it into a UBodySetup; the component reports collision to the engine like any other primitive. This page covers the configuration, the two geometry paths, custom collision meshes, and async cooking.

Everything here is grounded in ARealtimeMeshExample_Simple_Collision (Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_Collision.cpp) and the collision types in Public/Interface/Core/RealtimeMeshCollision.h. The collision API lives on URealtimeMeshManaged, so it’s available on Simple, Procedural, and Dynamic meshes.


FRealtimeMeshCollisionConfiguration is the top-level switch. The full set of flags (with defaults):

struct FRealtimeMeshCollisionConfiguration
{
bool bUseComplexAsSimpleCollision; // default true
bool bUseAsyncCook; // default true
bool bShouldFastCookMeshes; // default false
bool bFlipNormals; // default false
bool bDeformableMesh; // default false
bool bMergeAllMeshes; // default false
};
  • bUseComplexAsSimpleCollision — when true, the rendered/complex triangles are also used to satisfy simple collision queries. Turn it off when you supply analytic simple shapes and don’t want per-triangle collision.
  • bUseAsyncCook — cook collision on a worker thread instead of blocking the game thread. On by default; recommended.
  • bShouldFastCookMeshes — use the faster (lower-quality) cook path.
  • bFlipNormals — flip collision-mesh winding.
  • bDeformableMesh — hint for meshes whose collision updates frequently.
  • bMergeAllMeshes — merge all collision meshes into one during cook.

Apply it with SetCollisionConfig:

FRealtimeMeshCollisionConfiguration CollisionConfig;
CollisionConfig.bUseComplexAsSimpleCollision = false; // simple shapes only
CollisionConfig.bUseAsyncCook = true;
RealtimeMesh->SetCollisionConfig(CollisionConfig);
FRealtimeMeshCollisionConfiguration Current = RealtimeMesh->GetCollisionConfig();

SetCollisionConfig returns a TFuture<ERealtimeMeshCollisionUpdateResult> (and has a Blueprint overload taking a completion callback). See Update results below.


FRealtimeMeshSimpleGeometry is a bundle of shape sets, one per shape kind:

struct FRealtimeMeshSimpleGeometry
{
FSimpleShapeSet<FRealtimeMeshCollisionSphere> Spheres;
FSimpleShapeSet<FRealtimeMeshCollisionBox> Boxes;
FSimpleShapeSet<FRealtimeMeshCollisionCapsule> Capsules;
FSimpleShapeSet<FRealtimeMeshCollisionTaperedCapsule> TaperedCapsules;
FSimpleShapeSet<FRealtimeMeshCollisionConvex> ConvexHulls;
};

Every shape shares a base (FRealtimeMeshCollisionShape) with a Name, Center, Rotation, and bContributesToMass. The per-shape fields:

  • Sphere — Radius.
  • Box — Extents. These are full dimensions (they map straight to FKBoxElem.X/Y/Z), so a box built from a half-extent of 250 needs Extents of 500.
  • Capsule — Radius, Length.
  • Tapered capsule — RadiusA, RadiusB, Length.
  • Convex hull — a set of vertices via SetVertices(...).

Build the geometry, add shapes to the relevant set, and apply it with SetSimpleGeometry (returns a TFuture<ERealtimeMeshCollisionUpdateResult>; Blueprint overload takes a callback):

// A 500x500x10 analytic box, matching a rendered 250x250x5 half-extent floor.
FRealtimeMeshSimpleGeometry SimpleGeometry;
FRealtimeMeshCollisionBox CollisionBox;
CollisionBox.Extents = FVector(500.0f, 500.0f, 10.0f);
CollisionBox.Center = FVector::ZeroVector;
CollisionBox.Rotation = FRotator::ZeroRotator;
CollisionBox.bContributesToMass = true;
SimpleGeometry.Boxes.Add(CollisionBox);
RealtimeMesh->SetSimpleGeometry(SimpleGeometry);
FRealtimeMeshSimpleGeometry Current = RealtimeMesh->GetSimpleGeometry();

Analytic shapes are far cheaper than per-triangle collision — prefer them (with bUseComplexAsSimpleCollision = false) whenever a few primitives approximate your mesh well enough. This is the BuildSimpleCollision path in the example.

Each FSimpleShapeSet supports Add, Insert, Update, Remove (by index or name), GetByIndex, GetByName, and GetIndexFromName if you want to manage shapes by name.


Instead of authoring separate collision geometry, you can promote the mesh’s own rendered triangles to complex collision. Two things drive it:

  1. bUseComplexAsSimpleCollision = true in the config, so complex geometry also answers simple queries.
  2. Per-section opt-in via bShouldCreateCollision — a section only contributes its triangles to collision when it’s flagged.

You flag a section at creation or via its config update:

// When creating a section explicitly:
RealtimeMesh->CreateSection(SectionKey, FRealtimeMeshSectionConfig(0),
StreamRange, /*bShouldCreateCollision=*/ true);
// Or when updating an (e.g. auto-created) section's config:
RealtimeMesh->UpdateSectionConfig(SectionKey, FRealtimeMeshSectionConfig(0),
/*bShouldCreateCollision=*/ true);

The example’s BuildComplexCollision sets bUseComplexAsSimpleCollision = true and builds a stepped platform whose rendered triangles become the collision surface — no separate collision geometry is authored.

Complex collision is per-triangle and comparatively expensive to cook and query. Reserve it for shapes that genuinely need it (arbitrary terrain, static level geometry) and lean on simple shapes for everything else.


When you want per-triangle collision that is different from the rendered mesh (a lower-poly collision proxy, say), supply it directly as FRealtimeMeshComplexGeometry — a collection of FRealtimeMeshCollisionMesh objects:

FRealtimeMeshCollisionMesh CollisionMesh;
CollisionMesh.Name = FName("Proxy");
CollisionMesh.SetVertices(MoveTemp(Vertices)); // TArray<FVector3f>
CollisionMesh.SetTriangles(MoveTemp(Triangles)); // TArray<TIndex3<int32>>
// optional: SetMaterials(...), SetTexCoords(...)
FRealtimeMeshComplexGeometry ComplexGeometry;
ComplexGeometry.Add(MoveTemp(CollisionMesh));
RealtimeMesh->SetCustomComplexMeshGeometry(MoveTemp(ComplexGeometry));

The full API on the mesh (all returning TFuture<ERealtimeMeshCollisionUpdateResult>):

  • SetCustomComplexMeshGeometry(...) — by rvalue or const-ref.
  • EditCustomComplexMeshGeometry(TFunctionRef<void(FRealtimeMeshComplexGeometry&)>) — mutate the current geometry in place.
  • ClearCustomComplexMeshGeometry() — remove it.
  • ProcessCustomComplexMeshGeometry(TFunctionRef<void(const FRealtimeMeshComplexGeometry&)>) — read it under lock (don’t retain the reference past the callback).
  • HasCustomComplexMeshGeometry() — whether any custom meshes are set.
RealtimeMesh->EditCustomComplexMeshGeometry([](FRealtimeMeshComplexGeometry& Geometry)
{
Geometry.GetByIndex(0).SetVertices(NewVertices);
});

FRealtimeMeshCollisionMesh caches its cooked result and re-cooks lazily whenever you change its vertices/triangles/materials/UVs, so repeated edits only pay for what changed.


With bUseAsyncCook on (the default), collision cooks on a worker thread and applies when it finishes — so the game thread never blocks on a cook. Because a newer update can supersede an in-flight cook, each collision call reports how it resolved via ERealtimeMeshCollisionUpdateResult:

  • Updated — the collision was updated and applied.
  • Ignored — the update was superseded by a newer one and dropped.
  • Error — the update failed.
  • Unknown — default/unset.

Await the future (or use the Blueprint completion callback) if you need to react:

RealtimeMesh->SetSimpleGeometry(SimpleGeometry)
.Next([](ERealtimeMeshCollisionUpdateResult Result) { /* ... */ });

Two flags on URealtimeMeshComponent (see Public/RealtimeMeshComponent.h) tune what happens when collision updates:

  • KeepMomentumOnCollisionUpdate (default false) — preserve the body’s velocity across a collision rebuild instead of resetting it.
  • bUpdateNavigationOnCollisionUpdate (default true) — refresh the navigation system on each collision update. Disable this for frequently-updated deformable collision meshes to avoid continuous navmesh tile rebuilds. (The navigation update is also skipped automatically when the component can’t affect navigation, regardless of this flag.)

Two Blueprint function libraries assist with collision (Public/RealtimeMeshCollisionLibrary.h):

  • URealtimeMeshCollisionTools — utilities: FindCollisionUVRealtimeMesh (UV at a hit), CookConvexHull / CookComplexMesh (pre-cook a shape so it isn’t cooked lazily later), and AppendStreamsToCollisionMesh (fill an FRealtimeMeshCollisionMesh from a StreamSet, optionally a triangle sub-range).
  • URealtimeMeshSimpleGeometryFunctionLibrary — Blueprint-friendly Add/Insert/Get/Update/Remove (by index and by name) for each simple shape kind (spheres, boxes, capsules, tapered capsules, convex hulls) operating on an FRealtimeMeshSimpleGeometry.

From C++ you can also look up a UV coordinate at a hit directly on the mesh with URealtimeMesh::CalcTexCoordAtLocation(...).