From a1a90e0626942b96e480a4761c4dadb0fc35c9fd Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 29 Sep 2026 12:37:24 +0300 Subject: [PATCH] render3d/shadow: a dressed figure casts one capsule in the far cascades In cascades 2 and out (receivers from 60 m) a skinned figure of more than eight pieces - a dressed person, up to 86 of them - casts one capsule its own height (radius 13% of it) instead of every visible piece: past 60 m nobody can tell a garment's shadow from the body's. Animals (a piece or two), rigid props and the near two cascades are unchanged. The capsule is made once. Aspen view, same main (30ec1722) and foundations (15f1020), 240 frames, twice a side: shadow draws 1889 -> 1799; shadow GPU 4808/4819 -> 4710/4714 us; median frame 17200/17221 -> 16910/17128 us. Most of the far cascades' actor draws turned out to be rigid props (the census's "skinned" counts draws, not actors), so this is the smaller of the two. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.render3d/actor.ludic | 86 ++++++++++++++++++++++++++++- packages/ludic.render3d/env.ludic | 3 + 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/packages/ludic.render3d/actor.ludic b/packages/ludic.render3d/actor.ludic index 199be570..7f37a0c8 100644 --- a/packages/ludic.render3d/actor.ludic +++ b/packages/ludic.render3d/actor.ludic @@ -411,7 +411,7 @@ function actor_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> var ap = render3d_st.ac_sh if a.cutout { ap = render3d_st.ac_sh_cut } if render3d_st.ac_census_at > 0 and render3d_st.ac_frame == render3d_st.ac_census_at { ac_caster_count(render3d_st, a) } - actor_draw_one(render3d_st, a, ap, true) + if ac_proxy_figure(render3d_st, a) { actor_draw_proxy(render3d_st, a) } else { actor_draw_one(render3d_st, a, ap, true) } } if render3d_st.ac_census_at > 0 and render3d_st.ac_frame == render3d_st.ac_census_at { print(`actor census, cascade {render3d_st.sh_cascade}: {render3d_st.ac_cc_actors} actors cast {render3d_st.ac_cc_draws} draws ({render3d_st.ac_cc_skinned} of them skinned, {render3d_st.ac_cc_cut} cutout)`) @@ -492,3 +492,87 @@ function actor_room(l: []Actor, n: int) -> void { for i in 0 .. n { push(l, null) } while len(l) > have { List.pop(l) } } + +# ---- a figure's shadow past 60 m: one capsule --------------------------------------------------- +# A dressed figure is up to 86 skinned pieces, and the far cascades (2 and out: receivers from 60 m) +# drew each piece of every walker in them - most of a forest frame's shadow pass. Nobody can tell a +# garment's shadow from the body's there, so a figure casts one capsule its own height instead. +# Animals (a piece or two) and anything rigid keep their own shape. +const AC_PROXY_CASCADE: int = 2 +const AC_PROXY_SIDES: int = 8 +const AC_PROXY_R: float = 0.13 # the capsule's radius, as a fraction of the figure's height +function ac_proxy_figure(render3d_st: Render3dState, a: Actor) -> bool { + if render3d_st.sh_cascade < AC_PROXY_CASCADE { return false } + if a.skin == null and a.model.skin == null { return false } + return len(a.model.prims) > 8 +} +function actor_draw_proxy(render3d_st: mut Render3dState, a: Actor) -> void { + let m = ac_proxy_mesh(render3d_st) + let ap = render3d_st.ac_sh + gpu_use_program(render3d_st, ap.prog) + let h = a.model.height + m4_scaling(render3d_st.ac_proxy_s, h * AC_PROXY_R, h, h * AC_PROXY_R) + m4_mul(render3d_st.ac_proxy_m, a.mat, render3d_st.ac_proxy_s) + u_mat4(render3d_st, ap.l_model, render3d_st.ac_proxy_m) + u_f(render3d_st, ap.l_skin, 0.0) + gpu_cull(render3d_st, false) + mesh_draw(render3d_st, m) +} +# a unit capsule (radius 1, height 1, feet at the origin): a ring of AC_PROXY_SIDES at each end of the +# body and a cone to a point below and above them +@alloc_ok("made once: the capsule every far figure casts, and its two matrices") +function ac_proxy_mesh(render3d_st: mut Render3dState) -> Mesh { + if render3d_st.ac_proxy != null { return render3d_st.ac_proxy } + render3d_st.ac_proxy_s = m4_new() + render3d_st.ac_proxy_m = m4_new() + let n = AC_PROXY_SIDES + let nv = 2 + 2 * n + let pos = gl_floats(nv * 3) + let nrm = gl_floats(nv * 3) + let uv = gl_floats(nv * 2) + ac_proxy_vert(pos, nrm, 0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0) + ac_proxy_vert(pos, nrm, 1, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0) + for i in 0 .. n { + let t = float(i) * 6.2831853 / float(n) + let cx = Math.cos(t) + let sz = Math.sin(t) + ac_proxy_vert(pos, nrm, 2 + i, cx, AC_PROXY_R, sz, cx, 0.0, sz) + ac_proxy_vert(pos, nrm, 2 + n + i, cx, 1.0 - AC_PROXY_R, sz, cx, 0.0, sz) + } + for i in 0 .. nv * 2 { gl_put_bits(uv, i, float_bits(0.0)) } + let m = gpu_mesh_new(render3d_st) + gpu_mesh_vertices(render3d_st, m, pos, gl_bytes_of(nv * 3), GPU_STATIC) + gpu_mesh_attr(render3d_st, m, 0, 3, GPU_F32, 0, 0, false) + gpu_mesh_vertices(render3d_st, m, nrm, gl_bytes_of(nv * 3), GPU_STATIC) + gpu_mesh_attr(render3d_st, m, 1, 3, GPU_F32, 0, 0, false) + gpu_mesh_vertices(render3d_st, m, uv, gl_bytes_of(nv * 2), GPU_STATIC) + gpu_mesh_attr(render3d_st, m, 2, 2, GPU_F32, 0, 0, false) + free(pos) + free(nrm) + free(uv) + let ni = n * 12 + let idx = words(ni) + var k = 0 + for i in 0 .. n { + let j = (i + 1) % n + let b0 = 2 + i + let b1 = 2 + j + let t0 = 2 + n + i + let t1 = 2 + n + j + idx[k] = 0; idx[k + 1] = b0; idx[k + 2] = b1 + idx[k + 3] = b0; idx[k + 4] = t0; idx[k + 5] = t1 + idx[k + 6] = b0; idx[k + 7] = t1; idx[k + 8] = b1 + idx[k + 9] = 1; idx[k + 10] = t1; idx[k + 11] = t0 + k += 12 + } + gpu_mesh_indices(render3d_st, m, data_of(idx), ni * 4, 4) + free(idx) + m.count = ni + gpu_mesh_done(render3d_st, m) + render3d_st.ac_proxy = m + return m +} +function ac_proxy_vert(pos: pointer, nrm: pointer, v: int, x: float, y: float, z: float, nx: float, ny: float, nz: float) -> void { + gl_put_bits(pos, v * 3, float_bits(x)); gl_put_bits(pos, v * 3 + 1, float_bits(y)); gl_put_bits(pos, v * 3 + 2, float_bits(z)) + gl_put_bits(nrm, v * 3, float_bits(nx)); gl_put_bits(nrm, v * 3 + 1, float_bits(ny)); gl_put_bits(nrm, v * 3 + 2, float_bits(nz)) +} diff --git a/packages/ludic.render3d/env.ludic b/packages/ludic.render3d/env.ludic index bac82d23..740379da 100644 --- a/packages/ludic.render3d/env.ludic +++ b/packages/ludic.render3d/env.ludic @@ -19,6 +19,9 @@ export state Render3dState { ac_oq_n: int = 0 # live entries; the array is kept and reused ac_oq_closed: bool = true ac_census_at: int = -2 + ac_proxy: Mesh = null # the capsule a figure casts in the far cascades (actor.ludic) + ac_proxy_s: floats = null # its scale and its world matrix, made with it + ac_proxy_m: floats = null ac_lp0: floats = floats(3) ac_lpx: floats = floats(3) ac_lpy: floats = floats(3)