//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#[no_mangle]
#[allow(clippy::too_many_arguments)]
fn s5obj_init(
    argc: std::ffi::c_int,
    argv: *const *const std::ffi::c_char,
    inout_width: &mut std::ffi::c_int,
    inout_height: &mut std::ffi::c_int,
    inout_dt: &mut std::ffi::c_double,
) -> *mut std::ffi::c_void /* application */ {
    let args_utf8 = Vec::from_iter((0..argc).map(|a| {
        let c_ptr = unsafe { argv.offset(a as isize) };
        let c_str = unsafe { std::ffi::CStr::from_ptr(*c_ptr) };
        c_str.to_string_lossy()
    }));
    let args = Vec::from_iter(args_utf8.iter().map(|a| a.as_ref()));
    let mut w = *inout_width as usize;
    let mut h = *inout_height as usize;
    let mut dt = *inout_dt;
    match Application::new(args.as_slice(), &mut w, &mut h, &mut dt) {
        Ok(app) => {
            *inout_width = w as std::ffi::c_int;
            *inout_height = h as std::ffi::c_int;
            *inout_dt = dt as std::ffi::c_double;
            Box::into_raw(Box::new(app)) as *mut _
        }
        Err(e) => {
            eprintln!("ERROR: {}", e);
            std::ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(clippy::too_many_arguments)]
fn s5obj_update(
    c_evt: *const std::ffi::c_char,
    x: std::ffi::c_int,
    y: std::ffi::c_int,
    w: std::ffi::c_int,
    h: std::ffi::c_int,
    btn: std::ffi::c_int,
    c_key: *const std::ffi::c_char,
    c_screen: *mut std::ffi::c_char,
    c_app: *mut std::ffi::c_void,
) -> std::ffi::c_int /* -1: quit    0: go-on    1: redraw */ {
    let evt = unsafe { std::ffi::CStr::from_ptr(c_evt) }.to_string_lossy();
    let key = unsafe { std::ffi::CStr::from_ptr(c_key) }.to_string_lossy();
    let event = match evt.as_ref() {
        "Q" => Event::Quit,
        "T" => Event::Timeout,
        "C" => Event::Configure,
        "M" => Event::Motion,
        "KP" => Event::KeyPress(key.as_ref()),
        "KR" => Event::KeyRelease(key.as_ref()),
        "BP" => match btn {
            1 => Event::LeftPress,
            2 => Event::MiddlePress,
            3 => Event::RightPress,
            4 => Event::WheelUp,
            5 => Event::WheelDown,
            _ => unreachable!(),
        },
        "BR" => match btn {
            1 => Event::LeftRelease,
            2 => Event::MiddleRelease,
            3 => Event::RightRelease,
            _ => unreachable!(),
        },
        _ => unreachable!(),
    };
    let point = Point {
        x: x as f64,
        y: y as f64,
    };
    let mut screen = Screen::new(w as usize, h as usize, unsafe {
        std::slice::from_raw_parts_mut(
            c_screen as *mut Color,
            (w * h) as usize,
        )
    });
    let app = unsafe { &mut *(c_app as *mut Application) };
    let status = app.update(event, point, &mut screen).unwrap_or_else(|e| {
        eprintln!("ERROR: {}", e);
        UpdateStatus::Quit
    });
    match status {
        UpdateStatus::GoOn => 0,
        UpdateStatus::Redraw => 1,
        UpdateStatus::Quit => {
            // ensure deallocation
            let _owned = unsafe { Box::from_raw(app) };
            -1
        }
    }
}

#[derive(Debug, Clone)]
pub enum Event<'a> {
    Quit,
    Timeout,
    Configure,
    Motion,
    KeyPress(&'a str),
    KeyRelease(&'a str),
    LeftPress,
    LeftRelease,
    MiddlePress,
    MiddleRelease,
    RightPress,
    RightRelease,
    WheelUp,
    WheelDown,
}

#[derive(Debug, Clone)]
pub enum UpdateStatus {
    GoOn,
    Redraw,
    Quit,
}

#[derive(Debug, Default, Clone)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

#[derive(Debug, Default, Clone)]
pub struct Rect {
    pub w: f64,
    pub h: f64,
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

pub struct Application {
    window: crate::ApplicationWindow,
    first_time: std::time::Instant,
    last_time: std::time::Instant,
    last_five_seconds: isize,
}

impl Application {
    pub fn new(
        args: &[&str],
        inout_width: &mut usize,
        inout_height: &mut usize,
        inout_dt: &mut f64,
    ) -> Result<Self, Box<dyn std::error::Error>> {
        let window = crate::create_window(args)?;
        *inout_width = window.size().w as usize;
        *inout_height = window.size().h as usize;
        *inout_dt = 1.0 / 30.0;
        let now = std::time::Instant::now();
        Ok(Self {
            window,
            first_time: now,
            last_time: now,
            last_five_seconds: 0,
        })
    }

    pub fn update(
        &mut self,
        event: Event,
        point: Point,
        screen: &mut Screen,
    ) -> Result<UpdateStatus, Box<dyn std::error::Error>> {
        let status = match event {
            Event::Quit => UpdateStatus::Quit,
            Event::KeyRelease("Escape") => UpdateStatus::Quit,
            Event::Configure => {
                self.window.resize(Rect {
                    w: screen.width() as f64,
                    h: screen.height() as f64,
                });
                UpdateStatus::Redraw
            }
            Event::Timeout => {
                let now = std::time::Instant::now();
                let second = (now - self.first_time).as_secs_f64() as isize;
                if second >= self.last_five_seconds + 5 {
                    println!("{} seconds elapsed", second);
                    self.last_five_seconds = second;
                }
                let dt = (now - self.last_time).as_secs_f64();
                self.last_time = now;
                if self.window.animate(dt) {
                    UpdateStatus::Redraw
                } else {
                    UpdateStatus::Quit
                }
            }
            Event::LeftPress => {
                self.window.click(point);
                UpdateStatus::Redraw
            }
            _ => UpdateStatus::GoOn,
        };
        if let UpdateStatus::Redraw = status {
            self.window.redraw(screen);
        }
        Ok(status)
    }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

impl Color {
    pub const BLACK: Self = Self { r: 0, g: 0, b: 0 };
    pub const WHITE: Self = Self {
        r: 255,
        g: 255,
        b: 255,
    };
    pub const RED: Self = Self { r: 255, g: 0, b: 0 };
    pub const GREEN: Self = Self { r: 0, g: 255, b: 0 };
    pub const BLUE: Self = Self { r: 0, g: 0, b: 255 };
    pub const YELLOW: Self = Self {
        r: 255,
        g: 255,
        b: 0,
    };
    pub const CYAN: Self = Self {
        r: 0,
        g: 255,
        b: 255,
    };
    pub const PURPLE: Self = Self {
        r: 255,
        g: 0,
        b: 255,
    };

    pub fn new(
        r: u8,
        g: u8,
        b: u8,
    ) -> Self {
        Self { r, g, b }
    }

    pub fn from_grey(grey: u8) -> Self {
        Self::new(grey, grey, grey)
    }

    pub fn grey(&self) -> u8 {
        let r = self.r as u32;
        let g = self.g as u32;
        let b = self.b as u32;
        ((r + g + b) / 3) as u8
    }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#[derive(Debug)]
pub struct Screen<'a> {
    width: usize,
    height: usize,
    pixels: &'a mut [Color],
}

impl<'a> Screen<'a> {
    pub fn new(
        width: usize,
        height: usize,
        pixels: &'a mut [Color],
    ) -> Self {
        assert_eq!(width * height, pixels.len());
        Self {
            width,
            height,
            pixels,
        }
    }

    pub fn width(&self) -> usize {
        self.width
    }

    pub fn height(&self) -> usize {
        self.height
    }

    pub fn coords(
        &self,
        position: &Point,
    ) -> Option<(usize, usize)> {
        if position.x >= 0.0 && position.y >= 0.0 {
            let x = position.x as usize;
            let y = position.y as usize;
            if x < self.width && y < self.height {
                return Some((x, y));
            }
        }
        None
    }

    /// # Safety
    /// No bound check.
    pub unsafe fn get_unchecked(
        &self,
        x: usize,
        y: usize,
    ) -> Color {
        *unsafe { self.pixels.get_unchecked(y * self.width + x) }
    }

    pub fn get(
        &self,
        position: &Point,
    ) -> Option<Color> {
        if let Some((x, y)) = self.coords(position) {
            Some(unsafe { self.get_unchecked(x, y) })
        } else {
            None
        }
    }

    pub fn clear(
        &mut self,
        color: Color,
    ) {
        for p in self.pixels.iter_mut() {
            *p = color;
        }
    }

    /// # Safety
    /// No bound check.
    pub unsafe fn plot_unchecked(
        &mut self,
        x: usize,
        y: usize,
        color: Color,
    ) {
        *unsafe { self.pixels.get_unchecked_mut(y * self.width + x) } = color;
    }

    pub fn plot(
        &mut self,
        position: &Point,
        color: Color,
    ) {
        if let Some((x, y)) = self.coords(position) {
            unsafe { self.plot_unchecked(x, y, color) };
        }
    }

    /// # Safety
    /// No bound check.
    pub unsafe fn h_line_unchecked(
        &mut self,
        x: usize,
        y: usize,
        length: usize,
        color: Color,
    ) {
        for dx in 0..=length {
            unsafe { self.plot_unchecked(x + dx, y, color) };
        }
    }

    pub fn h_line(
        &mut self,
        origin: &Point,
        length: f64,
        color: Color,
    ) {
        if origin.y >= 0.0 {
            let y = origin.y as usize;
            if y < self.height {
                let x = origin.x as usize;
                let x1 = x.max(0);
                let x2 = (x + length as usize).min(self.width);
                unsafe { self.h_line_unchecked(x1, y, x2 - x1, color) };
            }
        }
    }

    /// # Safety
    /// No bound check.
    pub unsafe fn v_line_unchecked(
        &mut self,
        x: usize,
        y: usize,
        length: usize,
        color: Color,
    ) {
        for dy in 0..=length {
            unsafe { self.plot_unchecked(x, y + dy, color) };
        }
    }

    pub fn v_line(
        &mut self,
        origin: &Point,
        length: f64,
        color: Color,
    ) {
        if origin.x >= 0.0 {
            let x = origin.x as usize;
            if x < self.width {
                let y = origin.y as usize;
                let y1 = y.max(0);
                let y2 = (y + length as usize).min(self.height);
                unsafe { self.v_line_unchecked(x, y1, y2 - y1, color) };
            }
        }
    }

    pub fn line(
        &mut self,
        p1: &Point,
        p2: &Point,
        color: Color,
    ) {
        let dx = p2.x - p1.x;
        let dy = p2.y - p1.y;
        if dx.abs() > dy.abs() {
            let slope = dy / dx;
            let (px1, px2, py1) = if dx >= 0.0 {
                (p1.x as usize, p2.x as usize, p1.y)
            } else {
                (p2.x as usize, p1.x as usize, p2.y)
            };
            for px in px1..=px2 {
                let x = (px - px1) as f64;
                let y = x * slope;
                let py = py1 + y;
                self.plot(
                    &Point {
                        x: px as f64,
                        y: py,
                    },
                    color,
                );
            }
        } else {
            let slope = dx / dy;
            let (py1, py2, px1) = if dy >= 0.0 {
                (p1.y as usize, p2.y as usize, p1.x)
            } else {
                (p2.y as usize, p1.y as usize, p2.x)
            };
            for py in py1..=py2 {
                let y = (py - py1) as f64;
                let x = y * slope;
                let px = px1 + x;
                self.plot(
                    &Point {
                        x: px,
                        y: py as f64,
                    },
                    color,
                );
            }
        }
    }

    fn rotated(
        &mut self,
        center: &Point,
        size: &Rect,
        angle: f64,
        fnct: impl Fn(f64, f64) -> Option<Color>,
    ) {
        let w = size.w * 0.5;
        let h = size.h * 0.5;
        let (sa, ca) = angle.sin_cos();
        let x = [
            center.x + w * ca - h * sa,
            center.x - w * ca - h * sa,
            center.x - w * ca + h * sa,
            center.x + w * ca + h * sa,
        ];
        let y = [
            center.y + w * sa + h * ca,
            center.y - w * sa + h * ca,
            center.y - w * sa - h * ca,
            center.y + w * sa - h * ca,
        ];
        let x_min = x.into_iter().reduce(f64::min).unwrap().max(0.0) as usize;
        let y_min = y.into_iter().reduce(f64::min).unwrap().max(0.0) as usize;
        let x_max = x
            .into_iter()
            .reduce(f64::max)
            .unwrap()
            .min(self.width as f64 - 1.0) as usize;
        let y_max = y
            .into_iter()
            .reduce(f64::max)
            .unwrap()
            .min(self.height as f64 - 1.0) as usize;
        for y in y_min..=y_max {
            for x in x_min..=x_max {
                let dx = x as f64 - center.x;
                let dy = y as f64 - center.y;
                let tx = dy * sa + dx * ca;
                let ty = dy * ca - dx * sa;
                if let Some(color) = fnct(tx, ty) {
                    unsafe { self.plot_unchecked(x, y, color) };
                }
            }
        }
    }

    pub fn rectangle(
        &mut self,
        center: &Point,
        size: &Rect,
        inner_size: Option<&Rect>,
        angle: f64,
        color: Color,
    ) {
        let w = size.w * 0.5;
        let h = size.h * 0.5;
        let (iw, ih) = if let Some(inner) = inner_size {
            (inner.w * 0.5, inner.h * 0.5)
        } else {
            (0.0, 0.0)
        };
        self.rotated(center, size, angle, |x, y| {
            if (x >= -w && x <= w && y >= -h && y <= h)
                && (x <= -iw || x >= iw || y <= -ih || y >= ih)
            {
                Some(color)
            } else {
                None
            }
        });
    }

    pub fn ellipse(
        &mut self,
        center: &Point,
        size: &Rect,
        inner_size: Option<&Rect>,
        angle: f64,
        color: Color,
    ) {
        let w = size.w * 0.5;
        let h = size.h * 0.5;
        let (iw, ih) = if let Some(inner) = inner_size {
            (inner.w * 0.5, inner.h * 0.5)
        } else {
            (0.0, 0.0)
        };
        let w2 = w * w;
        let h2 = h * h;
        let w2h2 = w2 * h2;
        let iw2 = iw * iw;
        let ih2 = ih * ih;
        let iw2ih2 = iw2 * ih2;
        self.rotated(center, size, angle, |x, y| {
            let x2 = x * x;
            let y2 = y * y;
            if (x2 * h2 + y2 * w2) <= w2h2 && (x2 * ih2 + y2 * iw2) >= iw2ih2
            {
                Some(color)
            } else {
                None
            }
        });
    }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#[derive(Debug, Default, Clone)]
pub struct Rng {
    seed: u64,
}

impl Rng {
    pub fn new() -> Self {
        let now = (std::time::UNIX_EPOCH.elapsed().unwrap().as_millis() >> 8)
            as u32;
        let addr = std::ptr::addr_of!(now) as usize as u32;
        Self::from_seed(((now as u64) << 32) | addr as u64)
    }

    pub fn from_seed(seed: u64) -> Self {
        Self { seed }
    }

    pub fn seed(&self) -> u64 {
        self.seed
    }

    pub fn number<T: RngNumber>(&mut self) -> T {
        T::number(self)
    }

    pub fn sign<T: RngSign>(&mut self) -> T {
        T::sign(self)
    }

    pub fn range<T: RngRange>(
        &mut self,
        low: T,
        high: T,
    ) -> T {
        T::range(self, low, high)
    }

    // random value with mean 0.0 and standard deviation 1.0
    pub fn normal<T: RngNormal>(&mut self) -> T {
        T::normal(self)
    }

    pub fn shuffle<T>(
        &mut self,
        data: &mut [T],
    ) {
        let length = data.len();
        for dst in 0..length {
            let src = self.range(dst, length);
            data.swap(dst, src);
        }
    }

    // Adapted from MWC generator described in George Marsaglia's post
    // ``Random numbers for C: End, at last?'' on sci.stat.math,
    // sci.math, sci.math.num-analysis, sci.crypt, sci.physics.research,
    // comp.os.msdos.djgpp (Thu, 21 Jan 1999 03:08:52 GMT)
    fn advance(&mut self) -> u32 {
        const MASK_32: u64 = (1 << 32) - 1;
        const MASK_16: u64 = (1 << 16) - 1;
        let mut s0 = self.seed >> 32;
        let mut s1 = self.seed & MASK_32;
        s0 = (36969 * (s0 & MASK_16) + (s0 >> 16)) & MASK_32;
        s1 = (18000 * (s1 & MASK_16) + (s1 >> 16)) & MASK_32;
        self.seed = (s0 << 32) | s1;
        ((s0 << 16) + s1) as u32
    }
}

pub trait RngNumber {
    fn number(rng: &mut Rng) -> Self;
}

impl RngNumber for bool {
    fn number(rng: &mut Rng) -> Self {
        rng.advance() & 1 != 0
    }
}

macro_rules! impl_rng_number_small {
    ($( $int:ident ),+) => {$(
        impl RngNumber for $int {
            fn number(rng: &mut Rng) -> Self {
                rng.advance() as Self
            }
        }
    )+};
}
impl_rng_number_small! { i8, u8, i16, u16, i32, u32 }

impl RngNumber for u64 {
    fn number(rng: &mut Rng) -> Self {
        ((rng.advance() as Self) << 32) | rng.advance() as Self
    }
}

impl RngNumber for u128 {
    fn number(rng: &mut Rng) -> Self {
        ((u64::number(rng) as Self) << 64) | u64::number(rng) as Self
    }
}

impl RngNumber for usize {
    fn number(rng: &mut Rng) -> Self {
        match core::mem::size_of::<Self>() {
            1 => u8::number(rng) as Self,
            2 => u16::number(rng) as Self,
            4 => u32::number(rng) as Self,
            8 => u64::number(rng) as Self,
            16 => u128::number(rng) as Self,
            _ => unreachable!(),
        }
    }
}

macro_rules! impl_rng_number_large_signed {
    ($( $s_int:ident | $u_int:ident ),+) => {$(
        impl RngNumber for $s_int {
            fn number(rng: &mut Rng) -> Self {
                $u_int::number(rng) as Self
            }
        }
    )+};
}
impl_rng_number_large_signed! { i64|u64, i128|u128, isize|usize }

macro_rules! impl_rng_number_real {
    ($( $real:ident | $u_int:ident ),+) => {$(
        impl RngNumber for $real {
            // nb: 32 bits will overflow a 24-bit significand for SP-IEEE754
            // nb: 64 bits will overflow a 53-bit significand for DP-IEEE754
            fn number(rng: &mut Rng) -> Self {
                const BITS: $u_int = $real::MANTISSA_DIGITS as $u_int;
                const MASK: $u_int = (1 << BITS) - 1;
                const DIV: $real = 1.0 / (MASK + 1) as $real;
                ($u_int::number(rng) & MASK) as Self * DIV
            }
        }
    )+};
}
impl_rng_number_real! { f32|u32, f64|u64 }

pub trait RngSign {
    fn sign(rng: &mut Rng) -> Self;
}

macro_rules! impl_rng_sign {
    ($( $s_type:ident ),+) => {$(
        impl RngSign for $s_type {
            fn sign(rng: &mut Rng) -> Self {
                if bool::number(rng) {
                    1 as Self
                } else {
                    -1 as Self
                }
            }
        }
    )+};
}
impl_rng_sign! { i8, i16, i32, i64, i128, isize, f32, f64 }

pub trait RngRange {
    fn range(
        rng: &mut Rng,
        low: Self,
        high: Self,
    ) -> Self;
}

macro_rules! impl_rng_range_int {
    ($( $u_int:ident | $s_int:ident ),+) => {$(
        impl RngRange for $u_int {
            fn range(
                rng: &mut Rng,
                low: Self,
                high: Self,
            ) -> Self {
                low + rng.number::<Self>() % (high - low)
            }
        }
        impl RngRange for $s_int {
            fn range(
                rng: &mut Rng,
                low: Self,
                high: Self,
            ) -> Self {
                type U = $u_int;
                let (lu, hu) = (low as U, high as U);
                U::range(rng, 0, hu.wrapping_sub(lu)).wrapping_add(lu)
                    as Self
            }
        }
    )+};
}
impl_rng_range_int! {
    u8|i8, u16|i16, u32|i32, u64|i64, u128|i128, usize|isize
}

macro_rules! impl_rng_range_real {
    ($( $real:ident ),+) => {$(
        impl RngRange for $real {
            fn range(
                rng: &mut Rng,
                low: Self,
                high: Self,
            ) -> Self {
                low + rng.number::<Self>() * (high - low)
            }
        }
    )+};
}
impl_rng_range_real! { f32, f64 }

pub trait RngNormal {
    fn normal(rng: &mut Rng) -> Self;
}

macro_rules! impl_rng_normal {
    ($( $real:ident ),+) => {$(
        impl RngNormal for $real {
            fn normal(rng: &mut Rng) -> Self {
                let r1 = core::$real::consts::PI * 2.0 * rng.number::<Self>();
                let r2 = 1.0 - rng.number::<Self>();
                r1.cos() * (-2.0 * r2.ln()).sqrt() // Box-Muller method
            }
        }
    )+};
}
impl_rng_normal! { f32, f64 }

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#[derive(Debug, Default, Clone)]
pub struct KineticState {
    pub x: f64,
    pub y: f64,
    pub angle: f64,
    pub x_speed: f64,
    pub y_speed: f64,
    pub angular_speed: f64,
}

impl KineticState {
    pub fn integrate(
        &mut self,
        dt: f64,
    ) {
        self.x += self.x_speed * dt;
        self.y += self.y_speed * dt;
        self.angle = (self.angle + self.angular_speed * dt)
            .rem_euclid(2.0 * std::f64::consts::PI);
    }

    pub fn distance(
        &self,
        other: &Self,
    ) -> f64 {
        let dx = other.x - self.x;
        let dy = other.y - self.y;
        (dx * dx + dy * dy).sqrt()
    }

    pub fn repulse(
        &mut self,
        other: &mut Self,
        ref_distance: f64,
        coef: f64,
        dt: f64,
    ) {
        let dx = other.x - self.x;
        let dy = other.y - self.y;
        let d2 = dx * dx + dy * dy;
        if d2 > 0.0 {
            let d = d2.sqrt();
            let influence = ref_distance / d.max(ref_distance * 0.2);
            let f = coef * influence * influence * dt / d;
            self.x_speed -= dx * f;
            self.y_speed -= dy * f;
            other.x_speed += dx * f;
            other.y_speed += dy * f;
        }
    }

    pub fn friction(
        &mut self,
        coef: f64,
        dt: f64,
    ) {
        self.x_speed -= self.x_speed * (coef * dt);
        self.y_speed -= self.y_speed * (coef * dt);
    }

    pub fn bounce(
        &mut self,
        x_normal: f64,
        y_normal: f64,
        elasticity: f64,
    ) {
        let d2 = x_normal * x_normal + y_normal * y_normal;
        if d2 > 0.0 {
            let norm = -1.0 / d2.sqrt();
            let nx = norm * x_normal;
            let ny = norm * y_normal;
            let s_n =
                (1.0 + elasticity) * (self.x_speed * nx + self.y_speed * ny);
            if s_n > 0.0 {
                self.x_speed -= s_n * nx;
                self.y_speed -= s_n * ny;
            }
        }
    }

    pub fn collide(
        &mut self,
        other: &mut Self,
        mass1: f64,
        mass2: f64,
        elasticity: f64,
    ) {
        let dx = other.x - self.x;
        let dy = other.y - self.y;
        let d2 = dx * dx + dy * dy;
        if d2 > 0.0 {
            let norm = 1.0 / d2.sqrt();
            let nx = norm * dx;
            let ny = norm * dy;
            let s_n1 = self.x_speed * nx + self.y_speed * ny;
            let s_n2 = other.x_speed * nx + other.y_speed * ny;
            if s_n1 > s_n2 {
                // not separating
                let next_s_n1 = (elasticity * mass2 * (s_n2 - s_n1)
                    + mass1 * s_n1
                    + mass2 * s_n2)
                    / (mass1 + mass2);
                let next_s_n2 = (elasticity * mass1 * (s_n1 - s_n2)
                    + mass1 * s_n1
                    + mass2 * s_n2)
                    / (mass1 + mass2);
                self.x_speed += (next_s_n1 - s_n1) * nx;
                self.y_speed += (next_s_n1 - s_n1) * ny;
                other.x_speed += (next_s_n2 - s_n2) * nx;
                other.y_speed += (next_s_n2 - s_n2) * ny;
            }
        }
    }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
