use core::f32::consts::FRAC_1_SQRT_2;
use super::Velocity;
use crate::Direction;
pub trait Force {
fn apply(&self, subject: &mut Subject);
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Subject {
velocity: Velocity,
mass: f32,
pos: (f32, f32),
}
impl Subject {
pub const fn new(velocity: Velocity, mass: f32, pos: (f32, f32)) -> Self {
Self {
velocity,
mass,
pos,
}
}
pub const fn velocity(&self) -> Velocity {
self.velocity
}
pub const fn velocity_mut(&mut self) -> &mut Velocity {
&mut self.velocity
}
pub const fn mass(&self) -> f32 {
self.mass
}
pub const fn pos(&self) -> (f32, f32) {
self.pos
}
}
impl Force for () {
fn apply(&self, _: &mut Subject) {}
}
macro_rules! forces_compose {
($( ( $($force:ident),+ ) )+) => {$(
#[allow(non_snake_case)]
impl<$($force: Force),+> Force for ($($force,)+) {
fn apply(&self, subject: &mut Subject) {
let ($($force,)+) = self;
$($force.apply(subject);)+
}
}
)+};
}
forces_compose!((A)(A, B)(A, B, C)(A, B, C, D)(A, B, C, D, E)(
A, B, C, D, E, G
));
pub(super) fn weighed(mass: f32) -> f32 {
if mass > 0.0 {
mass
} else {
1.0
}
}
impl Direction {
pub(super) fn unit(self) -> (f32, f32) {
match self {
Self::Up => (0.0, -1.0),
Self::UpRight => (FRAC_1_SQRT_2, -FRAC_1_SQRT_2),
Self::Right => (1.0, 0.0),
Self::DownRight => (FRAC_1_SQRT_2, FRAC_1_SQRT_2),
Self::Down => (0.0, 1.0),
Self::DownLeft => (-FRAC_1_SQRT_2, FRAC_1_SQRT_2),
Self::Left => (-1.0, 0.0),
Self::UpLeft => (-FRAC_1_SQRT_2, -FRAC_1_SQRT_2),
}
}
}
#[cfg(test)]
pub(super) const DIRECTIONS: [Direction; 8] = [
Direction::Up,
Direction::UpRight,
Direction::Right,
Direction::DownRight,
Direction::Down,
Direction::DownLeft,
Direction::Left,
Direction::UpLeft,
];
#[cfg(test)]
pub(super) struct Mob {
pub(super) body: crate::Body,
pub(super) velocity: super::Velocity,
pub(super) contacts: super::Contacts,
mass: f32,
}
#[cfg(test)]
impl Mob {
pub(super) fn new() -> Self {
Self {
body: crate::Body::new(0.0, 0.0),
velocity: super::Velocity::default(),
contacts: super::Contacts::default(),
mass: 1.0,
}
}
pub(super) fn with_mass(mass: f32) -> Self {
Self {
mass,
..Self::new()
}
}
pub(super) fn moving(dx: f32, dy: f32) -> Self {
Self {
velocity: super::Velocity::new(dx, dy),
..Self::new()
}
}
pub(super) fn shove(&mut self, force: &dyn Force) {
let mut subject = Subject::new(self.velocity, self.mass, self.body.pos());
force.apply(&mut subject);
self.velocity = subject.velocity();
}
pub(super) fn under(mut self, force: &dyn Force, updates: usize) -> super::Velocity {
for _ in 0..updates {
self.shove(force);
}
self.velocity
}
pub(super) fn as_kinetic(&mut self) -> &mut dyn super::Kinetic {
self
}
}
#[cfg(test)]
impl super::Kinetic for Mob {
fn body(&self) -> &crate::Body {
&self.body
}
fn body_mut(&mut self) -> &mut crate::Body {
&mut self.body
}
fn velocity_mut(&mut self) -> &mut super::Velocity {
&mut self.velocity
}
fn contacts(&self) -> &super::Contacts {
&self.contacts
}
fn contacts_mut(&mut self) -> &mut super::Contacts {
&mut self.contacts
}
fn bounds(&self) -> super::Bounds {
super::Bounds::of(&self.body, 8, 8)
}
}
#[cfg(test)]
mod tests {
use super::{super::Velocity, *};
#[test]
fn unit_vectors_point_where_they_say_and_are_all_one_long() {
assert_eq!(Direction::Up.unit(), (0.0, -1.0));
assert_eq!(Direction::Down.unit(), (0.0, 1.0));
assert_eq!(Direction::Right.unit(), (1.0, 0.0));
assert_eq!(Direction::Left.unit(), (-1.0, 0.0));
let (x, y) = Direction::UpLeft.unit();
assert!(x < 0.0 && y < 0.0);
for direction in DIRECTIONS {
let (x, y) = direction.unit();
assert!(
(x * x + y * y - 1.0).abs() < 1e-6,
"{direction:?} is not a unit vector: ({x}, {y})"
);
}
}
#[test]
fn opposite_directions_have_opposite_units() {
for (there, back) in [
(Direction::Up, Direction::Down),
(Direction::Left, Direction::Right),
(Direction::UpLeft, Direction::DownRight),
(Direction::UpRight, Direction::DownLeft),
] {
let ((x, y), (bx, by)) = (there.unit(), back.unit());
assert_eq!((x, y), (-bx, -by), "{there:?} does not face {back:?}");
}
}
#[test]
fn a_mass_that_means_nothing_weighs_one() {
assert_eq!(weighed(4.0), 4.0);
for mass in [0.0, -3.0, f32::NAN, f32::NEG_INFINITY] {
assert_eq!(weighed(mass), 1.0, "a mass of {mass} was not read as 1.0");
}
}
#[test]
fn a_force_reaches_a_member_through_the_subject_alone() {
struct Updraft;
impl Force for Updraft {
fn apply(&self, subject: &mut Subject) {
let mass = subject.mass();
subject.velocity_mut().dy -= 1.0 / mass;
}
}
let force: &dyn Force = &Updraft;
let mut subject = Subject::new(Velocity::default(), 2.0, (8.0, 16.0));
force.apply(&mut subject);
assert_eq!(subject.velocity(), Velocity::new(0.0, -0.5));
assert_eq!(subject.pos(), (8.0, 16.0));
}
#[test]
fn a_force_that_reads_where_it_is_pushing_is_shown_the_exact_position() {
struct Draught;
impl Force for Draught {
fn apply(&self, subject: &mut Subject) {
let (x, _) = subject.pos();
subject.velocity_mut().dx += if x < 64.0 { 1.0 } else { -1.0 };
}
}
let mut near = Subject::new(Velocity::default(), 1.0, (10.5, 0.0));
let mut far = Subject::new(Velocity::default(), 1.0, (100.0, 0.0));
Draught.apply(&mut near);
Draught.apply(&mut far);
assert_eq!(near.velocity().dx, 1.0);
assert_eq!(far.velocity().dx, -1.0);
}
}