spitfire-draw 0.36.14

Drawing helper module for Spitfire toolset
Documentation
use crate::{
    context::DrawContext,
    utils::{Drawable, ShaderRef, TextureRef, Vertex, transform_to_matrix},
};
use smallvec::SmallVec;
use spitfire_glow::{
    graphics::{GraphicsBatch, GraphicsTarget},
    renderer::{GlowBlending, GlowTextureFiltering, GlowUniformValue},
};
use std::{borrow::Cow, collections::HashMap};
use vek::{Quaternion, Rect, Rgba, Transform, Vec2, Vec3};

#[derive(Debug, Clone)]
pub struct SpriteTexture {
    pub sampler: Cow<'static, str>,
    pub texture: TextureRef,
    pub filtering: GlowTextureFiltering,
}

impl SpriteTexture {
    pub fn new(sampler: Cow<'static, str>, texture: TextureRef) -> Self {
        Self {
            sampler,
            texture,
            filtering: GlowTextureFiltering::Linear,
        }
    }

    pub fn filtering(mut self, value: GlowTextureFiltering) -> Self {
        self.filtering = value;
        self
    }
}

#[derive(Debug, Clone)]
pub struct Sprite {
    pub shader: Option<ShaderRef>,
    pub textures: SmallVec<[SpriteTexture; 4]>,
    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
    pub region: Rect<f32, f32>,
    pub page: f32,
    pub tint: Rgba<f32>,
    pub transform: Transform<f32, f32, f32>,
    pub size: Option<Vec2<f32>>,
    pub pivot: Vec2<f32>,
    pub blending: Option<GlowBlending>,
    pub screen_space: bool,
}

impl Default for Sprite {
    fn default() -> Self {
        Self {
            shader: Default::default(),
            textures: Default::default(),
            uniforms: Default::default(),
            region: Rect::new(0.0, 0.0, 1.0, 1.0),
            page: Default::default(),
            tint: Rgba::white(),
            transform: Default::default(),
            size: Default::default(),
            pivot: Default::default(),
            blending: Default::default(),
            screen_space: Default::default(),
        }
    }
}

impl Sprite {
    pub fn single(texture: SpriteTexture) -> Self {
        Self {
            textures: vec![texture].into(),
            ..Default::default()
        }
    }

    pub fn shader(mut self, value: ShaderRef) -> Self {
        self.shader = Some(value);
        self
    }

    pub fn texture(mut self, value: SpriteTexture) -> Self {
        self.textures.push(value);
        self
    }

    pub fn uniform(mut self, key: Cow<'static, str>, value: GlowUniformValue) -> Self {
        self.uniforms.insert(key, value);
        self
    }

    pub fn region_page(mut self, region: Rect<f32, f32>, page: f32) -> Self {
        self.region = region;
        self.page = page;
        self
    }

    pub fn tint(mut self, value: Rgba<f32>) -> Self {
        self.tint = value;
        self
    }

    pub fn transform(mut self, value: Transform<f32, f32, f32>) -> Self {
        self.transform = value;
        self
    }

    pub fn position(mut self, value: Vec2<f32>) -> Self {
        self.transform.position = value.into();
        self
    }

    pub fn orientation(mut self, value: Quaternion<f32>) -> Self {
        self.transform.orientation = value;
        self
    }

    pub fn rotation(mut self, angle_radians: f32) -> Self {
        self.transform.orientation = Quaternion::rotation_z(angle_radians);
        self
    }

    pub fn scale(mut self, value: Vec2<f32>) -> Self {
        self.transform.scale = Vec3::new(value.x, value.y, 1.0);
        self
    }

    pub fn size(mut self, value: Vec2<f32>) -> Self {
        self.size = Some(value);
        self
    }

    pub fn pivot(mut self, value: Vec2<f32>) -> Self {
        self.pivot = value;
        self
    }

    pub fn blending(mut self, value: GlowBlending) -> Self {
        self.blending = Some(value);
        self
    }

    pub fn screen_space(mut self, value: bool) -> Self {
        self.screen_space = value;
        self
    }
}

impl Drawable for Sprite {
    fn draw(&self, context: &mut DrawContext, graphics: &mut dyn GraphicsTarget<Vertex>) {
        let batch = GraphicsBatch {
            shader: context.shader(self.shader.as_ref()),
            uniforms: self
                .uniforms
                .iter()
                .map(|(k, v)| (k.clone(), v.to_owned()))
                .chain(std::iter::once((
                    "u_projection_view".into(),
                    GlowUniformValue::M4(
                        if self.screen_space {
                            graphics.state().main_camera.screen_matrix()
                        } else {
                            graphics.state().main_camera.world_matrix()
                        }
                        .into_col_array(),
                    ),
                )))
                .chain(self.textures.iter().enumerate().map(|(index, texture)| {
                    (texture.sampler.clone(), GlowUniformValue::I1(index as _))
                }))
                .collect(),
            textures: self
                .textures
                .iter()
                .filter_map(|texture| {
                    Some((context.texture(Some(&texture.texture))?, texture.filtering))
                })
                .collect(),
            blending: self.blending.unwrap_or_else(|| context.top_blending()),
            scissor: None,
            wireframe: context.wireframe,
        };
        let transform = context.top_transform() * transform_to_matrix(self.transform);
        let size = self
            .size
            .or_else(|| {
                batch
                    .textures
                    .first()
                    .map(|(texture, _)| Vec2::new(texture.width() as _, texture.height() as _))
            })
            .unwrap_or_default();
        let offset = size * self.pivot;
        let color = self.tint.into_array();
        graphics.state_mut().stream.batch_optimized(batch);
        graphics.state_mut().stream.transformed(
            |stream| {
                stream.quad([
                    Vertex {
                        position: [0.0, 0.0],
                        uv: [self.region.x, self.region.y, self.page],
                        color,
                    },
                    Vertex {
                        position: [size.x, 0.0],
                        uv: [self.region.x + self.region.w, self.region.y, self.page],
                        color,
                    },
                    Vertex {
                        position: [size.x, size.y],
                        uv: [
                            self.region.x + self.region.w,
                            self.region.y + self.region.h,
                            self.page,
                        ],
                        color,
                    },
                    Vertex {
                        position: [0.0, size.y],
                        uv: [self.region.x, self.region.y + self.region.h, self.page],
                        color,
                    },
                ]);
            },
            |vertex| {
                let point = transform.mul_point(Vec2::from(vertex.position) - offset);
                vertex.position[0] = point.x;
                vertex.position[1] = point.y;
            },
        );
    }
}