diff --git a/packages/ludic.physics/index.ludic b/packages/ludic.physics/index.ludic index d269641f..9fd169bc 100644 --- a/packages/ludic.physics/index.ludic +++ b/packages/ludic.physics/index.ludic @@ -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" diff --git a/packages/ludic.physics/shape_ids.ludic b/packages/ludic.physics/shape_ids.ludic new file mode 100644 index 00000000..199bc482 --- /dev/null +++ b/packages/ludic.physics/shape_ids.ludic @@ -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) } diff --git a/packages/ludic.physics/shapes.ludic b/packages/ludic.physics/shapes.ludic index ead672d0..3ec7a04d 100644 --- a/packages/ludic.physics/shapes.ludic +++ b/packages/ludic.physics/shapes.ludic @@ -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)) } diff --git a/packages/ludic.physics/state.ludic b/packages/ludic.physics/state.ludic index b99bb926..dd5cf4cc 100644 --- a/packages/ludic.physics/state.ludic +++ b/packages/ludic.physics/state.ludic @@ -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 diff --git a/packages/ludic.physics/tests/shape_free_test.ludic b/packages/ludic.physics/tests/shape_free_test.ludic new file mode 100644 index 00000000..11e2831c --- /dev/null +++ b/packages/ludic.physics/tests/shape_free_test.ludic @@ -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) + } +}