Expand description
One-shot heightmap → quantized-mesh (.terrain) encoding.
This module ties together the three crates that otherwise have to be wired up by hand:
martinigenerates an adaptive RTIN mesh from the elevation grid.- The mesh’s
(u, v, height)are quantised to the 0..=32767 range. quantized_meshencodes the header, vertices, edge indices and optional extensions into the quantized-mesh-1.0 byte stream.
The fiddly bits it handles for you:
- Re-sampling each mesh vertex’s height (martini discards heights once
the error pyramid is built, so the transform has to recover the grid
coordinate from
(u, v)and read the DEM again). - Computing the encoded height range from the mesh vertices (what is actually stored), not the full grid.
- Streaming the mesh vertices through
QuantizedMeshHeader::from_bounds_with_vertices_iterfor a tight horizon-occlusion point. - Vertex normals via the
NormalModeof your choice.
§Grid orientation
elevations (and the get_height closure’s y) are row-major,
north → south: row 0 is the northern edge, row grid_size - 1 the
southern edge. This matches crate::normals::BufferedElevations, so a
buffered grid can be reused directly for NormalMode::BufferedGradient.
§Seamless tiling — the caller supplies the halo
These functions encode one tile in isolation; they never fetch neighbouring tiles. For gap-free, seam-free output the caller must widen the input to overlap the neighbours — fetch the halo cells along with the tile and stitch them in before calling:
- Geometry seam. martini needs a
2^n + 1grid, so anN-post DEM tile needs one extra post on its east and south edges. That+1post is the neighbour tile’s first post for the shared edge — read it from the neighbour, don’t edge-replicate, or adjacent tiles won’t agree on the boundary and the globe cracks along tile seams. - Normal seam.
NormalMode::BufferedGradientneeds abuffer-cell halo of neighbour samples on every side (seeBufferedElevations). Edge vertices read their±1neighbours out of that halo, so the same physical edge gets identical normals from either tile and lighting stays continuous.
Gathering that neighbour data (over HTTP, from disk, from a cache, …) is deliberately left to the caller — hence this module takes an already-assembled grid rather than fetching tiles itself, which also keeps it free of any async/runtime assumptions.
§Example
use terrain_codec::quantized_mesh::TileBounds;
use terrain_codec::terrain::{encode_terrain, TerrainOptions};
let grid_size = 65; // 2^6 + 1
let elevations = vec![0.0f32; (grid_size * grid_size) as usize];
let bounds = TileBounds::new(139.0, 35.0, 139.01, 35.01);
let terrain: Vec<u8> = encode_terrain(
&elevations,
grid_size,
&bounds,
&TerrainOptions {
max_error: 1.0,
..Default::default()
},
);
assert!(terrain.starts_with(&[0x1f, 0x8b])); // gzip magic (default level 6)Structs§
- Terrain
Options - Options controlling
encode_terrainand the other encode functions in this module.
Enums§
- Normal
Mode - How (and whether) per-vertex normals are computed for the oct-encoded vertex-normals extension.
Functions§
- encode_
terrain - Encode a flat row-major (north → south)
f32elevation grid to a quantized-mesh.terrainbyte vector. - encode_
terrain_ from_ fn - Encode a heightmap to a quantized-mesh
.terrainbyte vector, sampling elevations through a closure. - encode_
terrain_ from_ fn_ to - Like
encode_terrain_from_fn, but streams the encoded bytes to a writer instead of allocating aVec. - encode_
terrain_ to - Like
encode_terrain, but streams the encoded bytes to a writer.