From a1cd5a196d8e92b7989c031eab075b0c718c00d1 Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Tue, 5 Jul 2022 11:24:51 +0200 Subject: [PATCH] Sprite 9 slice --- crates/bevy_sprite/src/lib.rs | 1 + crates/bevy_sprite/src/render/mod.rs | 60 ++++++++++++++++------ crates/bevy_sprite/src/sprite.rs | 77 ++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 15 deletions(-) diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 8529e01ab3e98..95aba8c272620 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -12,6 +12,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ bundle::{SpriteBundle, SpriteSheetBundle}, + rect::{BorderRect, Rect}, sprite::Sprite, texture_atlas::{TextureAtlas, TextureAtlasSprite}, ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder, diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 8b71558813973..3fcb8b11f2e91 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use crate::{ texture_atlas::{TextureAtlas, TextureAtlasSprite}, - Sprite, SPRITE_SHADER_HANDLE, + Rect, Sprite, SpriteSlice, SPRITE_SHADER_HANDLE, }; use bevy_asset::{AssetEvent, Assets, Handle, HandleId}; use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping}; @@ -316,6 +316,7 @@ pub fn extract_sprite_events( pub fn extract_sprites( mut extracted_sprites: ResMut, texture_atlases: Extract>>, + images: Extract>>, sprite_query: Extract< Query<( Entity, @@ -323,6 +324,7 @@ pub fn extract_sprites( &Sprite, &GlobalTransform, &Handle, + Option<&SpriteSlice> )>, >, atlas_query: Extract< @@ -336,23 +338,51 @@ pub fn extract_sprites( >, ) { extracted_sprites.sprites.clear(); - for (entity, visibility, sprite, transform, handle) in sprite_query.iter() { + for (entity, visibility, sprite, transform, handle, slice) in sprite_query.iter() { if !visibility.is_visible() { continue; } - // PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive - extracted_sprites.sprites.push(ExtractedSprite { - entity, - color: sprite.color, - transform: *transform, - rect: sprite.rect, - // Pass the custom size - custom_size: sprite.custom_size, - flip_x: sprite.flip_x, - flip_y: sprite.flip_y, - image_handle_id: handle.id(), - anchor: sprite.anchor.as_vec(), - }); + if let Some(slice) = slice { + let image_size = match images.get(handle) { + None => continue, + Some(i) => Vec2::new( + i.texture_descriptor.size.width as f32, + i.texture_descriptor.size.height as f32, + ), + }; + // TODO: remove + let slice: &SpriteSlice = slice; + // + for rect in slice.slice_rects(image_size) { + extracted_sprites.sprites.alloc().init(ExtractedSprite { + entity, + color: sprite.color, + transform: *transform, + rect: Some(rect), + // Pass the custom size + custom_size: None, + flip_x: sprite.flip_x, + flip_y: sprite.flip_y, + image_handle_id: handle.id, + anchor: sprite.anchor.as_vec(), + }); + } + } else { + // PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive + extracted_sprites.sprites.alloc().init(ExtractedSprite { + entity, + color: sprite.color, + transform: *transform, + // Use the full texture + rect: None, + // Pass the custom size + custom_size: sprite.custom_size, + flip_x: sprite.flip_x, + flip_y: sprite.flip_y, + image_handle_id: handle.id, + anchor: sprite.anchor.as_vec(), + }); + } } for (entity, visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() { if !visibility.is_visible() { diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index da2a6de1c2cf1..38c0457ad6da2 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -1,3 +1,4 @@ +use crate::{BorderRect, Rect}; use bevy_ecs::component::Component; use bevy_math::{Rect, Vec2}; use bevy_reflect::Reflect; @@ -22,6 +23,27 @@ pub struct Sprite { pub anchor: Anchor, } +/// Defines how the non corner sections of a [`SpriteSlice`] are scaled. +#[derive(Debug, Copy, Clone, Default, Reflect)] +pub enum SliceScaleMode { + /// The sections will stretch + #[default] + Stretch, + /// The sections will repeat + Tile, +} + +/// Component for [9-sliced](https://en.wikipedia.org/wiki/9-slice_scaling) sprites. +/// +/// When resizing a 9-sliced sprite the corners will remain unscaled while the other sections will be scaled or tiled +#[derive(Component, Debug, Clone, Default, Reflect)] +pub struct SpriteSlice { + /// The sprite borders, defining the 9 sections of the image + pub border: BorderRect, + /// How do the the non corner sections scale + pub scale_mode: SliceScaleMode, +} + /// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform). /// It defaults to `Anchor::Center`. #[derive(Debug, Clone, Default, Reflect)] @@ -42,6 +64,61 @@ pub enum Anchor { Custom(Vec2), } +impl SpriteSlice { + /// Computes the 9 [`Rect`] and size values for a [`Sprite`] given its `image_size` + pub fn slice_rects(&self, image_size: Vec2) -> [Rect; 9] { + // corners + let bl_corner = Rect { + min: Vec2::ZERO, + max: Vec2::new(self.border.left, self.border.bottom), + }; + let br_corner = Rect { + min: Vec2::new(image_size.x - self.border.right, 0.0), + max: Vec2::new(image_size.x, self.border.bottom), + }; + let tl_corner = Rect { + min: Vec2::new(0.0, image_size.y - self.border.top), + max: Vec2::new(self.border.left, image_size.y), + }; + let tr_corner = Rect { + min: Vec2::new( + image_size.x - self.border.right, + image_size.y - self.border.top, + ), + max: Vec2::new(image_size.x, image_size.y), + }; + // Sides + let left_side = Rect { + min: Vec2::new(0.0, self.border.bottom), + max: Vec2::new(self.border.left, image_size.y - self.border.top), + }; + let right_side = Rect { + min: Vec2::new(image_size.x - self.border.right, self.border.bottom), + max: Vec2::new(image_size.x, image_size.y - self.border.top), + }; + let bot_side = Rect { + min: Vec2::new(self.border.left, 0.0), + max: Vec2::new(image_size.x - self.border.right, self.border.bottom), + }; + let top_side = Rect { + min: Vec2::new(self.border.left, image_size.y - self.border.top), + max: Vec2::new(image_size.x - self.border.right, image_size.y), + }; + // Center + let center = Rect { + min: Vec2::new(self.border.left, self.border.bottom), + max: Vec2::new( + image_size.x - self.border.right, + image_size.y - self.border.top, + ), + }; + [ + bl_corner, br_corner, tl_corner, tr_corner, left_side, right_side, bot_side, top_side, + center, + ] + } +} + impl Anchor { pub fn as_vec(&self) -> Vec2 { match self {