feat(render3d): replace the world at run time, water bodies, terrain_sea
terrain_reload/terrain_unload release one map's height field, survey, photograph and patch bounds and generate another at any TERRAIN_HALF; scatter_clear_all (with streams), actor_clear_all, col_reset and mesh_free empty the scene; water_body_add/water_bodies_clear draw several still-water planes with one reflecting; terrain_sea separates the coast's sea level from the carved lake's, and grass keeps off both. Fixes CDLOD patch bounds for TERRAIN_HALF != 4096 and water_init leaking a mesh and program per call. examples/rendering/reload.ludic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
70bda0c4df
commit
a4a3d75cd9
13 changed files with 308 additions and 19 deletions
82
examples/rendering/reload.ludic
Normal file
82
examples/rendering/reload.ludic
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# reload.ludic — the world replaced at run time.
|
||||
#
|
||||
# Generate a 2 km map, scatter onto it, give it colliders and water; release all of it and
|
||||
# generate a 3 km map in its place with two water planes; then go back. Prints RELOAD OK when
|
||||
# nothing of the previous map survived and the new one is whole.
|
||||
#
|
||||
# bin/ludic build examples/rendering/reload.ludic --headless && ./build/reload_headless
|
||||
program Reload {
|
||||
import "ludic.render3d/r3d.ludic"
|
||||
|
||||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
# A program only links the GL runtime render3d stands on when it names Gl.* itself
|
||||
# (the parser's g_uses_gl), so even a check that draws nothing presents its frame.
|
||||
# render3d calls back into the program for what stands on the terrain
|
||||
function scene_draw() -> void { scatter_draw() }
|
||||
function scene_draw_casters() -> void { scatter_draw_casters(shadow_cascade_vp(sh_cascade)) }
|
||||
# no streamed cover here, but stream.ludic names the generator, so every program supplies one
|
||||
function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void { }
|
||||
|
||||
handler Present phase Render {
|
||||
Gl.swap()
|
||||
}
|
||||
|
||||
function check(what: string, cond: bool) -> bool {
|
||||
if not cond { print(`reload: FAILED - {what}`) }
|
||||
return cond
|
||||
}
|
||||
function populate() -> void {
|
||||
let l = layer_cards(model_cross_card(), 1000, F_ZERO, fi(400))
|
||||
for i in 0 .. 400 {
|
||||
let x = fi(i * 2 - 400)
|
||||
layer_add(l, x, terrain_height(x, fi(30)), fi(30), F_ONE, F_ZERO, F_ZERO, F_ZERO)
|
||||
col_add(x, fi(30), F_ONE)
|
||||
}
|
||||
col_build()
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
spawn Anchor {}
|
||||
TERRAIN_HALF = 1000
|
||||
ter_smooth = true
|
||||
if not r3d_init(640, 360, "Reload") { quit() }
|
||||
cam_set(F_ZERO, fi(80), fi(300), fi(180), f_neg(fi(10)))
|
||||
water_init(fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
|
||||
populate()
|
||||
var ok = true
|
||||
ok = check("the first map has layers and colliders", len(sc_layers) == 1 and col_n == 400) and ok
|
||||
# --- swap to a 3 km map with a sea and a lake above it
|
||||
scatter_clear_all()
|
||||
actor_clear_all()
|
||||
col_reset()
|
||||
water_bodies_clear()
|
||||
terrain_lake(F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO)
|
||||
terrain_reload("", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1500)
|
||||
ok = check("everything of the first map released", len(sc_layers) == 0 and col_n == 0 and wb_n == 0 and not water_on) and ok
|
||||
ok = check("the new half", TERRAIN_HALF == 1500 and ter_heights != null and ter_height_tex != 0) and ok
|
||||
ok = check("patch bounds rebuilt", cd_min != null and len(cd_min) == CD_LEVELS) and ok
|
||||
ok = check("patches scale with the map", cd_size(0) == f_mul(fi(32), fr(3000, 8192))) and ok
|
||||
terrain_height(F_ZERO, F_ZERO)
|
||||
ok = check("the cached height scale is the new map's", ter_h_scale == fr(TERRAIN_RES, 3000)) and ok
|
||||
let sea = water_body_add(F_ZERO, F_ZERO, F_ZERO, fi(20000), fi(20000), true)
|
||||
let lake = water_body_add(fl(12.0), fi(600), fi(-400), fi(150), fi(100), false)
|
||||
ok = check("two water bodies, the sea mirrored", sea == 0 and lake == 1 and wb_n == 2 and wb_primary == 0 and water_level == F_ZERO and water_on) and ok
|
||||
populate()
|
||||
ok = check("the new map takes layers and colliders", len(sc_layers) == 1 and col_n == 400 and col_side == 3000 / COL_CELL) and ok
|
||||
# a sea below the carved lake: set once, and not undone by a reload
|
||||
terrain_sea(fl(-2.0))
|
||||
terrain_lake(fl(12.0), fi(600), fi(-400), fi(150), fi(100))
|
||||
ok = check("the sea is its own level", ter_sea_set and ter_sea_gen() == fl(-2.0) and ter_lake_level == fl(12.0)) and ok
|
||||
# --- and back: the swap is repeatable
|
||||
scatter_clear_all()
|
||||
col_reset()
|
||||
water_init(fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200))
|
||||
terrain_reload("", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1000)
|
||||
populate()
|
||||
ok = check("back to 2 km", TERRAIN_HALF == 1000 and len(cd_min) == CD_LEVELS and wb_n == 1 and col_side == 2000 / COL_CELL) and ok
|
||||
if ok { print("RELOAD OK") } else { print("RELOAD FAILED") }
|
||||
quit()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue