use crate::sim::math::Vec3;
const UPWARD: f32 = 1.0e-3;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct CharacterConfig {
pub max_slope_deg: f32,
pub step_height: f32,
pub grounded: bool,
}
impl Default for CharacterConfig {
fn default() -> Self {
CharacterConfig {
max_slope_deg: 45.0,
step_height: 0.3,
grounded: true,
}
}
}
impl CharacterConfig {
pub(crate) fn new(max_slope_deg: f32, step_height: f32, grounded: bool) -> Self {
CharacterConfig {
max_slope_deg,
step_height,
grounded,
}
}
pub(crate) fn min_ground_normal_y(&self) -> f32 {
if self.max_slope_deg <= 0.0 {
return UPWARD;
}
let radians = self.max_slope_deg * (core::f32::consts::PI / 180.0);
libm::cosf(radians).max(UPWARD)
}
pub(crate) fn is_walkable(&self, normal: Vec3) -> bool {
normal.y >= self.min_ground_normal_y()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::math::vec3;
fn tilted(degrees: f32) -> Vec3 {
let radians = degrees * (core::f32::consts::PI / 180.0);
vec3(libm::sinf(radians), libm::cosf(radians), 0.0)
}
#[test]
fn a_slope_at_the_limit_is_walkable_and_one_past_it_is_not() {
let config = CharacterConfig::new(45.0, 0.3, true);
assert!(config.is_walkable(Vec3::Y));
assert!(config.is_walkable(tilted(44.0)));
assert!(!config.is_walkable(tilted(46.0)));
assert!(
!config.is_walkable(vec3(1.0, 0.0, 0.0)),
"a wall is not ground"
);
assert!(!config.is_walkable(-Vec3::Y), "a ceiling is not ground");
}
#[test]
fn a_disabled_limit_walks_every_upward_face_but_still_not_a_wall() {
for max_slope_deg in [0.0, -10.0] {
let config = CharacterConfig::new(max_slope_deg, 0.3, true);
assert!(config.is_walkable(tilted(80.0)), "{max_slope_deg}");
assert!(!config.is_walkable(vec3(1.0, 0.0, 0.0)), "{max_slope_deg}");
assert!(!config.is_walkable(tilted(95.0)), "{max_slope_deg}");
}
}
#[test]
fn a_limit_at_vertical_or_beyond_stops_at_upward_facing() {
for max_slope_deg in [90.0, 120.0] {
let config = CharacterConfig::new(max_slope_deg, 0.3, true);
assert!(config.min_ground_normal_y() > 0.0, "{max_slope_deg}");
assert!(config.is_walkable(tilted(89.0)), "{max_slope_deg}");
assert!(!config.is_walkable(tilted(91.0)), "{max_slope_deg}");
}
}
#[test]
fn the_default_is_a_grounded_character_on_ordinary_terrain() {
let config = CharacterConfig::default();
assert!(config.grounded);
assert!(config.step_height > 0.0);
assert!(config.is_walkable(tilted(30.0)));
assert!(!config.is_walkable(tilted(60.0)));
}
}