twgpu 0.4.1

Render Teeworlds and DDNet maps
Documentation
use fixed::types::I27F5;
use twmap::{Group, Layer, LayerKind};
use vek::{Extent2, Vec2};
use wgpu::Device;

use super::{
    GpuGroupData, GpuLayerData, GpuMapDataDyn, GpuMapDataStatic, GpuMapStatic, GpuQuadsRender,
    GpuTilemapRender, TilemapCorner,
};
use crate::buffer::GpuBuffer;
use crate::shared::{Clip, ScissorRect};
use crate::textures::Samplers;
use crate::{GpuCamera, TwRenderPass};

pub enum GpuLayerRender {
    Tilemap(GpuTilemapRender),
    Quads(GpuQuadsRender),
}

pub struct GpuGroupRender {
    pub clip: Option<Clip>,
    pub parallax: Vec2<f32>,
    pub offset: Vec2<f32>,
    pub layers: Vec<GpuLayerRender>,
    pub game_layer_cutoff: Option<usize>,
}

pub(crate) fn is_gpu_layer(layer: &&Layer) -> bool {
    matches!(layer.kind(), LayerKind::Tiles | LayerKind::Quads)
}

fn is_gpu_or_game_layer(layer: &&Layer) -> bool {
    matches!(
        layer.kind(),
        LayerKind::Game | LayerKind::Tiles | LayerKind::Quads
    )
}

impl GpuMapStatic {
    pub fn prepare_group_render(
        &self,
        group: &Group,
        data: &GpuGroupData,
        map_data_static: &GpuMapDataStatic,
        map_data_dyn: &GpuMapDataDyn,
        camera: &GpuCamera,
        tilemap_vertices: &[Option<GpuBuffer<[TilemapCorner; 4]>>],
        samplers: &Samplers,
        device: &Device,
    ) -> GpuGroupRender {
        let clip = if group.clipping {
            let top_left: Vec2<f32> = group.clip.position().az();
            let extent: Extent2<f32> =
                Extent2::<I27F5>::max(group.clip.extent(), Extent2::zero()).az();
            Some(Clip {
                top_left,
                bottom_right: top_left + extent,
            })
        } else {
            None
        };
        let parallax = group.parallax.az::<f32>() / 100.;
        let offset = group.offset.az::<f32>() * -1.; // Group offset is inverted for some reason
        let mut layers = Vec::new();
        for ((layer, tilemap_vertices), layer_data) in group
            .layers
            .iter()
            .filter(is_gpu_layer)
            .zip(tilemap_vertices.iter())
            .zip(data.layers.iter())
        {
            match (layer, layer_data) {
                (Layer::Tiles(layer), GpuLayerData::Tilemap(layer_data)) => {
                    layers.push(GpuLayerRender::Tilemap(self.tilemap.prepare_render(
                        layer,
                        layer_data,
                        tilemap_vertices.as_ref().unwrap(),
                        map_data_static,
                        samplers,
                        device,
                    )))
                }
                (Layer::Quads(layer), GpuLayerData::Quads(layer_data)) => {
                    layers.push(GpuLayerRender::Quads(self.quads.prepare_render(
                        layer,
                        layer_data,
                        data,
                        map_data_static,
                        map_data_dyn,
                        camera,
                        samplers,
                        device,
                    )))
                }
                _ => panic!("Mismatched layers between Group and GroupData"),
            }
        }
        let game_layer_cutoff = group
            .layers
            .iter()
            .filter(is_gpu_or_game_layer)
            .position(|layer| layer.kind() == LayerKind::Game);
        GpuGroupRender {
            clip,
            parallax,
            offset,
            layers,
            game_layer_cutoff,
        }
    }
}

impl GpuLayerRender {
    pub fn render<'pass>(&'pass self, render_pass: &mut TwRenderPass<'pass>) {
        match self {
            GpuLayerRender::Tilemap(tilemap) => tilemap.render(render_pass),
            GpuLayerRender::Quads(quads) => quads.render(render_pass),
        }
    }
}

impl GpuGroupRender {
    pub fn render<'pass>(&'pass self, foreground: bool, render_pass: &mut TwRenderPass<'pass>) {
        let scissor_rect = match &self.clip {
            None => ScissorRect::viewport(render_pass.size), // No own clipping
            Some(clip) => {
                match clip.project(render_pass, Vec2::new(1., 1.)) {
                    // Clip of group does not intersect with render target
                    None => return,
                    Some(scissor_rect) => {
                        match scissor_rect.intersect(&render_pass.scissor_rect) {
                            // No intersection between clip and previous scissor rect
                            None => return,
                            Some(scissor_rect) => scissor_rect,
                        }
                    }
                }
            }
        };
        let layers = match (foreground, self.game_layer_cutoff) {
            (_, None) => &self.layers[..],
            (true, Some(cutoff)) => &self.layers[cutoff..],
            (false, Some(cutoff)) => &self.layers[..cutoff],
        };
        render_pass.set_scissor_rect(&scissor_rect);
        for layer in layers {
            layer.render(render_pass);
        }
    }
}