twgpu 0.4.1

Render Teeworlds and DDNet maps
Documentation
use std::f32::consts::PI;

use rand_core::{RngCore, SeedableRng};
use rand_pcg::Lcg64Xsh32;
use vek::{Aabr, Clamp, Vec2};
use wgpu::RenderPass;

use crate::TwRenderPass;

#[derive(Debug, Copy, Clone, Default)]
pub struct Clock {
    start: f64,
    end: f64,
    frac: f32,
    f64_tick: f64,
}

impl Clock {
    pub fn new(tick: i32) -> Self {
        Self {
            start: tick as f64 - 1.,
            end: tick as f64,
            frac: 1.,
            f64_tick: tick as f64,
        }
    }

    pub fn need_next_tick(&self) -> bool {
        self.current_tick() > self.end
    }

    /// Returns `true`, if the new tick still lies in the past.
    /// In that case, we need to advance to the next tick.
    pub fn next_tick(&mut self, tick: i32) -> bool {
        self.start = self.end;
        self.end = tick as f64;
        self.update(self.current_tick())
    }

    /// Returns `true`, if we should advance to the next tick
    pub fn update(&mut self, f_tick: f64) -> bool {
        let range = self.end - self.start;
        let offset_tick = f_tick - self.start;
        self.frac = (offset_tick / range) as f32;
        self.f64_tick = f_tick;
        f_tick > self.end
    }

    pub fn current_tick(&self) -> f64 {
        self.f64_tick
    }

    pub fn tick_rate(&self) -> f64 {
        50.
    }

    /// Returns current time in seconds
    pub fn current_time(&self) -> f64 {
        self.f64_tick / self.tick_rate()
    }

    pub fn frac(&self) -> f32 {
        self.frac
    }

    pub fn start_tick(&self) -> i32 {
        self.start as i32
    }

    pub fn end_tick(&self) -> i32 {
        self.end as i32
    }
}

pub struct Rng {
    rng: Lcg64Xsh32,
}

impl Rng {
    pub fn new(seed: u32) -> Self {
        let mut seed_bytes = [0; 16];
        seed_bytes[0..4].copy_from_slice(&seed.to_le_bytes());
        Self {
            rng: Lcg64Xsh32::from_seed(seed_bytes),
        }
    }

    pub fn u32(&mut self) -> u32 {
        self.rng.next_u32()
    }

    pub fn f32(&mut self) -> f32 {
        self.u32() as f32 / u32::MAX as f32
    }

    pub fn f32_max(&mut self, max: f32) -> f32 {
        self.f32() * max
    }

    pub fn f32_range(&mut self, min: f32, max: f32) -> f32 {
        let delta = max - min;
        min + delta * self.f32()
    }

    pub fn angle(&mut self) -> f32 {
        self.f32_max(2. * PI)
    }

    pub fn direction(&mut self) -> Vec2<f32> {
        let angle = self.angle();
        Vec2::new(angle.cos(), angle.sin())
    }

    pub fn chance(&mut self, odds: f32) -> bool {
        self.f32() < odds
    }
}

/// One dimensional line, used for [`ScissorRect`]
#[derive(Debug, Copy, Clone)]
pub struct Line {
    pub position: u32,
    pub length: u32,
}

impl Line {
    /// Returns `None` if the two lines do not intersect
    pub fn intersect(self, other: Line) -> Option<Self> {
        let (lower, upper) = if self.position < other.position {
            (self, other)
        } else {
            (other, self)
        };
        if upper.position >= lower.position + lower.length {
            None
        } else {
            Some(Line {
                position: upper.position,
                length: upper
                    .length
                    .min(lower.position + lower.length - upper.position),
            })
        }
    }
}

/// Defines a rectangle that can be used to limit the rendering area
#[derive(Debug, Copy, Clone)]
pub struct ScissorRect {
    pub x: Line,
    pub y: Line,
}

impl ScissorRect {
    /// Returns a ScissorRect that covers the entire viewport
    pub fn viewport(size: Vec2<u32>) -> Self {
        // TODO: Return Option/None if size is 0 in one direction?
        Self {
            x: Line {
                position: 0,
                length: size.x,
            },
            y: Line {
                position: 0,
                length: size.y,
            },
        }
    }

    /// Assumes a coordinate system alignment with (0, 0) being the top-right corner
    /// This is the case for Teeworlds/DDNet maps
    pub fn from_corners(top_left: Vec2<u32>, bottom_right: Vec2<u32>) -> Option<Self> {
        if top_left.x == bottom_right.x || top_left.y == bottom_right.y {
            None
        } else {
            Some(Self {
                x: Line {
                    position: top_left.x,
                    length: bottom_right.x - top_left.x + 1,
                },
                y: Line {
                    position: top_left.y,
                    length: bottom_right.y - top_left.y + 1,
                },
            })
        }
    }

    /// Returns the area of the viewport that both `ScissorRect`s cover
    /// If they do not intersect, None is returned
    pub fn intersect(&self, other: &Self) -> Option<Self> {
        Some(Self {
            x: self.x.intersect(other.x)?,
            y: self.y.intersect(other.y)?,
        })
    }

    pub fn apply(&self, render_pass: &mut RenderPass) {
        render_pass.set_scissor_rect(
            self.x.position,
            self.y.position,
            self.x.length,
            self.y.length,
        );
    }
}

