use glam::{Affine2, Vec2};
use nanoserde::DeRon;
use rgb::RGBA8;
use super::Loadable;
use crate::{
assets::{
Id,
loader::{png::PngLoader, ron::RonLoader},
},
context::ContextInner,
graphics::atlas::TextureRef,
};
#[derive(Clone, Copy)]
pub(crate) struct Sprite {
pub(crate) texture: TextureRef,
pub(crate) sub_rectangle: (f32, f32, f32, f32),
metadata: SpriteMetadata,
}
impl Sprite {
pub(crate) fn new_and_upload(
width: f32,
height: f32,
pivot_x: f32,
pivot_y: f32,
pixels: &[RGBA8],
ctx: &mut ContextInner,
) -> Self {
let texture =
ctx.graphics
.upload_texture(width.round() as u32, height.round() as u32, pixels);
let sub_rectangle = (0.0, 0.0, width, height);
let metadata = SpriteMetadata {
pivot_x: SpritePivot::Pixels(pivot_x),
pivot_y: SpritePivot::Pixels(pivot_y),
};
Self {
texture,
sub_rectangle,
metadata,
}
}
pub(crate) fn horizontal_parts(&self, part_width: f32) -> Vec<Self> {
let (x, y, width, height) = self.sub_rectangle;
assert!(
width % part_width == 0.0,
"Cannot split image into equal horizontal parts of {part_width} pixels"
);
let sub_images = (width / part_width) as usize;
(0..sub_images)
.map(|index| {
let sub_rectangle = (part_width.mul_add(index as f32, x), y, part_width, height);
Self {
sub_rectangle,
..*self
}
})
.collect()
}
#[inline]
#[must_use]
pub(crate) fn pivot_offset(&self, pivot_x: SpritePivot, pivot_y: SpritePivot) -> (f32, f32) {
(
pivot_x.pivot(self.sub_rectangle.2),
pivot_y.pivot(self.sub_rectangle.3),
)
}
#[inline]
#[must_use]
#[allow(clippy::too_many_arguments)]
pub(crate) fn affine_matrix(
&self,
x: f32,
y: f32,
previous_x: f32,
previous_y: f32,
blending_factor: f32,
blend: bool,
rotation: f32,
scale_x: f32,
scale_y: f32,
pivot_x: SpritePivot,
pivot_y: SpritePivot,
) -> Affine2 {
let (sprite_offset_x, sprite_offset_y) = self.pivot_offset(pivot_x, pivot_y);
let (x, y) = if blend {
(
crate::math::lerp(previous_x, x, blending_factor),
crate::math::lerp(previous_y, y, blending_factor),
)
} else {
(x, y)
};
#[allow(clippy::float_cmp)]
if scale_x == 1.0 && scale_y == 1.0 && rotation == 0.0 {
Affine2::from_translation((x + sprite_offset_x, y + sprite_offset_y).into())
} else {
let mut affine = Affine2::from_angle(rotation)
* Affine2::from_scale((scale_x, scale_y).into())
* Affine2::from_translation((sprite_offset_x, sprite_offset_y).into());
affine.translation += Vec2::new(x, y);
affine
}
}
pub(crate) fn load_if_exists_without_metadata(id: &Id, ctx: &mut ContextInner) -> Option<Self> {
let (texture, width, height) = if let Some(texture) = ctx.asset_source.embedded_texture(id)
{
(
texture.reference,
texture.width as f32,
texture.height as f32,
)
} else {
let (width, height, pixels) = ctx.asset_source.load_if_exists::<PngLoader, _>(id)?;
let texture = ctx.graphics.upload_texture(width, height, &pixels);
(texture, width as f32, height as f32)
};
let sub_rectangle = (0.0, 0.0, width, height);
let metadata = SpriteMetadata::default();
Some(Self {
texture,
sub_rectangle,
metadata,
})
}
pub(crate) const fn pivot_x(&self) -> SpritePivot {
self.metadata.pivot_x
}
pub(crate) const fn pivot_y(&self) -> SpritePivot {
self.metadata.pivot_y
}
}
impl Loadable for Sprite {
fn load_if_exists(id: &Id, ctx: &mut ContextInner) -> Option<Self>
where
Self: Sized,
{
let mut sprite = Self::load_if_exists_without_metadata(id, ctx)?;
sprite.metadata = SpriteMetadata::load_if_exists(id, ctx).unwrap_or_default();
Some(sprite)
}
}
#[derive(Debug, Clone, Copy, Default, DeRon)]
pub struct SpriteMetadata {
pub(crate) pivot_x: SpritePivot,
pub(crate) pivot_y: SpritePivot,
}
impl Loadable for SpriteMetadata {
#[inline]
fn load_if_exists(id: &Id, ctx: &mut ContextInner) -> Option<Self>
where
Self: Sized,
{
ctx.asset_source.load_if_exists::<RonLoader, _>(id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, DeRon)]
pub enum SpritePivot {
#[default]
Start,
Center,
End,
Pixels(f32),
Fraction(f32),
}
impl SpritePivot {
#[inline]
pub(crate) fn pivot(self, axis_size: f32) -> f32 {
match self {
Self::Start => 0.0,
Self::Center => -axis_size / 2.0,
Self::End => -axis_size,
Self::Pixels(pixels) => -pixels,
Self::Fraction(fraction) => -axis_size * fraction,
}
}
}