use super::PhysicsSolver;
use crate::Transform;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PhysicsBenchmark {
Falling,
Stacking,
Debris,
Mixed,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BenchmarkBody {
pub transform: Transform,
pub linear_velocity: [f32; 3],
pub solver: PhysicsSolver,
}
pub const BENCHMARK_TOWER_HEIGHT: usize = 10;
impl PhysicsBenchmark {
pub const ALL: [Self; 4] =
[Self::Falling, Self::Stacking, Self::Debris, Self::Mixed];
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::Falling => "falling",
Self::Stacking => "stacking",
Self::Debris => "debris",
Self::Mixed => "mixed",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::ALL.into_iter().find(|scene| scene.name() == name)
}
#[must_use]
pub fn bodies(self, count: usize) -> Vec<BenchmarkBody> {
let body = |position, solver| BenchmarkBody {
transform: Transform::new(position),
linear_velocity: [0.0; 3],
solver,
};
match self {
Self::Falling | Self::Mixed => {
let side = square_side(count.div_ceil(10));
(0..count)
.map(|index| {
let [x, z] = grid(index % (side * side), side, 1.5);
let layer = (index / (side * side)) as f32;
let solver = if self == Self::Mixed {
[
PhysicsSolver::Full,
PhysicsSolver::Simplified,
PhysicsSolver::Space,
PhysicsSolver::NoCollision,
][index % 4]
} else {
PhysicsSolver::Full
};
body([x, 5.0 + 1.5 * layer, z], solver)
})
.collect()
}
Self::Stacking => {
let towers = count.div_ceil(BENCHMARK_TOWER_HEIGHT);
let side = square_side(towers);
(0..count)
.map(|index| {
let tower = index / BENCHMARK_TOWER_HEIGHT;
let level = (index % BENCHMARK_TOWER_HEIGHT) as f32;
let [x, z] = grid(tower, side, 2.0);
body([x, 0.5 + level, z], PhysicsSolver::Full)
})
.collect()
}
Self::Debris => {
let side = (count as f64).cbrt().ceil().max(1.0) as usize;
(0..count)
.map(|index| {
let [x, z] = grid(index % (side * side), side, 1.8);
let layer = (index / (side * side)) as f32;
let mut body = body(
[x, 1.0 + 1.8 * layer, z],
PhysicsSolver::Full,
);
body.transform.rotation =
[0, 1, 2].map(|axis| 0.4 * jitter(index, axis));
body.linear_velocity = [
0.5 * x + 2.0 * jitter(index, 3),
4.0 + 2.0 * jitter(index, 4),
0.5 * z + 2.0 * jitter(index, 5),
];
body
})
.collect()
}
}
}
}
fn square_side(cells: usize) -> usize {
(cells as f64).sqrt().ceil().max(1.0) as usize
}
pub(super) fn grid(index: usize, side: usize, spacing: f32) -> [f32; 2] {
let centre = (side - 1) as f32 / 2.0;
[
((index % side) as f32 - centre) * spacing,
((index / side) as f32 - centre) * spacing,
]
}
fn jitter(index: usize, channel: u64) -> f32 {
let mut value = (index as u64) ^ (channel << 56);
value = value.wrapping_add(0x9e37_79b9_7f4a_7c15);
value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
value ^= value >> 31;
(value >> 40) as f32 / (1u64 << 23) as f32 - 1.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scenes_are_repeatable_at_every_size() {
for scene in PhysicsBenchmark::ALL {
assert_eq!(PhysicsBenchmark::from_name(scene.name()), Some(scene));
for count in [1_000, 10_000, 100_000] {
let bodies = scene.bodies(count);
assert_eq!(bodies.len(), count);
assert_eq!(bodies, scene.bodies(count), "{scene:?} {count}");
assert!(bodies.iter().all(|body| {
body.transform.position.iter().all(|v| v.is_finite())
&& body.transform.position[1] >= 0.5
}));
}
}
}
#[test]
fn scenes_start_without_overlaps_and_mix_their_solvers() {
for scene in PhysicsBenchmark::ALL {
let bodies = scene.bodies(1_000);
for (a, first) in bodies.iter().enumerate() {
for second in &bodies[a + 1..] {
let apart = (0..3).any(|axis| {
(first.transform.position[axis]
- second.transform.position[axis])
.abs()
>= 0.999
});
assert!(apart, "{scene:?}: {first:?} and {second:?}");
}
}
}
let stacking = PhysicsBenchmark::Stacking.bodies(20);
assert_eq!(stacking[9].transform.position[1], 9.5);
assert_eq!(stacking[10].transform.position[1], 0.5);
let mixed = PhysicsBenchmark::Mixed.bodies(8);
let solvers: Vec<_> = mixed.iter().map(|body| body.solver).collect();
assert_eq!(solvers[..4], solvers[4..]);
assert!(solvers.contains(&PhysicsSolver::NoCollision));
let debris = PhysicsBenchmark::Debris.bodies(100);
assert!(debris.iter().all(|body| body.linear_velocity[1] >= 2.0));
}
}