twgpu 0.4.1

Render Teeworlds and DDNet maps
Documentation
use fixed::types::I22F10;
use std::f32::consts;
use twmap::{CurveKind, Env, Envelope, Position, Volume};
use vek::az::{Az, UnwrappedAs};
use vek::Rgba;

trait IntoEnvValue {
    fn into_env_val(self) -> Rgba<f32>;
}

impl IntoEnvValue for Volume {
    fn into_env_val(self) -> Rgba<f32> {
        Rgba::new(self.0.az(), 0., 0., 0.)
    }
}

impl IntoEnvValue for Position {
    fn into_env_val(self) -> Rgba<f32> {
        Rgba::new(
            self.offset.x.az(),
            self.offset.y.az(),
            self.rotation.az::<f32>() / 180. * consts::PI,
            0.,
        )
    }
}

impl IntoEnvValue for Rgba<I22F10> {
    fn into_env_val(self) -> Rgba<f32> {
        self.az()
    }
}

pub fn interpolate<T>(frac: f32, curve: CurveKind<T>) -> f32 {
    use CurveKind::*;
    match curve {
        Step => 0.,
        Linear => frac,
        Slow => frac.powi(3),
        Fast => 1. - (1. - frac).powi(3),
        // TODO: Implement Bezier curves
        Smooth | Bezier(_) => 3. * frac.powi(2) - 2. * frac.powi(3),
        Unknown(_) => 0.5,
    }
}

pub trait EnvelopeSampling {
    fn sample(&self, client_micros: i64, server_micros: i64, offset: i32) -> Rgba<f32>;
}

impl<T: Copy + IntoEnvValue> EnvelopeSampling for Env<T> {
    fn sample(&self, client_micros: i64, server_micros: i64, offset: i32) -> Rgba<f32> {
        match self.points.as_slice() {
            [] => return Rgba::zero(),
            [one] => return one.content.into_env_val(),
            _ => {}
        }
        let mut micros: i64 = match self.synchronized {
            true => server_micros,
            false => client_micros,
        };
        micros += i64::from(offset) * 1000;
        let modulo = self.points.last().unwrap().time;
        if modulo != 0 {
            micros %= i64::from(modulo) * 1000;
        }
        let millis: i32 = (micros / 1000).unwrapped_as();
        if self.points[0].time > millis {
            return self.points.last().unwrap().content.into_env_val();
        }
        let interpolation_points = self
            .points
            .windows(2)
            .find(|tuple| tuple[0].time <= millis && tuple[1].time >= millis)
            .unwrap();
        let left_micros = i64::from(interpolation_points[0].time) * 1000;
        let right_micros = i64::from(interpolation_points[1].time) * 1000;
        let time_span_micros = right_micros - left_micros;
        let time_span_moment = micros - left_micros;
        let frac = time_span_moment as f32 / time_span_micros as f32;

        let left = interpolation_points[0].content.into_env_val();
        let right = interpolation_points[1].content.into_env_val();
        left + (right - left) * interpolate(frac, interpolation_points[0].curve)
    }
}

impl EnvelopeSampling for Envelope {
    fn sample(&self, client_micros: i64, server_micros: i64, offset: i32) -> Rgba<f32> {
        use Envelope::*;
        match self {
            Position(env) => env.sample(client_micros, server_micros, offset),
            Color(env) => env.sample(client_micros, server_micros, offset),
            Sound(env) => env.sample(client_micros, server_micros, offset),
        }
    }
}