feat(ludic.physics): phys_shape_free - a shape let go hands its id to the next, so a ground by chunk grows nothing (23.5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 11:43:40 +03:00
parent 96423634ad
commit 0c3c0c5e6a
5 changed files with 84 additions and 12 deletions

View file

@ -5,6 +5,7 @@ numbers float
import "ludic.base"
import "native.ludic"
import "state.ludic"
import "shape_ids.ludic"
import "shapes.ludic"
import "bodies.ludic"
import "queries.ludic"

View file

@ -0,0 +1,33 @@
# shape_ids.ludic - a shape id is its place in ph_shapes. A shape let go leaves its place to the next
# one made, so a ground that comes and goes by chunk grows nothing
function ph_keep(physics_st: mut PhysicsState, s: pointer) -> int {
if s == null { return -1 }
let n = len(physics_st.ph_spare)
if n > 0 {
let id = physics_st.ph_spare[n - 1]
List.pop(physics_st.ph_spare)
physics_st.ph_shapes[id] = s
return id
}
push(physics_st.ph_shapes, s)
return len(physics_st.ph_shapes) - 1
}
function ph_shape(physics_st: PhysicsState, id: int) -> pointer {
if physics_st.ph_shapes == null or id < 0 or id >= len(physics_st.ph_shapes) { return null }
return physics_st.ph_shapes[id]
}
# a shape let go: a body made with it keeps its own hold until it is removed, and the id may be
# handed to the next shape made
export function phys_shape_free(physics_st: mut PhysicsState, id: int) -> void {
let s = ph_shape(physics_st, id)
if s == null { return }
jph_shape_free(s)
physics_st.ph_shapes[id] = null
push(physics_st.ph_spare, id)
}
# how many shapes are held: a ground that comes and goes must hold this steady
export function phys_shapes(physics_st: PhysicsState) -> int { return len(physics_st.ph_shapes) - len(physics_st.ph_spare) }

View file

@ -32,23 +32,12 @@ export function phys_close(physics_st: mut PhysicsState) -> void {
}
}
physics_st.ph_shapes = new []pointer
List.clear(physics_st.ph_spare)
List.clear(physics_st.ph_floats)
List.clear(physics_st.ph_float_k)
if physics_st.ph_facts != null { q_clear(physics_st.ph_facts) }
}
function ph_keep(physics_st: mut PhysicsState, s: pointer) -> int {
if s == null { return -1 }
ph_buffers()
push(physics_st.ph_shapes, s)
return len(physics_st.ph_shapes) - 1
}
function ph_shape(physics_st: PhysicsState, id: int) -> pointer {
if physics_st.ph_shapes == null or id < 0 or id >= len(physics_st.ph_shapes) { return null }
return physics_st.ph_shapes[id]
}
# a crate: half extents in metres
export function phys_box(physics_st: mut PhysicsState, hx: float, hy: float, hz: float) -> int { return ph_keep(physics_st, jph_shape_box(hx, hy, hz)) }
export function phys_sphere(physics_st: mut PhysicsState, r: float) -> int { return ph_keep(physics_st, jph_shape_sphere(r)) }

View file

@ -37,6 +37,7 @@ export const PH_CAP: int = 512 # contacts drained a step, queries' buffer
export state PhysicsState {
ph_world: pointer = null
ph_shapes: []pointer = new []pointer # a shape id is its place here; null once freed
ph_spare: []int = new []int # the freed places, handed out again (shape_ids.ludic)
ph_step: float = 0.016666668 # seconds a step
ph_sub: int = 1 # collision steps in each
ph_max_steps: int = 4 # a slow frame catches up this far and no further

View file

@ -0,0 +1,48 @@
# shape_free_test.ludic - a shape let go: its body stands until removed, its id goes to the next
# shape made, and a ground that comes and goes a thousand times holds no more shapes than at once
import "ludic.physics"
import "ludic.base"
program ShapeFreeTest {
numbers float
function down(physics_st: mut PhysicsState, x: float, z: float) -> PhysHit {
let h = new PhysHit
phys_ray(physics_st, x, 50.0, z, 0.0, -100.0, 0.0, PHYS_M_ALL, h)
return h
}
# a flat 33 x 33 field at height y, 2 m cells, its first sample at (ox, 0)
function field(physics_st: mut PhysicsState, h: []float, ox: float, y: float) -> int {
for i in 0 .. 33 * 33 { h[i] = y }
return phys_heightfield(physics_st, h, 33, ox, 0.0, 2.0)
}
test "a body outlives its shape's id, and the id is handed out again" (physics_st: mut PhysicsState) {
expect(phys_open(physics_st, 64, 1))
let h = floats(33 * 33)
let s = field(physics_st, h, 0.0, 3.0)
let b = phys_ground_add(physics_st, s)
phys_shape_free(physics_st, s)
phys_settle(physics_st)
expect(Math.abs(down(physics_st, 10.0, 10.0).y - 3.0) < 0.01)
expect_eq(field(physics_st, h, 100.0, 5.0), s)
phys_remove(physics_st, b)
expect(down(physics_st, 10.0, 10.0).body < 0)
phys_close(physics_st)
}
test "a thousand chunks in and out hold two shapes and grow nothing" (physics_st: mut PhysicsState) {
expect(phys_open(physics_st, 64, 1))
let h = floats(33 * 33)
let keep = phys_box(physics_st, 1.0, 1.0, 1.0)
for k in 0 .. 1000 {
let s = field(physics_st, h, float(k % 7) * 64.0, 1.0)
let b = phys_ground_add(physics_st, s)
phys_remove(physics_st, b)
phys_shape_free(physics_st, s)
}
expect_eq(phys_shapes(physics_st), 1)
expect(phys_box(physics_st, 1.0, 1.0, 1.0) != keep)
expect_eq(phys_shapes(physics_st), 2)
phys_close(physics_st)
expect_eq(phys_shapes(physics_st), 0)
}
}