#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PhysicsCounts {
pub static_colliders: u32,
pub dynamic_colliders: u32,
pub trigger_volumes: u32,
pub joints: u32,
pub world_anchored_joints: u32,
pub player_capsules: u32,
pub rig_capsules: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PhysicsBudget {
pub fixed: u32,
pub dynamic: u32,
pub kinematic: u32,
pub sensors: u32,
pub joints: u32,
pub anchors: u32,
pub spawn_headroom: u32,
}
impl PhysicsBudget {
pub const fn derive(counts: &PhysicsCounts, spawn_headroom: u32) -> Self {
Self {
fixed: counts.static_colliders.saturating_add(1),
dynamic: counts.dynamic_colliders,
kinematic: counts.player_capsules.saturating_add(counts.rig_capsules),
sensors: counts.trigger_volumes,
joints: counts.joints,
anchors: counts.world_anchored_joints,
spawn_headroom,
}
}
pub const fn body_total(&self) -> u32 {
self.fixed
.saturating_add(self.dynamic)
.saturating_add(self.kinematic)
.saturating_add(self.sensors)
.saturating_add(self.anchors)
}
pub const fn body_cap(&self) -> u32 {
self.body_total().saturating_add(self.spawn_headroom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_empty_world_still_reserves_its_floor() {
let budget = PhysicsBudget::derive(&PhysicsCounts::default(), 0);
assert_eq!(budget.fixed, 1, "the floor body is always built");
assert_eq!(budget.body_total(), 1);
assert_eq!(budget.body_cap(), 1);
}
#[test]
fn each_count_lands_in_its_own_category() {
let counts = PhysicsCounts {
static_colliders: 4,
dynamic_colliders: 3,
trigger_volumes: 2,
joints: 5,
world_anchored_joints: 2,
player_capsules: 1,
rig_capsules: 6,
};
let budget = PhysicsBudget::derive(&counts, 0);
assert_eq!(budget.fixed, 5, "4 static colliders plus the floor");
assert_eq!(budget.dynamic, 3);
assert_eq!(budget.kinematic, 7, "1 player capsule plus 6 rig capsules");
assert_eq!(budget.sensors, 2);
assert_eq!(budget.joints, 5);
assert_eq!(budget.anchors, 2);
assert_eq!(budget.body_total(), 5 + 3 + 7 + 2 + 2);
}
#[test]
fn headroom_lifts_the_cap_but_not_the_total() {
let counts = PhysicsCounts {
static_colliders: 2,
..PhysicsCounts::default()
};
let budget = PhysicsBudget::derive(&counts, 32);
assert_eq!(budget.body_total(), 3);
assert_eq!(budget.body_cap(), 35);
}
#[test]
fn derivation_is_the_same_from_either_side() {
let counts = PhysicsCounts {
static_colliders: 9,
dynamic_colliders: 4,
rig_capsules: 2,
..PhysicsCounts::default()
};
assert_eq!(
PhysicsBudget::derive(&counts, 8),
PhysicsBudget::derive(&counts, 8)
);
}
#[test]
fn absurd_counts_saturate_instead_of_wrapping() {
let counts = PhysicsCounts {
static_colliders: u32::MAX,
dynamic_colliders: u32::MAX,
..PhysicsCounts::default()
};
let budget = PhysicsBudget::derive(&counts, u32::MAX);
assert_eq!(budget.fixed, u32::MAX);
assert_eq!(budget.body_cap(), u32::MAX);
}
}