This document describes BeamNG terrain files and their companion metadata files.
Terrain data is stored mainly in a binary .ter file. When terrain is saved, the engine also writes a companion .terrain.json file that describes the terrain data and referenced materials.
A terrain in a level is represented by a TerrainBlock scene object. The TerrainBlock references the .ter file using its terrainFile field.
This page describes terrain files and terrain material data. For the scene object that places terrain in a level, see TerrainBlock .
| File | Purpose |
|---|---|
.ter |
Binary terrain file containing heightmap data, layer map data, and terrain material names. |
.terrain.json |
Metadata file describing the .ter file. Useful for tools and debugging. |
.terrainheightmap.png |
Optional/exported heightmap image reference written by the terrain save path. |
items.level.json |
Contains the TerrainBlock scene object that references the .ter. |
*.materials.json |
Contains TerrainMaterial definitions referenced by the .ter. |
Terrain files are usually stored inside the level folder.
levels/<levelName>/
Example:
levels/example/theTerrain.ter
levels/example/theTerrain.terrain.json
levels/example/theTerrain.terrainheightmap.png
This root-level layout is conventional in current shipped levels. TerrainMaterial definitions and their textures are stored separately under levels/<levelName>/art/terrains/.
The terrain object itself is stored in the level scene data, for example:
levels/example/main/items.level.json
A terrain is placed in the level with a TerrainBlock object.
Example:
{
"class": "TerrainBlock",
"name": "theTerrain",
"terrainFile": "/levels/example/theTerrain.ter",
"materialTextureSet": "exampleTerrainMaterialTextureSet",
"squareSize": 1,
"maxHeight": 2048,
"screenError": 16,
"castShadows": true,
"position": [-1024, -1024, 0],
"rotationMatrix": [1, 0, 0, 0, 1, 0, 0, 0, 1]
}
| Field | Type | Description |
|---|---|---|
terrainFile |
string | Path to the .ter terrain data file. |
minimapImage |
string | Optional minimap image for this terrain block. |
materialTextureSet |
string | TerrainMaterialTextureSet used by the v1.5 terrain material path. Leave empty only for classic v1 terrain materials. |
castShadows |
bool | Whether terrain casts shadows. |
squareSize |
number | Distance between heightmap samples in meters. |
maxHeight |
number | Maximum terrain height in meters. |
baseTexSize |
integer | Legacy generated terrain base texture resolution. |
lightMapSize |
integer | Legacy terrain lightmap texture size. |
screenError |
integer | Terrain LOD/screen error setting. |
squareSize and maxHeight to control terrain dimensions instead of object scale.The .ter file stores a square grid:
size x size
The world-space terrain width is approximately:
world size = size x squareSize
Example:
size = 2048
squareSize = 1
world size = 2048 m x 2048 m
If squareSize is 2, the same terrain grid covers:
4096 m x 4096 m
Terrain heights are stored as unsigned 16-bit values (u16).
The TerrainBlock converts stored height values to meters using:
heightScale = maxHeight / 65536
heightMeters = storedHeight x heightScale
Example with:
"maxHeight": 2048
The height scale is:
2048 / 65536 = 0.03125 m
So one stored height unit equals approximately 0.03125 m.
The terrain object position is then applied on top of this height.
The .ter file is the main binary terrain data file. It stores terrain height data, material layer indices, and the list of terrain material names.
The file is read sequentially from start to end.
The current TerrainFile::save() path writes this binary layout:
| Step | Field | Type | Size | Description |
|---|---|---|---|---|
| 1 | version |
u8 |
1 byte | Terrain file version. |
| 2 | size |
u32 |
4 bytes | Width/height of the square terrain grid. |
| 3 | heightMap |
u16[size x size] |
size x size x 2 bytes |
Height samples. |
| 4 | layerMap |
u8[size x size] |
size x size bytes |
Terrain material index per sample. |
| 5 | materialCount |
u32 |
4 bytes | Number of terrain material names. |
| 6 | materialNames |
string array | variable | Terrain material internal names. |
Current save pseudocode:
write((u8)FILE_VERSION);
write(mSize);
write(mHeightMap.byteSize(), mHeightMap.address());
write(mLayerMap.byteSize(), mLayerMap.address());
write((u32)mMaterials.size());
for each material:
write(material.internalName);
To read a current .ter file:
version as u8.version <= FILE_VERSION.size as u32.sampleCount = size x size.sampleCount u16 values as the heightmap.sampleCount u8 values as the layer map.materialCount as u32.materialCount strings.TerrainMaterial definitions.Simplified pseudocode:
u8 version;
read(version);
u32 size;
read(size);
u32 sampleCount = size * size;
u16 heightMap[sampleCount];
read(heightMap, sampleCount * sizeof(u16));
u8 layerMap[sampleCount];
read(layerMap, sampleCount * sizeof(u8));
u32 materialCount;
read(materialCount);
for i in materialCount:
string materialName = readString();
The first byte is the terrain file version.
The current terrain file version is 9.
When loading:
version > FILE_VERSION, loading fails.version >= 7, the modern loader path is used.version < 7, a legacy loader path is used.For normal tools and new content, target the current saved layout described above.
The heightmap is stored as:
u16 heightMap[size x size]
Each sample is an unsigned 16-bit integer.
Conversion to meters:
heightMeters = storedHeight x (maxHeight / 65536)
Example:
storedHeight = 32768
maxHeight = 2048
heightMeters = 32768 x (2048 / 65536)
heightMeters = 1024 m
The layer map is stored as:
u8 layerMap[size x size]
Each value is an index into the terrain material name list.
Example:
0 = first terrain material
1 = second terrain material
2 = third terrain material
The value 255 (U8_MAX) is special:
255 = empty terrain / hole
Empty terrain is used by the terrain system to skip rendering and collision in those areas.
After the heightmap and layer map, the file stores terrain material names:
u32 materialCount
string materialNames[materialCount]
These names are resolved at load time using:
TerrainMaterial::findOrCreate(name)
If no valid materials are found, the terrain falls back to a warning material.
The terrain layer map uses u8 indices, with 255 reserved for empty terrain. The loader supports up to 254 terrain material entries.
Materials after that limit are ignored.
For performance and maintainability, keep terrain material counts much lower than the technical limit.
For a current terrain file, before material name strings:
1 byte version
4 bytes size
size x size x 2 bytes heightMap
size x size x 1 byte layerMap
4 bytes materialCount
variable material names
For 2048 x 2048:
heightMap = 2048 x 2048 x 2 = 8 MiB
layerMap = 2048 x 2048 x 1 = 4 MiB
Total map data:
~12 MiB
plus the small header and material name strings.
When the engine saves a terrain, it writes a companion .terrain.json file.
Example:
{
"version": 9,
"datafile": "/levels/example/theTerrain.ter",
"heightmapImage": "/levels/example/theTerrain.terrainheightmap.png",
"size": 2048,
"binaryFormat": "version(char), size(unsigned int), heightMap(heightMapSize * heightMapItemSize), layerMap(layerMapSize * layerMapItemSize), layerTextureMap(layerMapSize * layerMapItemSize), materialNames",
"heightMapSize": 4194304,
"heightMapItemSize": 2,
"layerMapSize": 4194304,
"layerMapItemSize": 1,
"materials": [
"grass",
"rock",
"asphalt"
]
}
| Field | Type | Description |
|---|---|---|
version |
number | Terrain file version. |
datafile |
string | Path to the binary .ter file. |
heightmapImage |
string | Path to associated/exported heightmap image. |
size |
number | Terrain grid size. |
binaryFormat |
string | Engine-generated human-readable description. It is metadata only and may retain legacy field names. |
heightMapSize |
number | Number of heightmap samples. |
heightMapItemSize |
number | Size of each heightmap sample in bytes. |
layerMapSize |
number | Number of layer map samples. |
layerMapItemSize |
number | Size of each layer map sample in bytes. |
materials |
array[string] | Terrain material internal names. |
.terrain.json is descriptive metadata for tools and debugging. The engine loads the actual terrain data from the .ter file referenced by the TerrainBlock.layerTextureMap into the binaryFormat description even though the version 9 payload written by TerrainFile::save() does not contain that array. Parse the .ter file using the current saved layout
, not this descriptive string.The .ter file stores only terrain material names. The actual material definitions are separate TerrainMaterial objects, stored in a materials JSON file.
Terrain materials come in two versions: the classic v1 path and the modern PBR v1.5 workflow. The sections below describe v1.5 - how it is enabled, how it works, and its fields.
Terrain materials have two versions:
| Version | When it is used | Texture model |
|---|---|---|
| v1 (classic) | TerrainBlock.materialTextureSet is empty. |
Single diffuseMap, normalMap, detailMap, macroMap. |
| v1.5 (PBR) | TerrainBlock.materialTextureSet references a TerrainMaterialTextureSet. |
baseColor, normal, roughness, ao, height, each with base/macro/detail textures. |
The whole terrain switches version based on the TerrainBlock. There is no per-material version flag: assigning a materialTextureSet to the TerrainBlock activates v1.5 for every material painted on that terrain.
In the World Editor, the Terrain Material Library has an Upgrade Terrain Materials action. It:
TerrainMaterialTextureSet object (saved in art/terrains/main.materials.json).TerrainBlock.materialTextureSet field.diffuseMap, normalMap, detailMap, macroMap) from existing terrain materials.TerrainBlock change.v1.5 terrain materials and the TerrainMaterialTextureSet are stored in:
levels/<levelName>/art/terrains/main.materials.json
Older levels may still store terrain materials in art/terrains/materials.json. This is a deprecated layout; the editor offers an Upgrade Terrain Material file format action that moves them into main.materials.json.
A v1.5 terrain material is built from five texture groups, and each group is sampled at three scales that combine into the final surface. Understanding this model is the key to authoring good-looking terrain.
baseColor, normal, roughness, ao, and height texture, and for each one supply a base (broad look), a macro (mid-range variation), and a detail (close-up) version. Assign them in the World Editor’s Terrain Material Library, set a groundmodelName (e.g. GRASS), and leave the distance/strength values at the typical defaults shown below. The deep-dive subsections explain how to fine-tune from there.| Group | What it controls | Color space | How to author |
|---|---|---|---|
| Base color | The albedo (surface color). | sRGB | Paint the real color of the surface (grass green, rock gray, …). |
| Normal | Surface relief / bumpiness (tangent-space normal map). | Linear | A normal map. Only red/green are stored; blue is reconstructed by the shader. |
| Roughness | Glossiness. Dark = glossy/wet, bright = rough/matte. | Linear (grayscale) | A grayscale roughness map. |
| Ambient occlusion | Self-shadowing in crevices under ambient light. | Linear (grayscale) | A grayscale AO map; white = no occlusion. |
| Height | Per-pixel height used for layer blending (see below). | Linear (grayscale) | A grayscale heightmap; white = raised, black = recessed. |
Within a single group, the base, macro, and detail textures are three tiling layers at different scales that are sampled and combined per pixel. The simplest way to think about them is as low-, medium-, and high-frequency versions of the same surface:
| Scale | Typical mapping size (*TexSize) |
Repetition | When it is sampled | Role |
|---|---|---|---|---|
| Base | Large - often whole-terrain (≈ 128–2048 m) | Low (large tile, sometimes unique) | Always, at every distance. | The foundation. Sets the broad, large-scale look and the group’s actual value. |
| Macro | Medium (≈ 30–80 m) | Medium | Within macroDistances, capped at 1000 m. |
Mid-scale variation that breaks up the base’s repetition at medium/long range. |
| Detail | Small (≈ 2–8 m) | High (small tile) | Within detailDistances, capped at 250 m. |
Fine, sharp close-up detail. |
Base is the primary layer and the only one that defines an absolute value (the others modify it). In official levels the base is usually a single large texture mapped across the whole terrain - often the same t_terrain_base_* set shared by every material (for example West Coast USA maps it at 2048 m for all materials). Because one tile spans the whole terrain it has no visible repetition, but it is soft / low-resolution up close - which is exactly what the detail layer fixes.
Macro is a second layer at a smaller mapping size (typically 30–80 m). Its job is to hide the fact that the base is repeating: large, soft patches of color or brightness variation that you notice across a hillside but not on a single tile. It is most useful at medium and long range and can be faded with macroDistances.
Detail is a third layer at a very small mapping size (typically 2–8 m), so it repeats often and stays crisp right under the camera or vehicle (individual blades, pebbles, fine normal bumps). Because high-frequency tiling becomes both obvious and expensive at distance, detail is faded out by detailDistances and is never sampled past 250 m.
*BaseTexSize, *MacroTexSize, and *DetailTexSize are world distances in meters for one tile of the texture - not the pixel resolution.
In official content the base is usually the whole-terrain size, macro is around 30–80 m, and detail is around 2–8 m. (If a size field is left unset, the engine defaults are 256 / 60 / 2 m for base / macro / detail.)
A single tiled texture cannot look good at every distance: make the tile large and it is blurry up close; make it small and it tiles visibly across the terrain. Splitting the surface into base + macro + detail lets the terrain stay sharp where the camera is close (detail), varied at mid range (macro), and free of obvious repetition far away (base) - all at the same time.
At a glance, by distance:
far : base (+ macro) -> broad, no obvious tiling
mid : base + macro -> base broken up by variation
near : base + macro + detail -> full crispness
For the color and data groups (base color, roughness, AO, height), the base texture sets the value and macro/detail are added as signed overlays around mid-gray:
final = base + (macro - 0.5) * 2 * macroStrength
+ (detail - 0.5) * 2 * detailStrength
Because of this, macro and detail textures should be authored around mid-gray (0.5): gray = no change, lighter areas brighten the base, darker areas darken it. For the normal group, the base/macro/detail normals are blended together instead of added.
This is the most important behavior to understand. When two terrain materials overlap (where you paint one over another), the engine does not simply cross-fade them. It compares the height texture of each layer per pixel and lets the higher one show through.
This produces natural transitions - for example gravel poking through where it sits “above” sand, instead of a soft, blurry seam.
To use it: author the height map so the parts that should appear first in a blend (pebbles, raised pattern, rocks) are brighter, and the recessed parts (mortar gaps, sand pockets) are darker.
These fields control how macro and detail fade with camera distance - they matter for both looks and performance.
macroDistances / detailDistances - [startFadeIn, near, far, endFadeOut] in meters (ascending). The contribution fades in over startFadeIn → near, is full between near and far, then fades out over far → endFadeOut.*MacroStrength / *DetailStrength - [near, far] intensity (0–1) of the overlay, so you can make a layer strong up close and weaker far away.macroDistAtten / detailDistAtten - [near, far] (0–1) controlling how much the contribution drops at the fade edges (1 = fades fully to zero, 0 = stays).250 m and macro beyond 1000 m. Setting endFadeOut higher than that does not extend them - official content often leaves endFadeOut large (e.g. 3000) and relies on these caps, controlling the real fade with the far value instead.Typical values seen in official levels:
| Field | Common value | Meaning |
|---|---|---|
macroDistances |
[0, 10, 100, 3000] |
Macro fully visible out to ~100 m, present until the 1000 m cap. |
detailDistances |
[0, 0, 30, 60] (or large endFadeOut) |
Detail fully visible to ~30 m, gone well before the 250 m cap. |
*MacroStrength |
[0.2, 0.4] (color), [0.5, 0.6] (normal) |
Subtle on color, stronger on normal. |
*DetailStrength |
[0.3, 0] (color), [0.8, 0.15] (normal) |
Strong up close, fading to nothing far away. |
macroDistAtten / detailDistAtten |
[1, 1] |
Fade in from zero and out to zero. |
Practical guidance:
far short (often 20–50 m). It is high-frequency, only useful up close, and stopping it early also saves performance.height macro/detail strengths are usually left at 0 - height drives layer blending
, so you rarely want macro/detail modulating it.useSideProjection - projects the texture onto vertical faces instead of straight down, so cliffs and steep rock do not look stretched.parallaxScale - adds a parallax / self-occlusion depth effect from the height/normal data.useSideProjection and parallaxScale belong to the classic v1 terrain path. The v1.5 renderer (and the v1.5 material editor) do not use them, so they have no effect on a terrain that has a materialTextureSet assigned.groundmodelName - links the painted area to a groundmodel, which defines the physics surface: friction, tyre particles, skid sounds, rolling resistance, and so on. Match it to the look (GRASS, ROCK, ASPHALT, …). See Groundmodels
.annotation - a semantic/debug class (e.g. GRASS, NATURE) used by the annotation render pass for tools such as semantic segmentation and sensors. Defaults to NATURE.Both fields are used by classic v1 and v1.5 materials.
A v1.5 TerrainMaterial is defined by per-group texture fields plus a few material-level fields. For what each field does and how to use it, see How v1.5 terrain materials work
; this section is a quick name/type lookup.
Values are stored as JSON numbers and arrays: single values as numbers (512, 0.3) and vectors as arrays ([0.3, 0], [0, 10, 100, 3000]).
Each of the five groups - baseColor, normal, roughness, ao, height - exposes the same fields. Build the field name from the group and the scale (Base, Macro, Detail):
| Field pattern | Type | Notes |
|---|---|---|
<group><Scale>Tex |
path | Source texture for that group/scale, e.g. baseColorBaseTex, normalDetailTex. |
<group><Scale>TexSize |
number | World mapping size in meters (typically whole-terrain for base, 30–80 for macro, 2–8 for detail). |
<group>MacroStrength |
number[2] [near, far] |
Macro overlay intensity, 0..1. |
<group>DetailStrength |
number[2] [near, far] |
Detail overlay intensity, 0..1. |
For example, the baseColor group expands to baseColorBaseTex, baseColorMacroTex, baseColorDetailTex, baseColorBaseTexSize, baseColorMacroTexSize, baseColorDetailTexSize, baseColorMacroStrength, and baseColorDetailStrength.
| Field | Type | Notes |
|---|---|---|
internalName |
string | Unique name referenced by the .ter material list. |
macroDistances |
number[4] [startFadeIn, near, far, endFadeOut] |
Distance fade curve (m) shared by all macro textures. Sampling stops at the 1000 m cap. Typical [0, 10, 100, 3000]. |
detailDistances |
number[4] [startFadeIn, near, far, endFadeOut] |
Distance fade curve (m) shared by all detail textures. Sampling stops at the 250 m cap. Typical [0, 0, 30, 60]. |
macroDistAtten |
number[2] [near, far] |
How much macro fades at the fade edges, 0..1. Typical [1, 1]. |
detailDistAtten |
number[2] [near, far] |
How much detail fades at the fade edges, 0..1. Typical [1, 1]. |
groundmodelName |
string | Physics groundmodel (friction, particles, sounds). |
annotation |
string | Semantic/debug class for the annotation pass. Default NATURE. |
useSideProjection |
bool | Classic v1 only - ignored by the v1.5 renderer. |
parallaxScale |
number | Classic v1 only - ignored by the v1.5 renderer. |
TerrainMaterial definitions are stored as a name-keyed JSON object, one key per material:
{
"grass01": {
"class": "TerrainMaterial",
"internalName": "grass01",
"annotation": "GRASS",
"groundmodelName": "GRASS",
"baseColorBaseTex": "/levels/example/art/terrains/t_terrain_base_grass_b.png",
"baseColorBaseTexSize": 512,
"baseColorMacroTex": "/levels/example/art/terrains/t_macro_grass_b.png",
"baseColorMacroTexSize": 64,
"baseColorMacroStrength": [0.1, 0.2],
"baseColorDetailTex": "/levels/example/art/terrains/t_grass_b.png",
"baseColorDetailTexSize": 4,
"baseColorDetailStrength": [0.25, 0],
"normalBaseTex": "/levels/example/art/terrains/t_terrain_base_grass_nm.png",
"normalBaseTexSize": 512,
"normalMacroTex": "/levels/example/art/terrains/t_macro_grass_nm.png",
"normalMacroTexSize": 64,
"normalMacroStrength": [0.2, 0.4],
"normalDetailTex": "/levels/example/art/terrains/t_grass_nm.png",
"normalDetailTexSize": 4,
"normalDetailStrength": [0.7, 0.15],
"roughnessBaseTex": "/levels/example/art/terrains/t_terrain_base_grass_r.png",
"roughnessBaseTexSize": 512,
"roughnessMacroTex": "/levels/example/art/terrains/t_macro_grass_r.png",
"roughnessMacroTexSize": 64,
"roughnessMacroStrength": [0.15, 0.8],
"roughnessDetailTex": "/levels/example/art/terrains/t_grass_r.png",
"roughnessDetailTexSize": 4,
"roughnessDetailStrength": [0.3, 0.3],
"aoBaseTex": "/levels/example/art/terrains/t_terrain_base_grass_ao.png",
"aoBaseTexSize": 512,
"aoMacroTex": "/levels/example/art/terrains/t_macro_grass_ao.png",
"aoMacroTexSize": 64,
"aoDetailTex": "/levels/example/art/terrains/t_grass_ao.png",
"aoDetailTexSize": 4,
"heightBaseTex": "/levels/example/art/terrains/t_terrain_base_grass_h.png",
"heightBaseTexSize": 512,
"heightMacroTex": "/levels/example/art/terrains/t_macro_grass_h.png",
"heightMacroTexSize": 64,
"heightDetailTex": "/levels/example/art/terrains/t_grass_h.png",
"heightDetailTexSize": 4,
"macroDistances": [0, 10, 100, 3000],
"detailDistances": [0, 0, 30, 60],
"macroDistAtten": [1, 1],
"detailDistAtten": [1, 1]
}
}
persistentId and uses a <name>-<persistentId> JSON key; both are omitted here for clarity.height strengths are left at their defaults so height only drives layer blending.TerrainMaterialTextureSet defines the expected texture array sizes used by the v1.5 terrain material renderer.
All terrain materials painted on a terrain are packed into a small set of shared GPU texture arrays (one slice per material), and a texture array requires every slice to have the same dimensions. The TerrainMaterialTextureSet declares those dimensions for the base, macro, and detail slots. This is why every texture you assign to a given slot must be exactly the declared size - a mismatch cannot be packed and the material falls back to the warning texture.
There is normally one TerrainMaterialTextureSet per level (named like <levelName>TerrainMaterialTextureSet), shared by all terrain materials in that level.
A TerrainBlock references it using:
"materialTextureSet": "myTerrainTextureSet"
Important fields (pixel size as [width, height]):
| Field | Description | Typical |
|---|---|---|
baseTexSize |
Pixel size of the base texture array. | [512, 512] – [4096, 4096] (higher when the base is a unique whole-terrain map) |
macroTexSize |
Pixel size of the macro texture array. | [1024, 1024] |
detailTexSize |
Pixel size of the detail texture array. | [1024, 1024] |
Example (from an official level layout):
{
"templateTerrainMaterialTextureSet": {
"class": "TerrainMaterialTextureSet",
"name": "templateTerrainMaterialTextureSet",
"baseTexSize": [512, 512],
"macroTexSize": [1024, 1024],
"detailTexSize": [1024, 1024]
}
}
The renderer uses these sizes when packing terrain material textures into GPU texture arrays.
TerrainMaterialTextureSet. For example, all base textures must match baseTexSize.In the v1.5 terrain material path, source textures are packed into generated cached textures and copied into terrain texture arrays.
Generated cache files can be stored under paths like:
/temp/art/terrainMaterialCache/<hash>.dds
The hash is based on the source texture paths, so the same texture combination can be reused.
The renderer packs terrain data into two main texture groups:
This group combines:
Typical channel usage:
R/G/B = base color
A = ambient occlusion
This group combines:
Typical channel usage:
R = roughness
G = height
B/A = normal-related data
If a source texture is missing, has the wrong size, or does not contain the expected channel, terrain material packing can fail or produce warning material output.
When authoring v1.5 terrain textures:
TerrainMaterialTextureSet slot size (baseTexSize, macroTexSize, or detailTexSize).Recommended source formats per slot:
| Slot group | Color space | Recommended source format |
|---|---|---|
| Base color | sRGB | R8G8B8 / R8G8B8A8 |
| Normal | Linear | R8G8B8 / R8G8B8A8 |
| Roughness / AO / Height | Linear | R8 (grayscale) |
The World Editor terrain material library validates these rules and reports size mismatches, missing textures, and unexpected formats.
The terrain renderer divides terrain into cells and builds a quadtree.
Important value:
minimum terrain cell size = 64
The cell system is used for:
Terrain cell data is uploaded to GPU buffers for rendering.
Terrain LOD is based on screen error and cell distance.
Relevant fields/preferences:
| Field | Description |
|---|---|
screenError |
Terrain screen error setting. |
$pref::Terrain::lodScale |
Global terrain LOD scale. |
$pref::Terrain::detailScale |
Global terrain detail distance scale. |
Terrain cells generate extra skirt geometry around cell edges.
Skirts help hide cracks between terrain cells when different LOD levels are used.
This geometry is generated automatically and is not stored in the .ter file.
The layer index value 255 (U8_MAX) marks empty terrain.
Empty terrain affects:
The renderer can skip empty terrain squares by generating a custom primitive buffer for affected cells.
Terrain collision is built from the heightmap.
When terrain height changes:
Terrain collision is used by:
Terrain can be imported from a heightmap and opacity/layer maps.
Requirements:
128 and 8192Supported input heightmap behavior:
R16 heightmaps are read directly as 16-bit height data.u16 range.8-bit conversion:
storedHeight = pixelValue / 255 x 65535
Import uses opacity layers to build the terrain layer map.
Each opacity layer can come from a texture channel:
R
G
B
A
For each terrain sample, the material layer with the highest opacity value wins.
Pseudocode:
for each sample:
bestLayer = 0
bestValue = 0
for each opacity layer:
if opacityValue >= bestValue:
bestLayer = layer
bestValue = opacityValue
layerMap[sample] = bestLayer
An optional hole map can mark terrain samples as empty.
If the hole map value is 0xFF, the layer map value becomes:
255
which means empty terrain.
Import supports a flipYAxis option.
If disabled, the importer flips input data vertically while copying it into terrain memory.
Use this if the imported terrain appears vertically flipped.
When creating a new terrain, the engine:
.ter filename.TerrainFile..ter..terrain.json metadata file.TerrainBlock.New terrain height is initialized above zero so the editor has room to excavate.
{
"class": "TerrainBlock",
"name": "theTerrain",
"terrainFile": "/levels/example/theTerrain.ter",
"materialTextureSet": "exampleTerrainMaterialTextureSet",
"squareSize": 1,
"maxHeight": 2048,
"screenError": 16,
"castShadows": true,
"position": [-1024, -1024, 0],
"rotationMatrix": [1, 0, 0, 0, 1, 0, 0, 0, 1]
}
{
"version": 9,
"datafile": "/levels/example/theTerrain.ter",
"heightmapImage": "/levels/example/theTerrain.terrainheightmap.png",
"size": 1024,
"binaryFormat": "version(char), size(unsigned int), heightMap(heightMapSize * heightMapItemSize), layerMap(layerMapSize * layerMapItemSize), layerTextureMap(layerMapSize * layerMapItemSize), materialNames",
"heightMapSize": 1048576,
"heightMapItemSize": 2,
"layerMapSize": 1048576,
"layerMapItemSize": 1,
"materials": [
"grass",
"rock"
]
}
128 and 8192.squareSize to control world scale.maxHeight to control vertical range.TerrainBlock object.TerrainMaterial internal names.255 layer values only for intended holes.Possible causes:
terrainFile path.ter filePossible causes:
.ter cannot be resolvedTerrainMaterial definition is missingmaterialTextureSet cannot be foundCheck:
maxHeightsquareSizePossible causes:
Layer map values may be 255, which marks terrain as empty.
Rebuild terrain collision or save/reload after major height edits.
Check that:
128 and 8192BeamNG terrain uses:
.ter file for heightmap, layer map, and material names.terrain.json metadata file for documentation/toolingTerrainBlock scene object to place the terrain in the levelTerrainMaterial definitions for visual and physical material dataThe .ter file is compact and fast to load, while the .terrain.json file helps tools understand the terrain data layout.
Was this article helpful?