ludic/packages/ludic.render3d/terrain_chunks.ludic
Orkuncakilkaya 5b3cfac48e render3d: the height field and photograph on the CPU as tiles read from a file (off until the game asks)
terrain_tiles_to(dir, key) before a map is made: once made, its heights, photograph and baked normals
are written tile by tile (64 m, TT_TEX) to <dir>/<key>.tiles - fresh every time, header last, so no
other generator's or a torn file is ever read - and the whole copies go. Every read goes through the
tile: resident, else read from the file there and then into a 2048-tile clock, so terrain_height,
terrain_height_smooth, terrain_ortho* and terrain_chunk_heights answer exactly what the whole copy
did (R3D_TT_CHECK: worst 0.0 m over 4000 points) whatever is resident - two machines and the boot's
placements agree to the bit. terrain_chunk_heights takes render3d_st mut for it.

terrain_height_near(read-only): the tile if it is in, else a coarse 2048^2 level (worst 0.91 m), never
the file - for line checks that must not take render3d_st mut. terrain_texel() is the texel size,
terrain_tiles_prefetch(x, z, r, budget) reads ahead, and a frame that reads more than 8 tiles says so
once. R3D_TERRAIN_TILES=<dir> turns it on for a run.

At the overlook with MallocLargeCache=0: 1731 MB off, 1658 MB on; 192 MB written in ~200 ms; the
frame unchanged (0 pixels over 8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:53:28 +03:00

39 lines
2.2 KiB
Text

# terrain_chunks.ludic - the height field a chunk at a time, for whatever is built per chunk (the
# ground's physics, plan 23.5 of maroon-lake): the texels the renderer read back, as they are.
# the ground's height at texel (tx, tz) as the renderer draws it - the cubic B-spline, which at a
# texel's centre is the separable (1 4 1) / 6 filter of its neighbours, clamped at the map's edge:
# the same numbers ludic.physics' jph_shape_heightfield_bspline makes of the whole map
function ter_spline_at(render3d_st: mut Render3dState, tx: int, tz: int) -> float {
var sum = 0.0
for d in -1 .. 2 {
let z = min(max(tz + d, 0), TERRAIN_RES - 1)
let row = (ter_h(render3d_st, max(tx - 1, 0), z) + 4.0 * ter_h(render3d_st, tx, z) + ter_h(render3d_st, min(tx + 1, TERRAIN_RES - 1), z)) / 6.0
if d == 0 { sum = sum + 4.0 * row } else { sum = sum + row }
}
return sum / 6.0
}
# The heights of chunk (i, j) of a size_m grid laid from the terrain's corner (ter_ox - TERRAIN_HALF,
# ter_oz - TERRAIN_HALF): n x n samples, row-major (z rows, x across), sample (a, b) at corner +
# (i * size_m + a * size_m / (n - 1), j * size_m + b * size_m / (n - 1)), each the ground's
# B-spline height at the texel under it (ter_spline_at: what the physics ground is built from).
# Neighbours share their edge row and column, so seams agree to the bit. Into `out` (n * n floats,
# the caller's, reused), nothing allocated. The read-back lives from terrain_generate to
# terrain_unload - the whole of a map - so a chunk can be read any time between; false when there
# is none (a plate, or before the map is made).
function terrain_chunk_heights(render3d_st: mut Render3dState, i: int, j: int, size_m: float, n: int, out: floats) -> bool {
if not ter_present(render3d_st) or n < 2 or len(out) < n * n { return false }
let texel = 2.0 * float(render3d_st.TERRAIN_HALF) / float(TERRAIN_RES)
let step = size_m / float(n - 1)
let x0 = float(i) * size_m
let z0 = float(j) * size_m
for b in 0 .. n {
let tz = min(max(int((z0 + float(b) * step) / texel + 0.5), 0), TERRAIN_RES - 1)
for a in 0 .. n {
let tx = min(max(int((x0 + float(a) * step) / texel + 0.5), 0), TERRAIN_RES - 1)
out[b * n + a] = ter_spline_at(render3d_st, tx, tz)
}
}
return true
}