/// An area in map coordinates, tiles as the unit
/// Specifies that rendering should be limited to the rectangle
#[derive(Debug, Copy, Clone)]
pub struct Clip {
    pub top_left: Vec2<f32>,
    pub bottom_right: Vec2<f32>,
}

impl Clip {
    /// Returns the area of the viewport that is part of the render-area with the clip active
    pub fn project(&self, render_pass: &TwRenderPass, parallax: Vec2<f32>) -> Option<ScissorRect> {
        // The amount of tiles vertically and horizontally across the screen
        let mut tile_counts = render_pass.camera.base_dimensions;
        // Parallax == 0 is special-cased to not include zoom
        if parallax != Vec2::zero() {
            tile_counts *= render_pass.camera.zoom;
        }

        let render_size = render_pass.size.az::<f32>();

        // Position of the camera from the clip's coordinate system (parallax is calculated into position of camera)
        let camera_position = render_pass.camera.position * parallax;
        // Since we want to calculate the area of the viewport that is covered by the clip,
        // we want to go into a coordinate system where the top-left corner of what the camera sees to be our coordinate system's origin
        // Lets call it the 'viewport' coordinate system
        let camera_top_left = camera_position - tile_counts / 2.;

        let top_left: Vec2<f32> = (self.top_left - camera_top_left) // Move into viewport coordinates
                / tile_counts // Normalize viewport coordinate system so that (1, 1) is the bottom-right corner of the viewport
                * render_size; // Change viewport coordinate system so that (viewport_width, viewport_height) is the bottom-right corner of the viewport
        let clip_top_left = top_left
            .clamped(Vec2::zero(), render_size - 1.) // Clamp the coordinates into the viewport
            .az::<u32>();

        let bottom_right = (self.bottom_right - camera_top_left) / tile_counts * render_size;
        let clip_bottom_right = bottom_right
            .clamped(Vec2::zero(), render_size - 1.)
            .az::<u32>();
        ScissorRect::from_corners(clip_top_left, clip_bottom_right)
    }

    pub fn offset(mut self, offset: Vec2<f32>) -> Self {
        self.top_left += offset;
        self.bottom_right += offset;
        self
    }
}

/// Represents one of the four corners of a rectangle
#[derive(Debug, Copy, Clone)]
pub enum Corner {
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight,
}

pub const QUAD: [Corner; 4] = [
    Corner::TopLeft,
    Corner::TopRight,
    Corner::BottomLeft,
    Corner::BottomRight,
];

impl Corner {
    /// Creates a quad with two separate triangles.
    /// The two triangles split the rectangle along the diagonal that is between the bottom-left and top-right corner.
    #[rustfmt::skip]
    pub const SPLIT_QUAD: [Self; 6] = [
        Corner::TopLeft,  Corner::TopRight,    Corner::BottomLeft,
        Corner::TopRight, Corner::BottomRight, Corner::BottomLeft,
    ];

    pub const fn is_top(self) -> bool {
        match self {
            Corner::TopLeft | Corner::TopRight => true,
            Corner::BottomLeft | Corner::BottomRight => false,
        }
    }

    pub const fn is_bottom(self) -> bool {
        !self.is_top()
    }

    pub const fn is_left(self) -> bool {
        match self {
            Corner::TopLeft | Corner::BottomLeft => true,
            Corner::TopRight | Corner::BottomRight => false,
        }
    }

    pub const fn is_right(self) -> bool {
        !self.is_left()
    }

    /// Position vector of this viewport corner
    #[rustfmt::skip]
    pub const fn viewport(self) -> Vec2<f32> {
        match self {
            Corner::TopLeft     => Vec2::new(-1.,  1.),
            Corner::TopRight    => Vec2::new( 1.,  1.),
            Corner::BottomLeft  => Vec2::new(-1., -1.),
            Corner::BottomRight => Vec2::new( 1., -1.),
        }
    }

    /// Directional vector in the direction of this co
    #[rustfmt::skip]
    pub const fn direction(self) -> Vec2<f32> {
        match self {
            Corner::TopLeft     => Vec2::new(-1., -1.),
            Corner::TopRight    => Vec2::new( 1., -1.),
            Corner::BottomLeft  => Vec2::new(-1.,  1.),
            Corner::BottomRight => Vec2::new( 1.,  1.),
        }
    }

    #[rustfmt::skip]
    pub const fn uv(self) -> Vec2<f32> {
        match self {
            Corner::TopLeft     => Vec2::new(0., 0.),
            Corner::TopRight    => Vec2::new(1., 0.),
            Corner::BottomLeft  => Vec2::new(0., 1.),
            Corner::BottomRight => Vec2::new(1., 1.),
        }
    }

    #[rustfmt::skip]
    pub const fn select_corner<T: Copy>(self, aabr: &Aabr<T>) -> Vec2<T> {
        match self {
            Corner::TopLeft     => Vec2::new(aabr.min.x, aabr.min.y),
            Corner::TopRight    => Vec2::new(aabr.max.x, aabr.min.y),
            Corner::BottomLeft  => Vec2::new(aabr.min.x, aabr.max.y),
            Corner::BottomRight => Vec2::new(aabr.max.x, aabr.max.y),

        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Zeroable, bytemuck::Pod)]
#[repr(C)]
pub struct Vertex {
    pub position: Vec2<f32>,
    pub uv: Vec2<f32>,
}