use crate::sim::math::{Vec3, vec3};
pub(crate) fn deflect(motion: Vec3, normal: Vec3, walkable: bool) -> Vec3 {
let slid = motion - normal * motion.dot(normal);
if walkable || slid.y <= 0.0 {
return slid;
}
horizontal(slid)
}
pub(crate) fn crease(motion: Vec3, first: Vec3, second: Vec3) -> Vec3 {
let along = first.cross(second).normalize_or_zero();
along * motion.dot(along)
}
pub(crate) fn re_entrant(slid: Vec3, previous: Vec3) -> bool {
slid.dot(previous) < 0.0
}
pub(crate) fn horizontal(v: Vec3) -> Vec3 {
vec3(v.x, 0.0, v.z)
}
#[cfg(test)]
mod tests {
use super::*;
fn hill(degrees: f32) -> Vec3 {
let radians = degrees * (core::f32::consts::PI / 180.0);
vec3(-libm::sinf(radians), libm::cosf(radians), 0.0)
}
#[test]
fn a_move_straight_into_a_wall_has_nothing_left() {
let slid = deflect(vec3(0.0, 0.0, 1.0), -Vec3::Z, false);
assert!(slid.length() < 1.0e-6, "{slid:?}");
}
#[test]
fn a_move_at_an_angle_to_a_wall_keeps_the_part_along_it() {
let slid = deflect(vec3(1.0, 0.0, 1.0), -Vec3::Z, false);
assert!((slid - vec3(1.0, 0.0, 0.0)).length() < 1.0e-6, "{slid:?}");
}
#[test]
fn a_wall_leaves_a_falling_move_falling() {
let slid = deflect(vec3(0.0, -1.0, 1.0), -Vec3::Z, false);
assert!((slid - vec3(0.0, -1.0, 0.0)).length() < 1.0e-6, "{slid:?}");
}
#[test]
fn a_walkable_slope_lets_the_move_climb_it() {
let slid = deflect(vec3(1.0, -0.1, 0.0), hill(20.0), true);
assert!(slid.y > 0.0, "walking into a ramp goes up it: {slid:?}");
}
#[test]
fn a_slope_past_the_limit_never_carries_the_move_upward() {
let climbing = deflect(vec3(1.0, -0.1, 0.0), hill(60.0), false);
assert!(climbing.y <= 0.0, "{climbing:?}");
assert!(
climbing.z.abs() < 1.0e-6,
"still along the slope: {climbing:?}"
);
let falling = deflect(vec3(0.0, -1.0, 0.0), hill(60.0), false);
assert!(falling.y < 0.0, "gravity still slides it down: {falling:?}");
assert!(falling.x < 0.0, "and away from the hill: {falling:?}");
}
#[test]
fn a_crease_leaves_only_the_line_the_two_planes_share() {
let along = crease(vec3(1.0, -1.0, 1.0), -Vec3::Z, -Vec3::X);
assert!(
along.x.abs() < 1.0e-6 && along.z.abs() < 1.0e-6,
"{along:?}"
);
assert!((along.y + 1.0).abs() < 1.0e-6, "{along:?}");
}
#[test]
fn two_opposed_walls_leave_nothing() {
let along = crease(vec3(1.0, 0.0, 0.0), Vec3::X, -Vec3::X);
assert_eq!(along, Vec3::ZERO);
}
#[test]
fn re_entry_is_a_slide_that_turns_back_into_what_was_already_met() {
assert!(re_entrant(vec3(0.0, 0.0, 1.0), -Vec3::Z));
assert!(!re_entrant(vec3(0.0, 0.0, -1.0), -Vec3::Z));
assert!(
!re_entrant(vec3(1.0, 0.0, 0.0), -Vec3::Z),
"along it is fine"
);
}
#[test]
fn horizontal_drops_the_vertical_part() {
assert_eq!(horizontal(vec3(1.0, 5.0, -2.0)), vec3(1.0, 0.0, -2.0));
}
}