use bevy_asset::{Assets, Handle};
use bevy_camera::visibility::{Visibility, VisibilityClass};
use bevy_color::Color;
use bevy_ecs::{component::Component, reflect::ReflectComponent, template::FromTemplate};
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout};
use bevy_math::{Rect, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, PartialReflect, Reflect};
use bevy_transform::components::Transform;
use crate::{Anchor, SpriteImageMode};
#[derive(Component, Debug, Default, Clone, Reflect, PartialEq, FromTemplate)]
#[require(Transform, Visibility, VisibilityClass, Anchor)]
#[reflect(Component, Default, Debug, Clone)]
pub struct SpriteMesh {
pub image: Handle<Image>,
#[template(built_in)]
pub texture_atlas: Option<TextureAtlas>,
pub color: Color,
pub flip_x: bool,
pub flip_y: bool,
pub custom_size: Option<Vec2>,
pub rect: Option<Rect>,
pub image_mode: SpriteImageMode,
pub alpha_mode: SpriteAlphaMode,
}
impl core::hash::Hash for SpriteMesh {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.image.hash(state);
self.texture_atlas.hash(state);
self.color.reflect_hash().hash(state);
self.custom_size.reflect_hash().hash(state);
self.flip_x.hash(state);
self.flip_y.hash(state);
}
}
impl Eq for SpriteMesh {}
impl SpriteMesh {
pub fn sized(custom_size: Vec2) -> Self {
SpriteMesh {
custom_size: Some(custom_size),
..Default::default()
}
}
pub fn from_image(image: Handle<Image>) -> Self {
Self {
image,
..Default::default()
}
}
pub fn from_atlas_image(image: Handle<Image>, atlas: TextureAtlas) -> Self {
Self {
image,
texture_atlas: Some(atlas),
..Default::default()
}
}
pub fn from_color(color: impl Into<Color>, size: Vec2) -> Self {
Self {
color: color.into(),
custom_size: Some(size),
..Default::default()
}
}
pub fn compute_pixel_space_point(
&self,
point_relative_to_sprite: Vec2,
anchor: Anchor,
images: &Assets<Image>,
texture_atlases: &Assets<TextureAtlasLayout>,
) -> Result<Vec2, Vec2> {
let image_size = images
.get(&self.image)
.map(Image::size)
.unwrap_or(UVec2::ONE);
let atlas_rect = self
.texture_atlas
.as_ref()
.and_then(|s| s.texture_rect(texture_atlases))
.map(|r| r.as_rect());
let texture_rect = match (atlas_rect, self.rect) {
(None, None) => Rect::new(0.0, 0.0, image_size.x as f32, image_size.y as f32),
(None, Some(sprite_rect)) => sprite_rect,
(Some(atlas_rect), None) => atlas_rect,
(Some(atlas_rect), Some(mut sprite_rect)) => {
sprite_rect.min += atlas_rect.min;
sprite_rect.max += atlas_rect.min;
sprite_rect
}
};
let sprite_size = self.custom_size.unwrap_or_else(|| texture_rect.size());
let sprite_center = -anchor.as_vec() * sprite_size;
let mut point_relative_to_sprite_center = point_relative_to_sprite - sprite_center;
if self.flip_x {
point_relative_to_sprite_center.x *= -1.0;
}
if !self.flip_y {
point_relative_to_sprite_center.y *= -1.0;
}
if sprite_size.x == 0.0 || sprite_size.y == 0.0 {
return Err(point_relative_to_sprite_center);
}
let sprite_to_texture_ratio = {
let texture_size = texture_rect.size();
Vec2::new(
texture_size.x / sprite_size.x,
texture_size.y / sprite_size.y,
)
};
let point_relative_to_texture =
point_relative_to_sprite_center * sprite_to_texture_ratio + texture_rect.center();
if texture_rect.contains(point_relative_to_texture) {
Ok(point_relative_to_texture)
} else {
Err(point_relative_to_texture)
}
}
}
#[derive(Debug, Reflect, Copy, Clone, PartialEq)]
#[reflect(Default, Debug, Clone)]
pub enum SpriteAlphaMode {
Opaque,
Mask(f32),
Blend,
}
impl Default for SpriteAlphaMode {
fn default() -> Self {
Self::Mask(0.5)
}
}