desk-physics 0.0.0

The application platform for your cyberpunk desk
Documentation
use std::f32::EPSILON;

use bevy_ecs::prelude::Component;
use bevy_math::Vec2;

use crate::Velocity;

#[derive(Component, Debug, Clone, PartialEq)]
pub struct Follow<T> {
    pub target: T,
    pub parameters: FollowParams,
}

#[derive(Debug, Clone, PartialEq)]
pub struct FollowParams {
    pub position_offset: Vec2,
    pub ignore_area_size: f32,
    pub velocity_coefficient: f32,
    pub velocity_power: f32,
    pub velocity_max: f32,
    pub velocity_offset: f32,
}

impl Default for FollowParams {
    fn default() -> Self {
        Self {
            position_offset: Default::default(),
            ignore_area_size: 0.0,
            velocity_coefficient: 1.0,
            velocity_power: 1.0,
            velocity_max: 1000.0,
            velocity_offset: 0.0,
        }
    }
}

impl FollowParams {
    pub fn follow_vector(&self, me: &Vec2, target: &Vec2) -> Velocity {
        let vec = Vec2::new(target.x - me.x, target.y - me.y) - self.position_offset;
        let length = vec.length();
        if length <= EPSILON {
            return Vec2::ZERO.into();
        }
        let velocity = (length + self.velocity_offset - self.ignore_area_size)
            .max(0.0)
            .powf(self.velocity_power)
            * self.velocity_coefficient;
        let vec = vec.normalize() * velocity.min(self.velocity_max);
        vec.into()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn default() {
        let params = FollowParams::default();
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.3, 2.), &Vec2::new(1.0, 0.5))
                .0,
            Vec2::new(0.7, -1.5),
        );
    }

    #[test]
    fn avoid_nan_zero() {
        let params = FollowParams::default();
        assert_eq!(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(0.0, 0.0))
                .0,
            Vec2::ZERO,
        );
    }

    #[test]
    fn offset() {
        let params = FollowParams {
            position_offset: Vec2::new(1.0, 2.0),
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.4, 2.), &Vec2::new(2.0, 3.5))
                .0,
            Vec2::new(0.6, -0.5),
        );
    }

    #[test]
    fn power() {
        let params = FollowParams {
            velocity_power: 1.0,
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
                .0,
            Vec2::new(3.0, 4.0),
        );

        let params = FollowParams {
            velocity_power: 2.0,
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
                .0,
            Vec2::new(15.0, 20.0),
        );
    }

    #[test]
    fn ignore_velocity() {
        let params = FollowParams {
            ignore_area_size: 5.0,
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
                .0,
            Vec2::new(0.0, 0.0),
        );
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.3, 4.4))
                .0,
            Vec2::new(0.3, 0.4),
        );
    }

    #[test]
    fn ignore_velocity_avoids_negative_velocity() {
        let params = FollowParams {
            ignore_area_size: 10.0,
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
                .0,
            Vec2::ZERO,
        );
    }

    #[test]
    fn max_velocity() {
        let params = FollowParams {
            velocity_max: 5.0,
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
                .0,
            Vec2::new(3.0, 4.0),
        );
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.03, 4.04))
                .0,
            Vec2::new(3.0, 4.0),
        );
    }

    #[test]
    fn velocty_offset() {
        let params = FollowParams {
            velocity_offset: -1.0,
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
                .0,
            Vec2::new(3.0 * 4.0 / 5.0, 4.0 * 4.0 / 5.0),
        );
    }

    #[test]
    fn velocity_offset_avoids_negative_velocity() {
        let params = FollowParams {
            velocity_offset: -10.0,
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
                .0,
            Vec2::ZERO,
        );
    }

    #[test]
    fn velocty_coefficient() {
        let params = FollowParams {
            velocity_coefficient: 2.0,
            ..Default::default()
        };
        assert_nearly_eq(
            params
                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(1.0, 1.0))
                .0,
            Vec2::new(2.0, 2.0),
        );
    }

    fn assert_nearly_eq(vec1: Vec2, vec2: Vec2) {
        let diff = vec1 - vec2;
        if diff.x.abs() > 0.0001 || diff.y.abs() > 0.0001 {
            assert_eq!(vec1, vec2);
        }
    }

    #[test]
    #[should_panic]
    fn assert_nearly_eq_fail() {
        assert_nearly_eq(Vec2::new(1.0, 1.0), Vec2::new(1.0, 1.0 + 0.001));
    }

    #[test]
    #[should_panic]
    fn assert_nearly_eq_fail2() {
        assert_nearly_eq(Vec2::new(1.0, 1.0), Vec2::new(1.0 + 0.001, 1.0));
    }
}