Skip to main content
Euca’s terrain (euca-terrain) is a chunked heightmap: a grid of height samples you create flat and then sculpt with brushes — raise, lower, flatten, smooth — each applied at a world position with a radius and amount. Foliage scatters instanced meshes across an area in one layer. The terrain data (the heightmap component and its edits) is fully headless and works on the bare server; the mesh — and the foliage — need a render host’s mesh assets. This page builds and sculpts a terrain end to end; every command is real and runnable against a local server, with output captured from one.

Sculpt a heightmap

1. Create a flat terrain. You give it a grid size and cell size; it spawns a terrain entity and reports the resulting world extents. Note the extents are (cells − 1) × cell_size — a 64-cell grid spans 63 world units:
2. Raise a hill. /terrain/edit applies one of four brush ops at (x, z) with a radius and amount. The edit lands immediately on the heightmap of the first terrain entity — no /step:
3. Use the other three brushes. lower digs, flatten pulls toward a target level, smooth averages neighbours — all the same request shape:

Scatter foliage

/foliage/scatter places an instanced layer of a named mesh across an area. The scatter is deterministic (fixed seed) — but it needs the host’s DefaultAssets to resolve the mesh and material. The minimal server doesn’t register them, so on it the call honestly reports the missing resource and the layer list stays empty:
On a host with DefaultAssets registered, the same scatter returns {"ok":true,"instance_count":N,"mesh":"cube","density":0.5} and /foliage/list reports each layer’s index, instance count, density, and max render distance.

Behavior and gotchas

The things you only find out by running it:
  • /terrain/create stores a heightmap component, not a rendered mesh. It builds a CPU Heightmap::flat and spawns it; building the displayable mesh (CPU or GPU) is the render host’s job. Creating terrain bumps entity_count on GET /; reading the rendered surface needs a host.
  • Edits are immediate — no /step. Unlike combat events, a brush op mutates the heightmap on the call. Read-back is via the host (the heightmap isn’t exposed as JSON), but the op itself succeeds synchronously.
  • /terrain/edit acts on the first terrain entity. It queries for any TerrainComponent and edits the first one found. With no terrain, or an unknown op, it returns {"ok":false,"message":"No terrain entity found or invalid operation"}.
  • World extents are (cells − 1) × cell_size. A width:64, cell_size:1.0 terrain has world_width:63.0, not 64 — the grid has 64 vertices spanning 63 cells.
  • Brush coordinates are world-space (x, z), not grid indices; radius and amount shape the falloff and strength. The four ops dispatch identically — only the math on the heightmap differs.
  • Foliage scatter needs host mesh assets. Without DefaultAssets it can’t resolve mesh_name ({"ok":false,"error":"DefaultAssets resource not found"}); an unknown mesh name on a host returns {"ok":false,"error":"Unknown mesh name"}.

Endpoints

Status

  • Shipped & headless — the CPU heightmap, the four brush ops, the terrain entity, and deterministic foliage scatter logic. Terrain create and edit work fully on the bare server.
  • Host-dependent — the terrain mesh and foliage placement need render-side assets: /foliage/scatter requires a registered DefaultAssets; displaying terrain or foliage needs a render host.
  • GPU terrain generation — a real compute-shader mesh generator (GpuTerrainGenerator) ships behind the gpu-terrain feature flag and needs a live RenderDevice. It is not the path the HTTP endpoint uses; the endpoint builds the heightmap on the CPU.

Terrain & foliage endpoints

Create, edit, scatter, and list endpoints, with request and response schemas.