Skip to content

Instantly share code, notes, and snippets.

@Staninna
Created July 5, 2025 12:29
Show Gist options
  • Save Staninna/9f13b1c503fe7edc3de3dfc360475884 to your computer and use it in GitHub Desktop.
Save Staninna/9f13b1c503fe7edc3de3dfc360475884 to your computer and use it in GitHub Desktop.

==== main.rs ====

mod functions;
mod interpolated;
mod vec2;

use crate::{functions::TransitionFunction, interpolated::Interpolated, vec2::Vec2};
use std::time::Duration;

fn main() {
    let init = Vec2::new(-35.0, 10.0);
    let end = Vec2::new(100.0, 50.0);
    let dur = 1.5;
    let trans = TransitionFunction::EaseInOutExponential;

    let mut pos = Interpolated::new(init);
    pos.set_duration(Duration::from_secs_f32(dur));
    pos.transition = trans;
    pos.set(end);

    while !pos.is_finished() {
        let v = pos.value();
        println!("position = {:+.1?}", v);
        std::thread::sleep(Duration::from_millis(100));
    }
}

==== interpolated.rs ====

use crate::functions::TransitionFunction;
use std::ops::{Add, Mul, Sub};
use std::time::{Duration, Instant};

pub struct Interpolated<T> {
    start: T,
    end: T,
    start_time: Instant,
    pub speed: f32,
    pub transition: TransitionFunction,
}

impl<T> Interpolated<T>
where
    T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f32, Output = T>,
{
    pub fn new(initial: T) -> Self {
        let now = Instant::now();
        Interpolated {
            start: initial,
            end: initial,
            start_time: now,
            speed: 1.0,
            transition: TransitionFunction::Linear,
        }
    }

    pub fn set_duration(&mut self, dur: Duration) {
        self.speed = 1.0 / dur.as_secs_f32();
    }

    pub fn set(&mut self, value: T) {
        self.start = self.value();
        self.end = value;
        self.start_time = Instant::now();
    }

    pub fn value(&self) -> T {
        let elapsed_secs = (Instant::now() - self.start_time).as_secs_f32() * self.speed;
        if elapsed_secs >= 1.0 {
            return self.end;
        }
        let r = self.transition.ratio(elapsed_secs);
        self.start + (self.end - self.start) * r
    }

    pub fn is_finished(&self) -> bool {
        let elapsed = (Instant::now() - self.start_time).as_secs_f32() * self.speed;
        elapsed >= 1.0
    }
}

==== vec2.rs ====

use std::ops::{Add, Mul, Sub};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Vec2<T> {
    pub x: T,
    pub y: T,
}

impl<T> Vec2<T> {
    pub fn new(x: T, y: T) -> Self {
        Self { x, y }
    }

    pub fn zero() -> Self
    where
        T: Default + Copy,
    {
        Self::new(T::default(), T::default())
    }
}

impl<T> Add for Vec2<T>
where
    T: Add<Output = T>,
{
    type Output = Vec2<T>;

    fn add(self, rhs: Vec2<T>) -> Vec2<T> {
        Vec2 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

impl<T> Sub for Vec2<T>
where
    T: Sub<Output = T>,
{
    type Output = Vec2<T>;

    fn sub(self, rhs: Vec2<T>) -> Vec2<T> {
        Vec2 {
            x: self.x - rhs.x,
            y: self.y - rhs.y,
        }
    }
}

impl<T> Mul<T> for Vec2<T>
where
    T: Mul<Output = T> + Copy,
{
    type Output = Vec2<T>;

    fn mul(self, scalar: T) -> Vec2<T> {
        Vec2 {
            x: self.x * scalar,
            y: self.y * scalar,
        }
    }
}

==== functions.rs ====

use std::f32::consts::PI;

fn ease_none(_: f32) -> f32 {
    1.0
}

fn linear(t: f32) -> f32 {
    t
}

fn expo_in_out(t: f32) -> f32 {
    if t < 0.5 {
        0.5 * (20.0 * t - 10.0).exp2()
    } else {
        1.0 - 0.5 * (10.0 - 20.0 * t).exp2()
    }
}

fn ease_out_back(t: f32) -> f32 {
    const C1: f32 = 1.70158;
    const C3: f32 = C1 + 1.0;
    1.0 + C3 * (t - 1.0).powi(3) + C1 * (t - 1.0).powi(2)
}

fn ease_in_back(t: f32) -> f32 {
    const C1: f32 = 1.70158;
    const C3: f32 = C1 + 1.0;
    C3 * t * t * t - C1 * t * t
}

fn ease_out_elastic(t: f32) -> f32 {
    const TWO_PI: f32 = 2.0 * PI;
    const C4: f32 = TWO_PI / 3.0;
    if t == 0.0 {
        return 0.0;
    }
    if t == 1.0 {
        return 1.0;
    }
    (-10.0 * t).exp2() * ((t * 10.0 - 0.75) * C4).sin() + 1.0
}

const EASINGS: [fn(f32) -> f32; 6] = [
    ease_none,
    linear,
    expo_in_out,
    ease_out_back,
    ease_in_back,
    ease_out_elastic,
];

#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum TransitionFunction {
    None = 0,
    Linear = 1,
    EaseInOutExponential = 2,
    EaseOutBack = 3,
    EaseInBack = 4,
    EaseOutElastic = 5,
}

impl TransitionFunction {
    #[inline]
    pub fn ratio(self, t: f32) -> f32 {
        let t = if t < 0.0 {
            0.0
        } else if t > 1.0 {
            1.0
        } else {
            t
        };
        EASINGS[self as u8 as usize](t)
    }
}

Output:

position = Vec2 { x: -34.9, y: +10.0 }
position = Vec2 { x: -34.8, y: +10.0 }
position = Vec2 { x: -34.6, y: +10.1 }
position = Vec2 { x: -33.9, y: +10.3 }
position = Vec2 { x: -32.3, y: +10.8 }
position = Vec2 { x: -28.2, y: +12.0 }
position = Vec2 { x: -17.8, y: +15.1 }
position = Vec2 { x: +8.4, y: +22.9 }
position = Vec2 { x: +58.5, y: +37.7 }
position = Vec2 { x: +83.6, y: +45.1 }
position = Vec2 { x: +93.5, y: +48.1 }
position = Vec2 { x: +97.4, y: +49.2 }
position = Vec2 { x: +99.0, y: +49.7 }
position = Vec2 { x: +99.6, y: +49.9 }
position = Vec2 { x: +99.8, y: +50.0 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment