Merge branch 'r3d/chunk-heights' into lang/chunks
This commit is contained in:
commit
96423634ad
5 changed files with 132 additions and 0 deletions
10
changes/render3d-chunk-heights.md
Normal file
10
changes/render3d-chunk-heights.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
bump: minor
|
||||
type: feature
|
||||
**The ground's heights a chunk at a time: `terrain_chunk_heights(i, j, size_m, n, out)`.** For
|
||||
whatever is built per chunk (plan 23.5 of maroon-lake: the physics ground by 64 m chunks): n x n
|
||||
samples of chunk (i, j) of a grid laid from the terrain's corner, row-major into the caller's
|
||||
buffer, nothing allocated. Each is the ground's cubic B-spline at the texel under it - the
|
||||
separable (1 4 1) / 6 filter `ter_spline_at`, the same numbers ludic.physics' B-spline
|
||||
heightfield makes of the whole map - so neighbouring chunks share their edges bit for bit and
|
||||
agree with the drawn ground. The read-back lives from `terrain_generate` to `terrain_unload`.
|
||||
`examples/rendering/chunks.ludic` holds it.
|
||||
80
examples/rendering/chunks.ludic
Normal file
80
examples/rendering/chunks.ludic
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# chunks.ludic - the height field a chunk at a time (terrain_chunk_heights): 64 m chunks of 33 x 33
|
||||
# B-spline heights, each the (1 4 1) / 6 filter of the texels under it, their edges shared with their
|
||||
# neighbours bit for bit. Over a known surface written into the read-back. Prints CHUNKS OK.
|
||||
#
|
||||
# bin/ludic build examples/rendering/chunks.ludic --headless && ./build/chunks_headless
|
||||
program Chunks {
|
||||
numbers float
|
||||
import "ludic.render3d/r3d.ludic"
|
||||
|
||||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
function scene_draw(render3d_st: mut Render3dState) -> void { }
|
||||
function scene_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { }
|
||||
function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void { }
|
||||
|
||||
function check(what: string, cond: bool) -> bool {
|
||||
if not cond { print(`chunks: FAILED - {what}`) }
|
||||
return cond
|
||||
}
|
||||
|
||||
handler Boot(render3d_st: mut Render3dState) phase Start {
|
||||
spawn Anchor {}
|
||||
r3d_on_draw(render3d_st, fn scene_draw)
|
||||
r3d_on_casters(render3d_st, fn scene_draw_casters)
|
||||
r3d_on_stream_fill(render3d_st, fn stream_fill)
|
||||
render3d_st.TERRAIN_HALF = 1000
|
||||
render3d_st.ter_smooth = true
|
||||
render3d_st.r3d_sky_path = "packages/ludic.lab/plate/sky.hdr"
|
||||
if not r3d_init(render3d_st, 320, 180, "Chunks") {
|
||||
quit()
|
||||
return
|
||||
}
|
||||
# a surface of its own in the read-back: what is tested is the chunking and the filter, not
|
||||
# the ground's generation
|
||||
for z in 0 .. TERRAIN_RES {
|
||||
for x in 0 .. TERRAIN_RES {
|
||||
render3d_st.ter_heights[z * TERRAIN_RES + x] = Math.sin(float(x) * 0.013) * 40.0 + Math.cos(float(z) * 0.021) * 25.0 + float((x * 7 + z * 13) % 11) * 0.1
|
||||
}
|
||||
}
|
||||
var ok = true
|
||||
let a = floats(33 * 33)
|
||||
let b = floats(33 * 33)
|
||||
let c = floats(33 * 33)
|
||||
ok = check("a chunk reads", terrain_chunk_heights(render3d_st, 10, 12, 64.0, 33, a)) and ok
|
||||
ok = check("its east neighbour reads", terrain_chunk_heights(render3d_st, 11, 12, 64.0, 33, b)) and ok
|
||||
ok = check("its north neighbour reads", terrain_chunk_heights(render3d_st, 10, 13, 64.0, 33, c)) and ok
|
||||
var east = true
|
||||
var north = true
|
||||
for k in 0 .. 33 {
|
||||
if a[k * 33 + 32] != b[k * 33] { east = false }
|
||||
if a[32 * 33 + k] != c[k] { north = false }
|
||||
}
|
||||
ok = check("the east edge is shared bit for bit", east) and ok
|
||||
ok = check("the north edge is shared bit for bit", north) and ok
|
||||
# sample (5, 7) of chunk (10, 12): the filter of the texels round the one under it, by hand
|
||||
let texel = 2.0 * float(render3d_st.TERRAIN_HALF) / float(TERRAIN_RES)
|
||||
let tx = int((10.0 * 64.0 + 5.0 * 2.0) / texel + 0.5)
|
||||
let tz = int((12.0 * 64.0 + 7.0 * 2.0) / texel + 0.5)
|
||||
let d = floats(33 * 33)
|
||||
terrain_chunk_heights(render3d_st, 10, 12, 64.0, 33, d)
|
||||
var want = 0.0
|
||||
for dz in -1 .. 2 {
|
||||
let r = (tz + dz) * TERRAIN_RES
|
||||
let row = (render3d_st.ter_heights[r + tx - 1] + 4.0 * render3d_st.ter_heights[r + tx] + render3d_st.ter_heights[r + tx + 1]) / 6.0
|
||||
if dz == 0 { want = want + 4.0 * row } else { want = want + row }
|
||||
}
|
||||
ok = check("a sample is the B-spline at its texel", d[7 * 33 + 5] == want / 6.0) and ok
|
||||
var flat = true
|
||||
for k in 1 .. 33 * 33 { if a[k] != a[0] { flat = false } }
|
||||
ok = check("the ground has relief in it (the test reads something)", not flat) and ok
|
||||
ok = check("too small a buffer is refused, not overrun", not terrain_chunk_heights(render3d_st, 0, 0, 64.0, 34, a)) and ok
|
||||
if ok { print("CHUNKS OK") } else { print("CHUNKS FAILED") }
|
||||
quit()
|
||||
}
|
||||
|
||||
handler Present(render3d_st: mut Render3dState) phase Render {
|
||||
r3d_present(render3d_st)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import "camera.ludic"
|
|||
import "sky.ludic"
|
||||
import "daylight.ludic"
|
||||
import "terrain.ludic"
|
||||
import "terrain_chunks.ludic"
|
||||
import "overlay.ludic"
|
||||
import "shadow.ludic"
|
||||
import "post.ludic"
|
||||
|
|
|
|||
40
packages/ludic.render3d/terrain_chunks.ludic
Normal file
40
packages/ludic.render3d/terrain_chunks.ludic
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# 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: 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 r = z * TERRAIN_RES
|
||||
let row = (render3d_st.ter_heights[r + max(tx - 1, 0)] + 4.0 * render3d_st.ter_heights[r + tx] + render3d_st.ter_heights[r + min(tx + 1, TERRAIN_RES - 1)]) / 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: Render3dState, i: int, j: int, size_m: float, n: int, out: floats) -> bool {
|
||||
if render3d_st.ter_heights == null 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
|
||||
}
|
||||
|
|
@ -1054,6 +1054,7 @@ function cmd_dev_test() -> int {
|
|||
headless_case("rendering/ui_render3d", "ok", "ui_render3d.ludic (ludic.ui's render3d backend builds against the renderer)")
|
||||
headless_line_case("rendering/release", "RELEASE OK", "release.ludic (model_release: meshes go, a shared texture stays until its last user goes)")
|
||||
headless_line_case("rendering/bc", "BC OK", "bc.ludic (a .dds beside a .png arrives BC7-compressed with every mip level)")
|
||||
headless_line_case("rendering/chunks", "CHUNKS OK", "chunks.ludic (the ground's B-spline heights a 64 m chunk at a time, edges shared bit for bit)")
|
||||
migrate_component_case()
|
||||
migrate_foreign_case()
|
||||
reject_case("rejected/runtime_type_clash", "PadButton is the runtime's enum", "a program's type named like one of the runtime's is refused")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue