terrain
stableA heightmap-based terrain generation and manipulation plugin supporting procedural generation (diamond-square), hydraulic erosion, noise, normals, and grayscale export.
use plugin terrain::{create_heightmap, get_height, set_height, …} Functions (16)
- create_heightmap Create an empty heightmap of given size
- get_height Read height at integer coordinates
- set_height Write height at integer coordinates
- normalize Scale all heights to the [0, 1] range
- smooth Average-blur the heightmap N times
- diamond_square Generate terrain via diamond-square algorithm
- normal_at Surface normal vector at a point
- slope_at Slope angle in radians at a point
- sample_bilinear Interpolated height at fractional coordinates
- erosion_hydraulic Simulate particle-based hydraulic erosion
- flatten_area Blend a circular area toward a target height
- add_noise Add value noise with amplitude and frequency
- min_max Get minimum and maximum height values
- scale Multiply all heights by a factor
- clamp Clamp all heights to a min/max range
- to_grayscale_bytes Export heightmap as grayscale byte array
Overview
terrain is a heightmap toolkit for procedurally generating and reshaping 2D
elevation grids. The core value is a plain heightmap table with width,
height, and a flat data array of row-major float heights — there are no
opaque handles, so a heightmap is just a table you can pass around, inspect, and
serialize. Every transform takes a heightmap and returns a new heightmap, which
makes operations easy to chain. Use it whenever you need procedural landscapes
(diamond-square), realistic weathering (hydraulic erosion), detail (value
noise), or analysis and export (normals, slopes, grayscale bytes).
The mental model is a pipeline: start from create_heightmap or
diamond_square, layer detail with add_noise, weather it with
erosion_hydraulic and smooth, normalize or clamp the range, then sample it
with get_height / sample_bilinear or hand it off with to_grayscale_bytes.
Common patterns
Generate a landscape, weather it, and normalize the result:
use plugin terrain::{terrain}
let raw = terrain.diamond_square(129, 0.9, 42)
let eroded = terrain.erosion_hydraulic(raw, 4, 800, 7)
let map = terrain.normalize(terrain.smooth(eroded, 1))
print("ready: {map["width"]}x{map["height"]}")
Build flat ground, add layered noise, then carve out a build site:
use plugin terrain::{terrain}
let base = terrain.create_heightmap(128, 128)
let hills = terrain.add_noise(base, 1.0, 0.04, 1)
let detail = terrain.add_noise(hills, 0.3, 0.12, 2)
let plot = terrain.flatten_area(terrain.normalize(detail), 64, 64, 12.0, 0.5)
Sample interpolated heights and surface normals for lighting:
use plugin terrain::{terrain}
let map = terrain.normalize(terrain.diamond_square(65, 0.8, 99))
let h = terrain.sample_bilinear(map, 32.5, 32.5)
let n = terrain.normal_at(map, 32, 32)
print("height {h}, normal.z {n["z"]}")
Create an empty heightmap of given size
Creates a new heightmap table with all values set to 0. The table has fields width, height, and data. This is the starting point for all terrain operations.
use plugin terrain::{terrain}
let map = terrain.create_heightmap(256, 256)
print("created {map["width"]}x{map["height"]} map")
Read height at integer coordinates
Returns the height value at integer grid position (x, y). Returns 0.0 for out-of-bounds coordinates.
use plugin terrain::{terrain}
let map = terrain.create_heightmap(64, 64)
let h = terrain.get_height(map, 10, 20)
print("height at (10,20): {h}")
Write height at integer coordinates
Sets the height at grid position (x, y) and returns the updated heightmap. Out-of-bounds writes are silently ignored.
use plugin terrain::{terrain}
let map = terrain.create_heightmap(64, 64)
let map2 = terrain.set_height(map, 32, 32, 1.0)
Scale all heights to the [0, 1] range
Rescales all height values to [0, 1] based on the current minimum and maximum. Returns a new heightmap.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.7, 42)
let normalized = terrain.normalize(map)
Average-blur the heightmap N times
Applies a 3x3 averaging blur for iterations passes. Each pass smooths sharp features into gentler slopes.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 1.5, 99)
let smooth = terrain.smooth(map, 3)
Generate terrain via diamond-square algorithm
Generates a procedural heightmap using the diamond-square (midpoint displacement) algorithm. size is rounded up to the nearest 2^n+1. Higher roughness produces more jagged terrain.
use plugin terrain::{terrain}
let map = terrain.diamond_square(129, 0.8, 12345)
let map2 = terrain.normalize(map)
print("{map2["width"]}x{map2["height"]} terrain generated")
use plugin terrain::{terrain}
let rough = terrain.diamond_square(65, 1.5, 1)
let smooth_terrain = terrain.smooth(terrain.normalize(rough), 2)
Surface normal vector at a point
Returns the surface normal vector at (x, y) as a table with fields x, y, z. The vector is normalized. Useful for lighting calculations.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.7, 42)
let n = terrain.normal_at(map, 32, 32)
print("normal: ({n["x"]}, {n["y"]}, {n["z"]})")
Slope angle in radians at a point
Returns the slope angle in radians at (x, y), computed from central differences. Flat areas return 0, steep cliffs return values near π/2.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.8, 7)
let angle = terrain.slope_at(map, 20, 20)
print("slope: {angle} radians")
Interpolated height at fractional coordinates
Returns a smoothly interpolated height at fractional coordinates (fx, fy) using bilinear interpolation. Useful when sampling terrain at sub-pixel precision.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.7, 1)
let h = terrain.sample_bilinear(map, 10.5, 20.75)
print("interpolated height: {h}")
Simulate particle-based hydraulic erosion
Simulates hydraulic erosion by dropping water particles that erode high points and deposit sediment in valleys. More iterations and drops produce more realistic results.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.9, 42)
let eroded = terrain.erosion_hydraulic(map, 3, 500, 99)
let result = terrain.normalize(eroded)
A light single pass keeps mountains intact while still cutting gullies; smoothing afterward softens the eroded channels:
use plugin terrain::{terrain}
let map = terrain.diamond_square(129, 1.2, 5)
let carved = terrain.erosion_hydraulic(map, 1, 200, 1)
let final = terrain.smooth(carved, 1)
Blend a circular area toward a target height
Smoothly blends a circular area centered at (cx, cy) toward target_height. The blend uses a linear falloff so edges stay natural. Useful for placing buildings or roads.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.7, 5)
let flat = terrain.flatten_area(map, 32, 32, 10.0, 0.5)
Add value noise with amplitude and frequency
Adds value noise to the heightmap. amplitude controls the height of noise features, frequency controls their density (higher = more detail).
use plugin terrain::{terrain}
let map = terrain.create_heightmap(128, 128)
let noisy = terrain.add_noise(map, 0.5, 0.05, 777)
let noisy2 = terrain.add_noise(noisy, 0.25, 0.1, 888)
let result = terrain.normalize(noisy2)
Get minimum and maximum height values
Returns {min, max} — the minimum and maximum height values in the entire heightmap. Useful before normalizing or clamping.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.8, 1)
let bounds = terrain.min_max(map)
print("range: {bounds["min"]} to {bounds["max"]}")
Multiply all heights by a factor
Multiplies all height values by factor. Use to change vertical scale before compositing multiple heightmaps.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.7, 1)
let tall = terrain.scale(map, 2.0)
Clamp all heights to a min/max range
Clamps all height values to the [min, max] range. Values below min become min and values above max become max.
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 1.2, 42)
let clamped = terrain.clamp(map, -1.0, 1.0)
Clamp the floor to zero to create flat sea level, then normalize so the remaining land fills the full range:
use plugin terrain::{terrain}
let map = terrain.diamond_square(65, 0.8, 3)
let coastline = terrain.normalize(terrain.clamp(map, 0.0, 1.0))
Export heightmap as grayscale byte array
Exports the heightmap as a flat byte buffer where each byte (0–255) represents one pixel's brightness. The heights are normalized automatically. The byte order is row-major (left to right, top to bottom).
use plugin terrain::{terrain}
let map = terrain.normalize(terrain.diamond_square(65, 0.7, 42))
let pixels = terrain.to_grayscale_bytes(map)
print("exported {map["width"] * map["height"]} bytes")
Because export normalizes internally, you can dump a raw generated map directly — the brightest pixel is the highest peak regardless of the source range:
use plugin terrain::{terrain}
let map = terrain.diamond_square(129, 1.0, 2026)
let pixels = terrain.to_grayscale_bytes(map)
print("first pixel: {pixels[0]}")