Version 1.0, coming to Fab. Orrery Terrain 1.0 is not on sale yet; this guide can still change before it is.
4. Tutorial: import a heightmap
You bring real ground into the terrain. The plugin imports four kinds of data:
| Kind | Input |
|---|---|
| Heightmap | One 16-bit greyscale PNG, or a 16-bit RAW file, plus a small JSON file next to it. |
| DEM | One GeoTIFF, or several on one grid. |
| Landscape | A Landscape actor in your level. Heights and paint layers come across. |
| Flat or Procedural | Nothing: the terrain generates its own ground. See Quick start. |
All imports are editor-only and run from the Terrain (Orrery) editor mode, tool Manage > New. The tool bakes the data into a tile store asset (an experimental asset type) and points a terrain actor at it.
Part 1: a PNG heightmap
Section titled “Part 1: a PNG heightmap”Prepare the files
Section titled “Prepare the files”You need two files in the same folder:
hills.png: a 16-bit greyscale PNG. It must be square. An 8-bit, colour, palette or alpha PNG is refused, because 8 bits give only 256 height levels and produce terraces.hills.png.json(orhills.json): the sidecar. It tells the importer the post spacing and the height scale.
A RAW file works the same way. Use the extension .r16, .raw or .bin: little-endian 16-bit samples, size x size of them, and the sidecar must give size.
A minimal sidecar for a 1025 by 1025 PNG with posts 2 m apart, where the darkest pixel is 0 m and the brightest 300 m:
{ "size": 1025, "cell_m": 2.0, "height_range_m": [0, 300]}Sidecar keys you can use (unknown keys are ignored):
| Key | Meaning |
|---|---|
size (or width and height) |
Samples per row and rows. A PNG’s own size wins and must agree. |
cell_m |
Post spacing in metres, from 0.25 to 16. |
world_size_m |
Alternative to cell_m: the distance from the first to the last sample. |
height_range_m |
[lowest, highest]: sample 0 is the lowest and 65535 the highest. |
metres_per_unit and z_offset_m |
Alternative to height_range_m: height = z_offset_m + sample x metres_per_unit. |
origin_m |
[x, y] world position of the first sample, in metres. Default [0, 0]. |
byte_order |
"little" (default) or "big". RAW only. |
nodata |
A sample value that means no data. |
crs, vertical_crs, crs_origin |
Recorded for your records. Never used to place the terrain. |
Give cell_m or world_size_m, not both unless they agree.
Import it
Section titled “Import it”- Open a new level and enable the Terrain (Orrery) mode (see Quick start).
- Choose Manage > New.
- Set Source Kind to
Heightmap. - Under Files, add one element and type the absolute path of
hills.png. - The message line shows the file size. The terrain size and post spacing are read from the sidecar when the file is imported.
- Set Asset Folder (default
/Game/Terrain) and Asset Name (defaultTerrain). - Press Create Terrain. The tool blocks until the import is done. There is no cancel.
- Save with File > Save All.
If the import is refused, the message line says why and nothing is created. Common reasons, with the text you will see: no sidecar (a heightmap needs its sidecar: size, post spacing, height scale), a non-square image (only square heightmaps are supported), an 8-bit image (is 8-bit; a 16-bit greyscale PNG is required), a colour image (a greyscale PNG is required), cell_m outside 0.25 to 16 (cell_m 40 is outside 0.25..16 metres), or heights beyond 100 km.
Pressing Create Terrain again with a terrain already in the level replaces the terrain’s source instead of making a second terrain.
You can also make the tile store asset without a terrain in the level: choose Tools > Orrery Terrain > Import Heightmap… (the same entries are on the Terrain toolbar drop-down, and in the Content Browser under Add > Orrery Terrain). It asks for the scale and extent when no sidecar gives them. Dropping a PNG into the Content Browser still makes an ordinary texture, never a terrain.
Part 2: a GeoTIFF
Section titled “Part 2: a GeoTIFF”- In Manage > New, set Source Kind to
DEM. (The kinds areFlat,Procedural,Heightmap,DEMandLandscape; GeoTIFF files useDEM.) - Add one or more absolute paths under Files. With several, the first file wins where they overlap.
- The message line shows one description line per file, or
refused: <reason>. Fix refusals before you press Create Terrain. - Set Decimate to keep every Nth post.
1keeps all of them. The post spacing becomes the pixel size times this number. - Press Create Terrain.
Tools > Orrery Terrain > Import GeoTIFF… (and Add > Orrery Terrain in the Content Browser) makes the tile store asset the same way, without a terrain in the level. A plain TIFF picture still imports as a texture: the GeoTIFF importer only claims a file that DescribeGeoTiffFile does not refuse.
The DEM importer:
- reads a single band of 16- or 32-bit integers or 32- or 64-bit floats;
- places the terrain from the file’s georeferencing: east is X, north is minus Y, and the north-west post is the origin;
- refuses files with several bands, palettes, rotation or shear, an unreadable or geographic coordinate system, and compressions it does not read. Each refusal names the fix, for example how to re-save the file with
gdal_translate; - refuses the import when more than 5 percent of the posts have no data.
Coordinate systems and units
Section titled “Coordinate systems and units”- The file must be in a projected coordinate system (UTM, a national grid such as Swiss LV95 or British National Grid, and so on). A file in latitude and longitude (a geographic system, such as EPSG:4326) is refused, as is a file with no coordinate system or a user-defined one. Re-project it first, for example with
gdalwarp -t_srs EPSG:<code>. - Horizontal units are read as metres, international feet or US survey feet and converted to metres. Any other unit is refused. A file with no unit tag is read as metres, and the message line warns you.
- Heights are read in the same three units and converted to metres. Any other height unit is refused. The importer takes the heights as stored: it records the vertical datum (for example EPSG:5728, the Swiss LN02 heights) but does not convert between datums.
- The post spacing (the pixel size, in metres after conversion, times Decimate) must be 0.25 to 16 m. A file outside that range is refused rather than resampled; resample it first or raise Decimate.
- Placement comes from the file: east is X, north is minus Y, and the north-west post is the world origin.
Missing data
Section titled “Missing data”A sample counts as no data when it equals the file’s GDAL_NODATA value, is not a number, or lies beyond 100 km up or down. The importer handles it in three steps:
- A missing post takes the height of the nearest valid post in the same row (a tie goes to the left). A lake or a gap that is no data in your file therefore becomes a step along each row, at the height of its nearest shores. If you can, fill water with its surface height in the source before you import, or fill the gaps with
gdal_fillnodata. - A row with no valid post at all refuses the import.
- More than 5 percent of the posts with no data refuses the import. The 5 percent counts the posts the importer keeps, after Decimate.
The editor tool and the Python function ImportGeoTiff always use 5 percent. From C++, UOrreryTerrainDemImport::ImportGeoTiffFiles takes the limit as the MaxNodataFraction option, and can replace the file’s nodata value with bOverrideNodata.
Large files
Section titled “Large files”- Test with a window first. Cut a few square kilometres out of the file, check the placement and the heights, then import the whole area.
- Pick the cell size for the game. File size and memory grow with the number of posts: halve the post spacing and you have four times the posts. Set Decimate to keep every Nth post and import a coarser ground from the same file (see Performance and quality, “Memory: choose the cell size first”).
- Tile big areas. Several GeoTIFFs on one grid import as one terrain: the files must share a coordinate system and a pixel size, and where they overlap the first file wins. The importer reads each file in blocks, so a mosaic of 1 km tiles does not need a single file.
- Plan for memory. Measured on the development laptop (Intel Core i7-12700H, 32 GB, Unreal Engine 5.8.4, no rendering), a 16 km square at 1 m raised memory use by about 0.35 GB. The 64 km hero map (64,000 by 64,000 posts) took 120 s to import and 129 s to save, made a 5.35 GB tile store, and the editor process peaked at 12.55 GB, because the import builds the whole store in memory before it saves. Leave room for that on a large import (2026-10-18 and 2026-10-25).
Part 3: a Landscape
Section titled “Part 3: a Landscape”- Have a Landscape actor in the level.
- In Manage > New, set Source Kind to
Landscapeand pick the actor in Landscape. - Press Create Terrain.
The importer reads the Landscape’s final heights and paint layers. It refuses rotated Landscapes, an X scale that differs from the Y scale, unloaded components, and more than 256 paint layers (a layer map, if you give one, must name every layer, with indexes 0 to 255). Holes in the Landscape are dropped. Your Landscape is not changed.
The terrain can hide the source Landscape for you after import (the bHideSourceAfterImport option of ImportLandscape). The editor tool does not expose it, so hide the Landscape yourself.
The editor warns you in the Map Check when a store no longer matches the Landscape it came from.
A real example: the Alpine_Hero ground
Section titled “A real example: the Alpine_Hero ground”The ground of the Alpine_Hero sample map, the product’s own showcase, is real elevation data imported with the steps above. Its source is swissALTI3D, the bare-earth height model of Switzerland from the Federal Office of Topography (swisstopo). swisstopo’s terms allow commercial use and processing of this free data if you give the source; the credit is “©swisstopo”.
| Item | Value |
|---|---|
| Source | swissALTI3D, 0.5 m grid, 1 km tiles, newest survey per tile |
| Area | 64 by 64 km of the Jungfrau region, centred on E 2,630,000 / N 1,166,000 |
| Coordinate system | Swiss national grid CH1903+ / LV95, EPSG:2056, in metres |
| Heights | LN02 (EPSG:5728), in metres |
| Prepared as | One BigTIFF, 32-bit float, Deflate, one band, 64,000 by 64,000 posts at 1 m (each post is the mean of the four 0.5 m posts it covers), about 7.2 GB |
| Missing data | None. The file marks -9999 as no data, and no post uses it. Lakes are in the model as flat surfaces, so they need no fill. |
The samples project’s Scripts/make_dem_alpine.py downloaded the 4,096 tiles (about 70 GB), averaged them to 1 m and wrote the file; the sources, the checks and the credit are in the project’s SourceData/DEM/SOURCES.md. The GeoTIFF importer of Part 2 read that one file with Decimate 1. The result is the 5.35 GB tile store TS_Alpine64, which the map’s terrain uses. Everything you see in the map apart from the store (the station yard, the road and the rail formation, the paint) is added on top with the editor tools, never written into the store.
Import from Python or Blueprint
Section titled “Import from Python or Blueprint”The importers are also editor functions you can call from Python or an editor utility Blueprint. Each takes an existing tile store asset and returns a result with success and report (a one-line summary or the reason for refusal):
| Function | Class |
|---|---|
ImportHeightmap(Path, Store) |
OrreryTerrainHeightmapImport |
ImportGeoTiff(Paths, Store, Decimate) |
OrreryTerrainDemImport |
ImportLandscape(Landscape, Store, bImportPaint, bHideSourceAfterImport) |
OrreryTerrainLandscapeImport |
ImportRaw16(Asset, Path, Width, Height) |
OrreryTerrainImportLibrary (fills a small heightmap source asset) |
Import does not save the asset. Save it yourself.
Check it worked
Section titled “Check it worked”- Press Play.
- In a Blueprint, call Get Ground Height Accurate at a point inside your data and compare it with the height your image should give at that pixel. A PNG or RAW16 import stores whole centimetres, so the read height is within half a centimetre of the height the image gives (a Landscape import keeps 5 mm).
- If you used
origin_m, remember the terrain starts there. A point outside the data returnsNoData.