use figures::{Displayable, Rectlike, Scaled};
use crate::math::{ExtentsRect, Pixels, Point, Rect, Size};
use crate::scene::{Element, Target};
use crate::sprite::{RenderedSprite, SpriteRotation};
use crate::texture::Texture;
#[derive(Debug, Clone)]
pub struct SpriteSource {
pub location: SpriteSourceLocation,
pub texture: Texture,
}
#[derive(Debug, Clone)]
pub enum SpriteSourceLocation {
Rect(Rect<u32>),
Joined(Vec<SpriteSourceSublocation>),
}
impl SpriteSourceLocation {
#[must_use]
pub fn bounds(&self) -> Rect<u32> {
match self {
Self::Rect(rect) => *rect,
Self::Joined(locations) => locations
.iter()
.fold(Option::<Rect<u32>>::None, |union, location| {
union.map_or_else(
|| Some(location.destination_rect()),
|total| {
total
.union(&location.destination_rect())
.map(|r| r.as_sized())
},
)
})
.unwrap_or_default(),
}
}
#[must_use]
pub fn size(&self) -> Size<u32> {
self.bounds().size
}
}
#[derive(Debug, Clone)]
pub struct SpriteSourceSublocation {
pub source: Rect<u32>,
pub destination: Point<u32>,
}
impl SpriteSourceSublocation {
#[must_use]
pub const fn destination_rect(&self) -> Rect<u32> {
Rect::new(self.destination, self.source.size)
}
}
impl SpriteSource {
#[must_use]
pub const fn new(location: Rect<u32>, texture: Texture) -> Self {
Self {
location: SpriteSourceLocation::Rect(location),
texture,
}
}
#[must_use]
pub fn joined<I: IntoIterator<Item = SpriteSourceSublocation>>(
locations: I,
texture: Texture,
) -> Self {
Self {
location: SpriteSourceLocation::Joined(locations.into_iter().collect()),
texture,
}
}
#[must_use]
pub fn joined_square<I: IntoIterator<Item = Self>>(sources: I) -> Self {
let sources: Vec<_> = sources.into_iter().collect();
#[allow(clippy::cast_sign_loss)] let sprites_wide = (sources.len() as f32).sqrt() as usize;
assert!(sprites_wide * sprites_wide == sources.len()); let texture = sources[0].texture.clone();
let sprite_size = sources[0].location.bounds().size;
let mut sources = sources.into_iter();
let mut locations = Vec::new();
for y in 0..sprites_wide {
for x in 0..sprites_wide {
let source = sources.next().unwrap();
debug_assert!(texture.id() == source.texture.id());
locations.push(SpriteSourceSublocation {
source: source.location.bounds(),
destination: Point::new(
x as u32 * sprite_size.width,
y as u32 * sprite_size.height,
),
});
}
}
Self::joined(locations, texture)
}
#[must_use]
pub fn entire_texture(texture: Texture) -> Self {
Self::new(Rect::new(Point::default(), texture.size()), texture)
}
pub fn render_at(
&self,
scene: &Target,
location: impl Displayable<f32, Pixels = Point<f32, Pixels>>,
rotation: impl Displayable<f32, Pixels = SpriteRotation<Pixels>>,
) {
self.render_at_with_alpha(scene, location, rotation, 1.);
}
pub fn render_within(
&self,
scene: &Target,
bounds: impl Displayable<f32, Pixels = Rect<f32, Pixels>>,
rotation: impl Displayable<f32, Pixels = SpriteRotation<Pixels>>,
) {
self.render_with_alpha(scene, bounds, rotation, 1.);
}
#[allow(clippy::needless_pass_by_value)]
pub fn render_at_with_alpha(
&self,
scene: &Target,
location: impl Displayable<f32, Pixels = Point<f32, Pixels>>,
rotation: impl Displayable<f32, Pixels = SpriteRotation<Pixels>>,
alpha: f32,
) {
self.render_with_alpha(
scene,
Rect::new(
location.to_pixels(scene.scale()),
self.location
.size()
.cast::<f32>()
.cast_unit::<Scaled>()
.to_pixels(scene.scale()),
),
rotation,
alpha,
);
}
#[allow(clippy::needless_pass_by_value)]
pub fn render_with_alpha(
&self,
scene: &Target,
bounds: impl Displayable<f32, Pixels = Rect<f32, Pixels>>,
rotation: impl Displayable<f32, Pixels = SpriteRotation<Pixels>>,
alpha: f32,
) {
self.render_with_alpha_in_box(
scene,
bounds.to_pixels(scene.scale()).as_extents(),
rotation,
alpha,
);
}
#[allow(clippy::needless_pass_by_value)]
pub fn render_with_alpha_in_box(
&self,
scene: &Target,
bounds: impl Displayable<f32, Pixels = ExtentsRect<f32, Pixels>>,
rotation: impl Displayable<f32, Pixels = SpriteRotation<Pixels>>,
alpha: f32,
) {
let effective_scale = scene.scale();
self.render_raw_with_alpha_in_box(
scene,
bounds.to_pixels(effective_scale),
rotation.to_pixels(effective_scale),
alpha,
);
}
#[allow(clippy::needless_pass_by_value)]
pub fn render_raw_with_alpha_in_box(
&self,
scene: &Target,
bounds: impl Displayable<f32, Pixels = ExtentsRect<f32, Pixels>>,
rotation: impl Displayable<f32, Pixels = SpriteRotation<Pixels>>,
alpha: f32,
) {
let bounds = bounds.to_pixels(scene.scale());
let bounds = ExtentsRect::new(
scene.offset_point_raw(bounds.origin),
scene.offset_point_raw(bounds.extent),
);
scene.push_element(Element::Sprite {
sprite: RenderedSprite::new(
bounds,
rotation.to_pixels(scene.scale()),
alpha,
self.clone(),
),
clip: scene.clip,
});
}
}