Version 1.0, coming to Fab. Orrery Terrain 1.0 is not on sale yet; this guide can still change before it is.
9. Tutorial: plates and corridors from code
A plate is a flat area: a building pad, a yard, an apron. A corridor is a linear earthwork: a road, a rail bed, a canal bank. You describe the shape. The terrain cuts the hill, fills the valley, and slopes the sides back to the natural ground.
Plates and corridors are features. They are not painted into the ground. They sit on top of the natural ground and the player’s sculpting, so you can preview them, undo them, or remove them later.
You need a level with a terrain and some relief (the procedural source from Quick start works). Commits run in Play, not in the editor viewport.
Plate in Blueprint
Section titled “Plate in Blueprint”- In the Level Blueprint, add Event BeginPlay.
- Make a Terrain Plate (right-click, Make Terrain Plate) and set:
- Polygon: at least three points in world X and Y, in centimetres, in either winding. For a 40 m square starting 1 km in from the corner:
(100000,100000),(104000,100000),(104000,104000),(100000,104000). - Top Z: the height of the top at the first polygon point, in centimetres. Use a height from Get Ground Height Accurate at that point, to make the pad sit on the ground.
- Grade X and Grade Y: top slope, as rise over run (0.02 is 2 percent). Leave 0 for flat.
- Fill HV and Cut HV: batter slope, horizontal per vertical.
1.5means 1.5 m out for every metre of height.
- Polygon: at least three points in world X and Y, in centimetres, in either winding. For a 40 m square starting 1 km in from the corner:
- Call Evaluate Plate first. It changes nothing and returns the earthworks: Cut Volume and Fill Volume (cubic metres), Footprint Area (square metres), the edit Bounds, wall counts and a flag if some ground was not loaded (Approximate). Use it for a cost preview.
- Call Commit Plate (Async) with the plate. It waits for the ground under the plate, commits it as one step, and fires On Completed with a Commit Result. On Failed gives a reason in its Reason output:
Timeout,NoTerrain,TerrainDestroyed,RefusedorWorldGone. After a refusal, the Result’s Reason text says what the terrain refused.
Keep the Undo token from the result if you want to take the plate back.
The graph as text
- Event BeginPlay (event)
- Out Output Delegate (delegate): not connected
- Then: runs Get Ground Height Accurate
- Get Ground Height Accurate (function call)
- In Location (Vector): (100000.0, 100000.0, 0.0)
- In Accuracy (EOrreryTerrainAccuracy): Best
- In Surface (EOrreryTerrainSurface): Modified
- Then: runs Evaluate Plate
- Out Out Z (Double): to Make Terrain Plate › Top Z
- Out Out Precision (EOrreryTerrainPrecision): not connected
- Out Return Value (Boolean): not connected
- Make Array (pure function)
- In [ 0] (Vector2D): not connected
- In [ 1] (Vector2D): not connected
- In [ 2] (Vector2D): not connected
- In [ 3] (Vector2D): not connected
- Out Array (array of Vector2D): to Make Terrain Plate › Polygon
- Make Terrain Plate (structure)
- In Polygon (array of Vector2D): from Make Array › Array
- In Top Z (Double): from Get Ground Height Accurate › Out Z
- In Grade X (Double): 0.0
- In Grade Y (Double): 0.0
- In Fill HV (Double): 1.5
- In Cut HV (Double): 1.5
- Out Orrery Terrain Plate BP (OrreryTerrainPlateBP): to Evaluate Plate › Plate, Commit Plate (Async) › Plate
- Evaluate Plate (function call)
- In Plate (OrreryTerrainPlateBP): from Make Terrain Plate › Orrery Terrain Plate BP
- Then: runs Commit Plate (Async)
- Out Return Value (OrreryTerrainEarthworks): not connected
- Commit Plate (Async) (latent function)
- In Plate (OrreryTerrainPlateBP): from Make Terrain Plate › Orrery Terrain Plate BP
- In Owner Domain (Integer64): 0
- In Owner Id (Integer64): 0
- In Timeout (Float): 30.0
- Then: not connected
- On Completed: runs Set PlateUndo
- On Failed: runs Print String
- Out Result (OrreryTerrainCommitResult): to Break Orrery Terrain Commit Result › Orrery Terrain Commit Result
- Out Reason (EOrreryTerrainAsyncFailure): to Enum to String › Enumerator
- Break Orrery Terrain Commit Result (structure)
- In Orrery Terrain Commit Result (OrreryTerrainCommitResult): from Commit Plate (Async) › Result
- Out Undo (OrreryTerrainUndoToken): to Set PlateUndo › Plate Undo
- Set PlateUndo (variable set)
- In Plate Undo (OrreryTerrainUndoToken): from Break Orrery Terrain Commit Result › Undo
- Out Output Get (OrreryTerrainUndoToken): not connected
- Enum to String (pure function)
- In Enumerator (EOrreryTerrainAsyncFailure): from Commit Plate (Async) › Reason
- Out Return Value (String): to Print String › In String
- Print String (function call; development only)
- In In String (String): from Enum to String › Return Value
- In Print to Screen (Boolean): true
- In Print to Log (Boolean): true
- In Text Color (LinearColor): (R=0.0, G=0.66, B=1.0, A=1.0)
- In Duration (Float): 2.0
- In Key (Name): None
BP_PlateBuilder in the samples project (/Game/DocExamples). To paste, the Blueprint needs a variable PlateUndo (OrreryTerrainUndoToken).
Corridor in Blueprint
Section titled “Corridor in Blueprint”- Add a Spline component to an actor in the level. Place its points over the terrain. The spline’s Z is the formation height.
- Make a Terrain Corridor and set:
- Half Width Left, Half Width Right: the flat formation band either side of the centreline, centimetres. Defaults: 300 each (a 6 m road bed).
- Station Spacing: distance between sample points along the line, in centimetres, kept between 10 and 199.
- Formation Source:
From Pointsfollows the spline’s height.Ground Plus Offsetfollows the natural ground plus Formation Offset, for a road that hugs the terrain. - Crossfall Percent: cross slope, positive raises the left edge.
- Fill HV, Cut HV: batters, as for plates.
- Call Evaluate Corridor From Spline for the preview, then Commit Corridor From Spline (synchronous, on the game thread). With ground that is not loaded yet, nothing is built and the missing tiles start loading. They keep loading even far from every camera and invoker, so call it again on a later frame.
- Without a spline, fill Points (at least two world points) and call Evaluate Corridor, Commit Corridor or Commit Corridor (Async).
Tunnels and bridges
Section titled “Tunnels and bridges”A corridor can leave stretches of ground alone: a bridge, a viaduct, a tunnel bore. Add entries to Keep Out. Each has a start and end distance along the centreline (centimetres). Tick Opening At Start or Opening At End to fold the cutting to a portal and open a hole in the hillside. The portal settings control the shape (head width, fold length, depth, opening width and height). The hole opens only where the ground over the track at the portal is lower than the opening height (6.5 m by default); under a higher hill the cutting folds but no hole opens.
The terrain only shapes the ground and the hole. Your game draws the tunnel mouth and the bridge.
Sculpting never touches ground within the Keep Out Margin of a keep-out run.
Commit from C++
Section titled “Commit from C++”#include "OrreryTerrainLibrary.h"
void AMyBuilder::BuildRoad(const USplineComponent* Spline){ FOrreryTerrainCorridorBP Road; Road.HalfWidthLeft = 400.0; Road.HalfWidthRight = 400.0; Road.FormationSource = EOrreryTerrainFormationSource::GroundPlusOffset; Road.FormationOffset = 20.0; Road.OwnerDomain = 0x524F4144; Road.OwnerId = MyRoadId;
const FOrreryTerrainEarthworks Preview = UOrreryTerrainLibrary::EvaluateCorridorFromSpline(this, Spline, Road); if (!Preview.bValid || Preview.bApproximate) return;
const FOrreryTerrainCommitResult Result = UOrreryTerrainLibrary::CommitCorridorFromSpline(this, Spline, Road); if (Result.bOk) { LastUndo = Result.Undo; }}The graph as text
- BuildRoad (event; Custom Event; C++ line 3)
- Out Output Delegate (delegate): not connected
- Then: runs Evaluate Corridor from Spline
- Out Spline (SplineComponent object): to Evaluate Corridor from Spline › Spline, Commit Corridor from Spline › Spline
- Get MyRoadId (variable; C++ line 11)
- Out My Road Id (Integer64): to Make Orrery Terrain Corridor BP › Owner Id
- Make Orrery Terrain Corridor BP (structure; C++ lines 5–11)
- In Formation Source (EOrreryTerrainFormationSource): GroundPlusOffset
- In Formation Offset (Double): 20.0
- In Half Width Left (Double): 400.0
- In Half Width Right (Double): 400.0
- In Owner Domain (Integer64): 1380926276
- In Owner Id (Integer64): from Get MyRoadId › My Road Id
- Out Orrery Terrain Corridor BP (OrreryTerrainCorridorBP): to Evaluate Corridor from Spline › Params, Commit Corridor from Spline › Params
- Evaluate Corridor from Spline (function call; C++ line 13)
- In Spline (SplineComponent object): from BuildRoad › Spline
- In Params (OrreryTerrainCorridorBP): from Make Orrery Terrain Corridor BP › Orrery Terrain Corridor BP
- Then: runs Branch 1
- Out Return Value (OrreryTerrainEarthworks): to Break Orrery Terrain Earthworks › Orrery Terrain Earthworks
- Break Orrery Terrain Earthworks (structure; C++ line 14)
- In Orrery Terrain Earthworks (OrreryTerrainEarthworks): from Evaluate Corridor from Spline › Return Value
- Out Valid (Boolean): to NOT › A
- Out Approximate (Boolean): to OR › B
- NOT (operator; C++ line 14)
- In A (Boolean): from Break Orrery Terrain Earthworks › Valid
- Out Return Value (Boolean): to OR › A
- OR (operator; C++ line 14)
- In A (Boolean): from NOT › Return Value
- In B (Boolean): from Break Orrery Terrain Earthworks › Approximate
- Out Return Value (Boolean): to Branch 1 › Condition
- Branch 1 (flow control; C++ line 14)
- In Condition (Boolean): from OR › Return Value
- True: not connected
- False: runs Commit Corridor from Spline
- Commit Corridor from Spline (function call; C++ line 16)
- In Spline (SplineComponent object): from BuildRoad › Spline
- In Params (OrreryTerrainCorridorBP): from Make Orrery Terrain Corridor BP › Orrery Terrain Corridor BP
- Then: runs Branch 2
- Out Return Value (OrreryTerrainCommitResult): to Break Orrery Terrain Commit Result › Orrery Terrain Commit Result
- Break Orrery Terrain Commit Result (structure; C++ lines 17–19)
- In Orrery Terrain Commit Result (OrreryTerrainCommitResult): from Commit Corridor from Spline › Return Value
- Out Ok (Boolean): to Branch 2 › Condition
- Out Undo (OrreryTerrainUndoToken): to Set LastUndo › Last Undo
- Branch 2 (flow control; C++ line 17)
- In Condition (Boolean): from Break Orrery Terrain Commit Result › Ok
- True: runs Set LastUndo
- False: not connected
- Set LastUndo (variable set; C++ line 19)
- In Last Undo (OrreryTerrainUndoToken): from Break Orrery Terrain Commit Result › Undo
- Out Output Get (OrreryTerrainUndoToken): not connected
BP_MyBuilder in the samples project (/Game/DocExamples). To paste, the Blueprint needs a variable MyRoadId (int64) and a variable LastUndo (OrreryTerrainUndoToken).
Preview.bApproximate is true while some of the ground under the road has not loaded: the function then returns without building, and you call it again later (or register an invoker first, see below). MyRoadId and LastUndo are yours. 0x524F4144 is the four letters ROAD. The owner domain is a number you choose. By convention it is four letters, and it must be in the range 0 to 4294967295.
Make sure the ground is there
Section titled “Make sure the ground is there”A commit over ground that is not loaded yet builds nothing, and the missing tiles start loading. It needs more ground than its footprint: the terrain rebuilds the ground in blocks, so a short road can need tiles several hundred metres away, past a 500 m invoker. A synchronous commit that built nothing keeps the tiles it requested for up to a minute, until the same owner commits again, so calling it again on a later frame succeeds once they are in. Give each builder its own Owner Id: up to 16 refused commits keep their tiles at once. Two other ways:
- Use the async nodes. They wait for the tiles and time out after 30 seconds by default.
- Register an invoker at the place you build. In C++, call
RegisterInvoker(Actor, Radius_m)on the subsystem. Full-resolution ground then stays loaded around that actor until you callUnregisterInvoker. The default radius is 500 m. In Blueprint, use Wait For Terrain Ground with the locations you need.
Undo and remove
Section titled “Undo and remove”- Undo Construction takes the token from a commit. It returns a result whose own token redoes the commit. A token is refused once the features it names have changed, or after a terrain load.
- Remove Features By Owner removes everything stated with one owner domain and id, one undo step each, and returns the count. Use it when a player demolishes a road you made.
- Get Feature Info reads back the kind (
Corridor,Plate,Channel), the owner and the bounds of a feature by id.
Features save and load with the terrain. See Save and load.
Live splines (runtime)
Section titled “Live splines (runtime)”A Terrain Spline Feature component ties one corridor or plate to a spline, so the ground can follow the spline when it changes. It is Experimental in v1.0. This section covers game worlds, Play in Editor included. In the editor the component follows your spline edits by itself: see Live splines in the editor.
- Add a Terrain Spline Feature component to an actor that has a Spline component. Leave Spline empty to use the actor’s first spline.
- Set Kind:
Corridorfor an open spline (the centreline) orPlatefor a closed one (the outline). Fill in the corridor’s or the plate’s settings. The spline replaces their points or polygon. A plate’s outline gets a vertex every Outline Spacing centimetres, at most 2048 vertices. - Call Rebuild when your game’s edit ends. The first call adds the feature. Later calls replace it in place, with the same feature id. It returns a Commit Result with an undo token. Over ground that is not loaded, it is refused as Commit Corridor From Spline is: call it again on a later frame, or use Rebuild Spline Feature (Async).
- Remove Feature removes the feature and unlinks the component. Destroying the actor leaves the ground as it is.
Rebuild Spline Feature (Async) samples the spline once, when it starts. It streams the footprint plus 300 m, tries every frame, and fails after its Timeout (30 seconds by default). It fails at once when waiting cannot help: in an editor world, on a read-only terrain, for a Baked or Copy component, or for a spline the kind refuses. The component reports Pending meanwhile.
Nothing rebuilds by itself
Section titled “Nothing rebuilds by itself”The component never rebuilds on its own: your game knows when an edit ends (a player releases a handle, confirms a route) and calls Rebuild then. To know whether it should:
- Is Stale is true when the feature exists but the spline and settings as they are now do not build it: they changed since the last build, or another author replaced the feature with another shape (your own game code, Undo Construction, another player’s request). It turns false again when the spline matches the ground once more.
- Get State tells you more:
Unlinked,Linked,Stale,Pending(an async rebuild waits for ground),Missing(the feature was removed elsewhere),Baked(the terrain’s source changed and the feature is gone, so Rebuild is refused) orCopy(another component in the level holds the same key). - Evaluate returns the cut and fill a Rebuild would make now, without changing anything: use it for a preview while the player drags.
Is Stale and Get State resample the spline, so call them when the spline changes rather than every frame. Evaluate costs more. Run it only when the spline has changed, and at most every 100 ms or four times its last cost, whichever is longer.
On a 2022 laptop (Core i7-12700H), for a 1 km double-track corridor on hills:
| Call | 1 m cells | 2 m cells |
|---|---|---|
| Rebuild (replace in place, from the call to the new ground) | 15 ms | 8 ms |
| Chunk rebuild on the next 2 frames, game thread | under 1 ms | under 1 ms |
| Evaluate | 2 ms | 1.1 ms |
| Is Stale, Get State | 0.1-0.2 ms | 0.1-0.2 ms |
The actor saves the feature’s id and its owner key. After a terrain load the component finds its feature again, without changing the ground.
In multiplayer
Section titled “In multiplayer”Only the server builds. What each side may call:
- Server: everything above.
- Client: Evaluate, Is Stale, Get State and Get Feature Id read the client’s own copy of the ground. Rebuild and Remove Feature are refused. Rebuild Spline Feature (Async) asks the server to replace the linked feature (same id) and completes once the server’s change has arrived on that client; its result carries no undo token. A client cannot add a feature: on an unlinked component the node fails at once.
- The server’s edit policy judges each request. With no policy, a client may replace only the features it made, so a feature the server or the editor built is refused as
Policy. Override Can Change Feature to allow it (see Edit rules).
After any replace, on the server and on every client, a component linked to that feature answers Is Stale for its own spline: true when that machine’s spline does not build the new ground. Splines are your actors’ data, so replicate their points yourself if every machine should agree. A player who joins after the replace, or a component that streams in after it, reconciles with the ground when the join has loaded or the component registers, so it answers as the server does. Nothing is built or sent.
Query the result
Section titled “Query the result”- Get Ground Height Accurate and Sample Ground read the new ground.
- Get Grade Along gives the slope along a direction, as rise over run. Use it to check a road’s gradient.
- Get Ground Height Range gives the lowest and highest ground within a circle (a centre in world cm, a radius in cm), from the loaded tiles. It returns false when no ground lies under the circle.
Check it worked
Section titled “Check it worked”- Evaluate a plate on a hillside. Cut Volume and Fill Volume are both above zero.
- Commit it. Walk or fly there: a level pad sits in the hill, with sloped sides.
- With both grades at zero, Get Ground Height Accurate at any point on the pad equals Top Z to within a centimetre.
- Call Undo Construction with the token. The hill comes back.