Merge commit '2ce2d99' into lang/foundations

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 15:38:40 +03:00
commit ef924b9e9e
9 changed files with 88 additions and 11 deletions

View file

@ -175,6 +175,10 @@ function ac_visible(render3d_st: Render3dState, a: Actor, shadow: bool) -> bool
if a.model == null { return false }
if not a.visible and not (shadow and a.cast_hidden) { return false }
if shadow and not a.casts { return false }
# an actor with no radius given is sized generously, since a building or the dock may be one
var fr = a.radius * a.scale
if fr <= 0.0 { fr = 20.0 }
if r3d_beyond_fog(render3d_st, a.x, a.y, a.z, fr) { return false }
if a.cull != 0.0 {
let dx = a.x - render3d_st.cam_pos[0]; let dz = a.z - render3d_st.cam_pos[2]
let d2 = dx * dx + dz * dz

View file

@ -70,6 +70,7 @@ function cam_planes_update(render3d_st: mut Render3dState) -> void {
# is a sphere (float bits) at least partly inside the side planes of the view?
function cam_sphere_visible(render3d_st: Render3dState, x: float, y: float, z: float, r: float) -> bool {
if render3d_st.cam_planes == null { return true }
if r3d_beyond_fog(render3d_st, x, y, z, r) { return false }
let nr = -r
for p in 0 .. 4 {
let o = p * 4
@ -78,6 +79,14 @@ function cam_sphere_visible(render3d_st: Render3dState, x: float, y: float, z: f
}
return true
}
# is a sphere wholly past the fog wall? Nothing there is seen, so nothing there is drawn or animated
export function r3d_beyond_fog(render3d_st: Render3dState, x: float, y: float, z: float, r: float) -> bool {
let w = render3d_st.r3d_fog_wall
if w <= 0.0 { return false }
let dx = x - render3d_st.cam_pos[0]; let dy = y - render3d_st.cam_pos[1]; let dz = z - render3d_st.cam_pos[2]
let far = w + R3D_FOG_MARGIN + r
return dx * dx + dy * dy + dz * dz > far * far
}
# fly: forward/strafe in metres, turn in radians
function cam_move(render3d_st: mut Render3dState, fwd: float, strafe: float, up: float, dyaw: float, dpitch: float) -> void {
render3d_st.cam_yaw = render3d_st.cam_yaw + dyaw

View file

@ -0,0 +1,40 @@
# fog_casters.ludic — under a fog wall, a layer's cards cast shadows only from where they can be seen.
# The impostor shadow list is every instance on the map; past the wall that is a vertex bill for
# nothing, so the casters inside the wall (and a shadow's throw past it) go into a list of their own.
const FOG_CAST_THROW: float = 30.0 # the longest shadow a tree throws back toward the camera
const FOG_CAST_STEP: float = 4.0 # the camera moves this far before the list is rebuilt
# the buffer and count the card shadows draw from: the whole list, or the wall's share of it
function fog_casters(render3d_st: mut Render3dState, l: Layer) -> int {
let w = render3d_st.r3d_fog_wall
if w <= 0.0 { return l.sh_buf }
let dx = render3d_st.cam_pos[0] - l.fog_x
let dz = render3d_st.cam_pos[2] - l.fog_z
if l.fog_wall == w and l.fog_src == l.n_sh and dx * dx + dz * dz < FOG_CAST_STEP * FOG_CAST_STEP { return l.fog_buf }
l.fog_wall = w; l.fog_src = l.n_sh
l.fog_x = render3d_st.cam_pos[0]; l.fog_z = render3d_st.cam_pos[2]
let far = w + R3D_FOG_MARGIN + FOG_CAST_THROW + FOG_CAST_STEP
let far2 = far * far
let src = l.inst
let dst = l.scratch
var n = 0
let total = Math.min(l.n_sh, l.count)
for i in 0 .. total {
let o = i * INST_FLOATS
let ex = src[o] - l.fog_x
let ez = src[o + 2] - l.fog_z
if ex * ex + ez * ez > far2 { continue }
let q = n * INST_FLOATS
for k in 0 .. INST_FLOATS { dst[q + k] = src[o + k] }
n += 1
}
l.fog_n = n
if n > 0 { gpu_buffer_upload(render3d_st, l.fog_buf, n * INST_FLOATS * 4, data_of(dst), GPU_DYNAMIC) }
return l.fog_buf
}
function fog_casters_n(render3d_st: Render3dState, l: Layer) -> int {
if render3d_st.r3d_fog_wall <= 0.0 { return l.n_sh }
return l.fog_n
}

View file

@ -127,6 +127,13 @@ function grass_init(render3d_st: mut Render3dState) -> void {
}
# indices per 16 m cell that could exist at distance d (the count the shader computes)
# no blades past the fog wall: they would be drawn only to be fogged away
function grass_reach(render3d_st: Render3dState) -> float {
let w = render3d_st.r3d_fog_wall
if w > 0.0 and w < render3d_st.grass_radius { return w }
return render3d_st.grass_radius
}
function grass_count_at(render3d_st: Render3dState, d: float) -> int {
let spacing = render3d_st.grass_s0 * (1.0 + d / render3d_st.grass_d0)
let n = float(GRASS_CELL * GRASS_CELL) / (spacing * spacing)
@ -137,7 +144,7 @@ function grass_count_at(render3d_st: Render3dState, d: float) -> int {
function grass_tiles(render3d_st: mut Render3dState, size: int, d_min: float, d_max: float, blade: Mesh) -> void {
let p = render3d_st.grass_prog
let cells = size / GRASS_CELL
if d_min >= render3d_st.grass_radius { return }
if d_min >= grass_reach(render3d_st) { return }
if render3d_st.grass_merge {
render3d_st.grass_band_start[render3d_st.grass_band_n] = render3d_st.grass_n; render3d_st.grass_band_cells[render3d_st.grass_band_n] = cells
render3d_st.grass_band_mesh[render3d_st.grass_band_n] = blade; render3d_st.grass_band_n += 1
@ -228,11 +235,11 @@ function grass_bind(render3d_st: mut Render3dState, p: int) -> void {
u_v3(render3d_st, gpu_uniform(render3d_st, p, "u_tint"), render3d_st.sc_blade_tint)
u_v3(render3d_st, gpu_uniform(render3d_st, p, "u_blade_base"), render3d_st.sc_blade_base)
u_v3(render3d_st, gpu_uniform(render3d_st, p, "u_blade_tip"), render3d_st.sc_blade_tip)
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_cull"), render3d_st.grass_radius)
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_cull"), grass_reach(render3d_st))
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_model_h"), 0.0)
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_s0"), render3d_st.grass_s0)
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_d0"), render3d_st.grass_d0)
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_radius"), render3d_st.grass_radius)
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_radius"), grass_reach(render3d_st))
var ph = render3d_st.post_h
if ph <= 0 { ph = gl_height() }
u_f(render3d_st, gpu_uniform(render3d_st, p, "u_px"), 2.0 * Math.tan(render3d_st.cam_fov * 0.5) / float(ph))
@ -268,7 +275,7 @@ function grass_bands(render3d_st: mut Render3dState) -> void {
grass_tiles(render3d_st, 4, 0.0, 6.0, render3d_st.grass_mesh)
grass_tiles(render3d_st, 4, 6.0, 16.0, render3d_st.grass_mesh3)
grass_tiles(render3d_st, 8, 16.0, 40.0, render3d_st.grass_mesh2)
grass_tiles(render3d_st, 16, 40.0, render3d_st.grass_radius, render3d_st.grass_mesh2)
grass_tiles(render3d_st, 16, 40.0, grass_reach(render3d_st), render3d_st.grass_mesh2)
}
# the records gathered this frame: uploaded once, then each band GRASS_CHUNK records a draw

View file

@ -66,7 +66,7 @@ function gg_blade(render3d_st: mut Render3dState, rows: int) -> Mesh {
# one tile size over one distance band: its visible tiles into gg_tv (corner, indices per cell, cells)
function gg_tiles(render3d_st: mut Render3dState, size: int, d_min: float, d_max: float) -> void {
if d_min >= render3d_st.grass_radius { return }
if d_min >= grass_reach(render3d_st) { return }
let cells = size / GRASS_CELL
let sz = float(size)
let half = sz * 0.5
@ -102,7 +102,7 @@ function gg_cull_frame(render3d_st: mut Render3dState) -> void {
render3d_st.gg_max = 0
gg_tiles(render3d_st, 4, 0.0, 16.0)
gg_tiles(render3d_st, 8, 16.0, 40.0)
gg_tiles(render3d_st, 16, 40.0, render3d_st.grass_radius)
gg_tiles(render3d_st, 16, 40.0, grass_reach(render3d_st))
# made once, with the device (gvk_startup_state)
let rb = render3d_st.gg_rb
rb[0] = GG_BANDS; rb[1] = 0; rb[2] = 0; rb[3] = 0
@ -126,7 +126,7 @@ function gg_params(render3d_st: mut Render3dState) -> words {
let pr = render3d_st.gg_pr
for i in 0 .. 48 { pr[i] = 0 }
if render3d_st.cam_planes != null { for i in 0 .. 16 { pr[i] = float_bits(render3d_st.cam_planes[i]) } }
pr[16] = float_bits(render3d_st.cam_pos[0]); pr[17] = float_bits(render3d_st.cam_pos[1]); pr[18] = float_bits(render3d_st.cam_pos[2]); pr[19] = float_bits(render3d_st.grass_radius)
pr[16] = float_bits(render3d_st.cam_pos[0]); pr[17] = float_bits(render3d_st.cam_pos[1]); pr[18] = float_bits(render3d_st.cam_pos[2]); pr[19] = float_bits(grass_reach(render3d_st))
var ph = render3d_st.post_h
if ph <= 0 { ph = gl_height() }
pr[20] = float_bits(render3d_st.grass_s0); pr[21] = float_bits(render3d_st.grass_d0); pr[22] = float_bits(2.0 * Math.tan(render3d_st.cam_fov * 0.5) / float(ph)); pr[23] = float_bits(render3d_st.ter_snow_line)

View file

@ -33,6 +33,7 @@ import "gltf_release.ludic"
import "gltf_cached.ludic"
import "skin.ludic"
import "scatter.ludic"
import "fog_casters.ludic"
import "actor.ludic"
import "stream.ludic"
import "grass.ludic"

View file

@ -54,7 +54,10 @@ const R3D_FOG_MARGIN: float = 8.0
export function r3d_fog_wall(render3d_st: mut Render3dState, dist: float) -> void {
var d = dist
if d < 0.0 { d = 0.0 }
if d == render3d_st.r3d_fog_wall { return }
render3d_st.r3d_fog_wall = d
# every layer and stream re-gathers for the new reach
render3d_st.sc_view_gen += 1
cam_update(render3d_st)
}
# how far anything is drawn: the fog wall and a margin, or `far` (0: no limit of its own) without one

View file

@ -32,6 +32,12 @@ property Layer {
imp_buf: int = 0,
sh_buf: int = 0, # every instance, for shadow casting (no cull, no LOD split)
n_sh: int = 0,
fog_buf: int = 0, # the casters inside the fog wall (fog_casters.ludic)
fog_n: int = 0,
fog_src: int = -1,
fog_wall: float = 0.0,
fog_x: float = 0.0,
fog_z: float = 0.0,
n_far: int = 0,
scratch: floats,
last_cam: floats,
@ -261,6 +267,7 @@ function layer_new(render3d_st: mut Render3dState, model: Model, cap: int, folia
l.buf = gpu_buffer_new(render3d_st)
l.imp_buf = gpu_buffer_new(render3d_st)
l.sh_buf = gpu_buffer_new(render3d_st)
l.fog_buf = gpu_buffer_new(render3d_st)
l.rough = 1.0
for i in 0 .. len(model.prims) { scatter_attach(render3d_st, model.prims[i].mesh, l.buf) }
push(render3d_st.sc_layers, l)
@ -983,8 +990,11 @@ function layer_draw_shadow(render3d_st: mut Render3dState, l: Layer, light_vp: f
u_v3(render3d_st, gpu_uniform(render3d_st, p, "u_face_dir"), render3d_st.sun_dir)
u_v3(render3d_st, gpu_uniform(render3d_st, p, "u_cam_pos"), render3d_st.cam_pos)
gpu_cull(render3d_st, false)
scatter_attach(render3d_st, render3d_st.sc_card, l.sh_buf)
mesh_draw_instanced(render3d_st, render3d_st.sc_card, l.n_sh)
let buf = fog_casters(render3d_st, l)
let n = fog_casters_n(render3d_st, l)
if n == 0 { return }
scatter_attach(render3d_st, render3d_st.sc_card, buf)
mesh_draw_instanced(render3d_st, render3d_st.sc_card, n)
}
@ -1196,6 +1206,7 @@ function scatter_clear_all(render3d_st: mut Render3dState) -> void {
if l.buf != 0 { gpu_buffer_free(render3d_st, l.buf) }
if l.imp_buf != 0 { gpu_buffer_free(render3d_st, l.imp_buf) }
if l.sh_buf != 0 { gpu_buffer_free(render3d_st, l.sh_buf) }
if l.fog_buf != 0 { gpu_buffer_free(render3d_st, l.fog_buf) }
if l.lod_buf != null { for k in 0 .. l.n_lods { gpu_buffer_free(render3d_st, l.lod_buf[k]) } }
if l.inst != null { free(l.inst) }
if l.scratch != null { free(l.scratch) }

View file

@ -221,7 +221,9 @@ function stream_update(render3d_st: mut Render3dState, s: Stream, cam_x: float,
render3d_st.stream_walks += 1
render3d_st.stream_walk_no += 1
let tw = gl_now_us()
let r = int(s.reach / s.size) + 1
# past the fog wall nothing is generated, gathered or kept wanted
let reach = r3d_reach(render3d_st, s.reach)
let r = int(reach / s.size) + 1
# rings outward from the camera's cell: the nearest chunks are generated first
var ring = 0
while ring <= r {
@ -236,7 +238,7 @@ function stream_update(render3d_st: mut Render3dState, s: Stream, cam_x: float,
let dx = wx - cam_x; let dz = wz - cam_z
let d = Math.sqrt(dx * dx + dz * dz)
let band = stream_band(s, d)
if band < 4 and band >= s.min_band and d < s.reach + s.size {
if band < 4 and band >= s.min_band and d < reach + s.size {
let key = stream_key(cx, cz, band)
var c = stream_find(s, key)
if c != null { c.used = render3d_st.stream_walk_no }