use crate::ColliderShape;
use crate::sim::math::Vec3;
pub(crate) fn min_extent(shape: &ColliderShape) -> f32 {
match *shape {
ColliderShape::Ball { radius } => 2.0 * libm::fabsf(radius),
ColliderShape::Capsule { radius, .. } => 2.0 * libm::fabsf(radius),
ColliderShape::Cuboid { half_extents } => {
let thinnest = half_extents
.iter()
.fold(f32::INFINITY, |least, &half| least.min(libm::fabsf(half)));
2.0 * thinnest
}
}
}
pub(crate) fn is_fast(width: f32, motion: Vec3, ratio: f32) -> bool {
if width <= 0.0 || ratio <= 0.0 {
return false;
}
let threshold = ratio * width;
motion.length_squared() > threshold * threshold
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::math::vec3;
const RATIO: f32 = 0.5;
const TICK: f32 = 1.0 / 60.0;
const BALL: ColliderShape = ColliderShape::Ball { radius: 0.5 };
const CRATE: ColliderShape = ColliderShape::Cuboid {
half_extents: [0.4, 0.4, 0.4],
};
const PANEL: ColliderShape = ColliderShape::Cuboid {
half_extents: [2.0, 2.0, 0.02],
};
const CAPSULE: ColliderShape = ColliderShape::Capsule {
half_height: 0.6,
radius: 0.3,
};
#[test]
fn the_width_is_the_thin_way_across_each_shape() {
assert_eq!(min_extent(&BALL), 1.0);
assert_eq!(min_extent(&CRATE), 0.8);
assert!((min_extent(&PANEL) - 0.04).abs() < 1.0e-6);
assert_eq!(min_extent(&CAPSULE), 0.6);
}
#[test]
fn ordinary_speeds_do_not_arm_the_sweep() {
for speed in [1.0, 4.0, 8.0, 20.0] {
let motion = vec3(0.0, -speed * TICK, 0.0);
assert!(
!is_fast(min_extent(&CRATE), motion, RATIO),
"a 0.8-wide crate at {speed} units per second travels {} per tick",
speed * TICK
);
}
assert!(
!is_fast(min_extent(&CAPSULE), vec3(8.0 * TICK, 0.0, 0.0), RATIO),
"a character walking is not a candidate"
);
}
#[test]
fn a_body_crossing_a_unit_per_tick_arms_the_sweep() {
assert!(is_fast(min_extent(&BALL), vec3(0.0, 0.0, -1.0), RATIO));
assert!(is_fast(
min_extent(&CRATE),
vec3(0.0, -60.0 * TICK, 0.0),
RATIO
));
}
#[test]
fn nothing_can_tunnel_before_the_gate_fires() {
for shape in [BALL, CRATE, PANEL, CAPSULE] {
let width = min_extent(&shape);
let motion = vec3(width * 1.000_1, 0.0, 0.0);
assert!(
is_fast(width, motion, RATIO),
"{shape:?} can cross its own {width} wide unswept"
);
assert!(is_fast(width, motion, 1.0), "{shape:?} at the ratio cap");
}
}
#[test]
fn a_shape_with_no_width_and_a_disabled_ratio_both_decline() {
let point = ColliderShape::Ball { radius: 0.0 };
assert!(!is_fast(min_extent(&point), vec3(100.0, 0.0, 0.0), RATIO));
assert!(!is_fast(min_extent(&BALL), vec3(100.0, 0.0, 0.0), 0.0));
assert!(!is_fast(min_extent(&BALL), vec3(100.0, 0.0, 0.0), -1.0));
}
#[test]
fn the_gate_reads_the_whole_motion_and_not_one_axis_of_it() {
let motion = vec3(0.3, 0.3, 0.3);
assert!(motion.x < 0.5 * min_extent(&BALL));
assert!(
is_fast(min_extent(&BALL), motion, RATIO),
"{}",
motion.length()
);
}
}