use bevy::{platform::collections::HashSet, prelude::*, sprite::Anchor};
use crate::sprites::ExtractedSlice;
#[derive(Debug, Clone, Component)]
pub struct ComputedTextureSlices(Vec<TextureSlice>);
impl ComputedTextureSlices {
#[must_use]
pub(crate) fn extract_slices<'a>(
&'a self,
sprite: &'a Sprite,
anchor: &'a Anchor,
) -> impl ExactSizeIterator<Item = ExtractedSlice> + 'a {
let mut flip = Vec2::ONE;
if sprite.flip_x {
flip.x *= -1.0;
}
if sprite.flip_y {
flip.y *= -1.0;
}
let anchor = anchor.as_vec()
* sprite
.custom_size
.unwrap_or(sprite.rect.unwrap_or_default().size());
self.0.iter().map(move |slice| ExtractedSlice {
offset: slice.offset * flip - anchor,
rect: slice.texture_rect,
size: slice.draw_size,
})
}
}
#[must_use]
fn compute_sprite_slices(
sprite: &Sprite,
images: &Assets<Image>,
atlas_layouts: &Assets<TextureAtlasLayout>,
) -> Option<ComputedTextureSlices> {
let (image_size, texture_rect) = match &sprite.texture_atlas {
Some(a) => {
let layout = atlas_layouts.get(&a.layout)?;
(
layout.size.as_vec2(),
layout.textures.get(a.index)?.as_rect(),
)
}
None => {
let image = images.get(&sprite.image)?;
let size = Vec2::new(
image.texture_descriptor.size.width as f32,
image.texture_descriptor.size.height as f32,
);
let rect = sprite.rect.unwrap_or(Rect {
min: Vec2::ZERO,
max: size,
});
(size, rect)
}
};
let slices = match &sprite.image_mode {
SpriteImageMode::Sliced(slicer) => slicer.compute_slices(texture_rect, sprite.custom_size),
SpriteImageMode::Tiled {
tile_x,
tile_y,
stretch_value,
} => {
let slice = TextureSlice {
texture_rect,
draw_size: sprite.custom_size.unwrap_or(image_size),
offset: Vec2::ZERO,
};
slice.tiled(*stretch_value, (*tile_x, *tile_y))
}
SpriteImageMode::Auto => {
unreachable!("Slices should not be computed for SpriteImageMode::Stretch")
}
SpriteImageMode::Scale(_) => {
unreachable!("Slices should not be computed for SpriteImageMode::Scale")
}
};
Some(ComputedTextureSlices(slices))
}
pub(crate) fn compute_slices_on_asset_event(
mut commands: Commands,
mut events: MessageReader<AssetEvent<Image>>,
images: Res<Assets<Image>>,
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
sprites: Query<(Entity, &Sprite)>,
) {
let added_handles: HashSet<_> = events
.read()
.filter_map(|e| match e {
AssetEvent::Added { id } | AssetEvent::Modified { id } => Some(*id),
_ => None,
})
.collect();
if added_handles.is_empty() {
return;
}
for (entity, sprite) in &sprites {
if !sprite.image_mode.uses_slices() {
continue;
}
if !added_handles.contains(&sprite.image.id()) {
continue;
}
if let Some(slices) = compute_sprite_slices(sprite, &images, &atlas_layouts) {
commands.entity(entity).insert(slices);
}
}
}
pub(crate) fn compute_slices_on_sprite_change(
mut commands: Commands,
images: Res<Assets<Image>>,
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
changed_sprites: Query<(Entity, &Sprite), Changed<Sprite>>,
) {
for (entity, sprite) in &changed_sprites {
if !sprite.image_mode.uses_slices() {
continue;
}
if let Some(slices) = compute_sprite_slices(sprite, &images, &atlas_layouts) {
commands.entity(entity).insert(slices);
}
}
}
pub(crate) fn apply_scaling(
scaling_mode: ScalingMode,
texture_size: Vec2,
quad_size: &mut Vec2,
quad_translation: &mut Vec2,
uv_offset_scale: &mut Vec4,
) {
let quad_ratio = quad_size.x / quad_size.y;
let texture_ratio = texture_size.x / texture_size.y;
let tex_quad_scale = texture_ratio / quad_ratio;
let quad_tex_scale = quad_ratio / texture_ratio;
match scaling_mode {
ScalingMode::FillCenter => {
if quad_ratio > texture_ratio {
uv_offset_scale.y += (uv_offset_scale.w - uv_offset_scale.w * tex_quad_scale) * 0.5;
uv_offset_scale.w *= tex_quad_scale;
} else {
uv_offset_scale.x += (uv_offset_scale.z - uv_offset_scale.z * quad_tex_scale) * 0.5;
uv_offset_scale.z *= quad_tex_scale;
};
}
ScalingMode::FillStart => {
if quad_ratio > texture_ratio {
uv_offset_scale.y += uv_offset_scale.w - uv_offset_scale.w * tex_quad_scale;
uv_offset_scale.w *= tex_quad_scale;
} else {
uv_offset_scale.z *= quad_tex_scale;
}
}
ScalingMode::FillEnd => {
if quad_ratio > texture_ratio {
uv_offset_scale.w *= tex_quad_scale;
} else {
uv_offset_scale.x += uv_offset_scale.z - uv_offset_scale.z * quad_tex_scale;
uv_offset_scale.z *= quad_tex_scale;
}
}
ScalingMode::FitCenter => {
if texture_ratio > quad_ratio {
quad_size.y *= quad_tex_scale;
} else {
quad_size.x *= tex_quad_scale;
}
}
ScalingMode::FitStart => {
if texture_ratio > quad_ratio {
let scale = Vec2::new(1.0, quad_tex_scale);
let new_quad = *quad_size * scale;
let offset = *quad_size - new_quad;
*quad_translation = Vec2::new(0.0, -offset.y);
*quad_size = new_quad;
} else {
let scale = Vec2::new(tex_quad_scale, 1.0);
let new_quad = *quad_size * scale;
let offset = *quad_size - new_quad;
*quad_translation = Vec2::new(offset.x, 0.0);
*quad_size = new_quad;
}
}
ScalingMode::FitEnd => {
if texture_ratio > quad_ratio {
let scale = Vec2::new(1.0, quad_tex_scale);
let new_quad = *quad_size * scale;
let offset = *quad_size - new_quad;
*quad_translation = Vec2::new(0.0, offset.y);
*quad_size = new_quad;
} else {
let scale = Vec2::new(tex_quad_scale, 1.0);
let new_quad = *quad_size * scale;
let offset = *quad_size - new_quad;
*quad_translation = Vec2::new(-offset.x, 0.0);
*quad_size = new_quad;
}
}
}
}