Skip to content

Commit

Permalink
Working slice, added example
Browse files Browse the repository at this point in the history
register example

Refactoring

Fixed issue on top/bot rects, added max scaling for corners

Tiling

Fixed example artifacts

Update Cargo.toml

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Example page update

Sprite draw mode

removed extra type registration

Fixed performance regression

Rect slicing

Code cleanup

Applied review suggestions

Rebased on main
  • Loading branch information
ManevilleF committed Nov 14, 2022
1 parent a1cd5a1 commit 9f85d5a
Show file tree
Hide file tree
Showing 11 changed files with 653 additions and 110 deletions.
20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,26 @@ description = "Renders a sprite"
category = "2D Rendering"
wasm = true

[[example]]
name = "sprite_tile"
path = "examples/2d/sprite_tile.rs"

[package.metadata.example.sprite_tile]
name = "Sprite Tile"
description = "Renders a sprite tiled in a grid"
category = "2D Rendering"
wasm = true

[[example]]
name = "sprite_slice"
path = "examples/2d/sprite_slice.rs"

[package.metadata.example.sprite_slice]
name = "Sprite Slice"
description = "Showcases slicing sprites into sections that can be scaled independently via the 9-patch technique"
category = "2D Rendering"
wasm = true

[[example]]
name = "sprite_flipping"
path = "examples/2d/sprite_flipping.rs"
Expand Down
Binary file added assets/textures/slice_sprite.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/textures/slice_square.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/textures/slice_square_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ mod render;
mod sprite;
mod texture_atlas;
mod texture_atlas_builder;
mod texture_slice;

pub mod collide_aabb;

pub mod prelude {
#[doc(hidden)]
pub use crate::{
bundle::{SpriteBundle, SpriteSheetBundle},
rect::{BorderRect, Rect},
sprite::Sprite,
sprite::{Sprite, SpriteDrawMode},
texture_atlas::{TextureAtlas, TextureAtlasSprite},
texture_slice::{BorderRect, SliceScaleMode, TextureSlicer},
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
};
}
Expand All @@ -26,6 +27,7 @@ pub use render::*;
pub use sprite::*;
pub use texture_atlas::*;
pub use texture_atlas_builder::*;
pub use texture_slice::*;

use bevy_app::prelude::*;
use bevy_asset::{AddAsset, Assets, HandleUntyped};
Expand Down
91 changes: 57 additions & 34 deletions crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::cmp::Ordering;

use crate::{
texture_atlas::{TextureAtlas, TextureAtlasSprite},
Rect, Sprite, SpriteSlice, SPRITE_SHADER_HANDLE,
Sprite, SpriteDrawMode, TextureSlice, SPRITE_SHADER_HANDLE,
};
use bevy_asset::{AssetEvent, Assets, Handle, HandleId};
use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping};
Expand Down Expand Up @@ -30,7 +30,7 @@ use bevy_render::{
},
Extract,
};
use bevy_transform::components::GlobalTransform;
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::FloatOrd;
use bevy_utils::HashMap;
use bytemuck::{Pod, Zeroable};
Expand Down Expand Up @@ -324,7 +324,6 @@ pub fn extract_sprites(
&Sprite,
&GlobalTransform,
&Handle<Image>,
Option<&SpriteSlice>
)>,
>,
atlas_query: Extract<
Expand All @@ -338,48 +337,72 @@ pub fn extract_sprites(
>,
) {
extracted_sprites.sprites.clear();
for (entity, visibility, sprite, transform, handle, slice) in sprite_query.iter() {
for (entity, visibility, sprite, transform, handle) in sprite_query.iter() {
if !visibility.is_visible() {
continue;
}
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 {
if let SpriteDrawMode::Simple = sprite.draw_mode {
// 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 {
extracted_sprites.sprites.push(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,
image_handle_id: handle.id(),
anchor: sprite.anchor.as_vec(),
});
continue;
}
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,
),
};

let slices = match &sprite.draw_mode {
SpriteDrawMode::Sliced(slicer) => slicer.compute_slices(
Rect {
min: Vec2::ZERO,
max: image_size,
},
sprite.custom_size,
),
SpriteDrawMode::Tiled {
tile_x,
tile_y,
stretch_value,
} => {
let slice = TextureSlice {
texture_rect: Rect {
min: Vec2::ZERO,
max: image_size,
},
draw_size: sprite.custom_size.unwrap_or(image_size),
offset: Vec2::ZERO,
};
slice.tiled(*stretch_value, (*tile_x, *tile_y))
}
SpriteDrawMode::Simple => unreachable!(),
};
for slice in slices {
let mut transform: GlobalTransform = *transform;
transform =
transform.mul_transform(Transform::from_translation(slice.offset.extend(0.0)));
extracted_sprites.sprites.push(ExtractedSprite {
entity,
color: sprite.color,
transform,
rect: Some(slice.texture_rect),
custom_size: Some(slice.draw_size),
flip_x: sprite.flip_x,
flip_y: sprite.flip_y,
image_handle_id: handle.id(),
anchor: sprite.anchor.as_vec(),
});
}
Expand Down Expand Up @@ -575,7 +598,7 @@ pub fn queue_sprites(
};
let mut current_batch_entity = Entity::from_raw(u32::MAX);
let mut current_image_size = Vec2::ZERO;
// Add a phase item for each sprite, and detect when succesive items can be batched.
// Add a phase item for each sprite, and detect when successive items can be batched.
// Spawn an entity with a `SpriteBatch` component for each possible batch.
// Compatible items share the same entity.
// Batches are merged later (in `batch_phase_system()`), so that they can be interrupted
Expand Down
95 changes: 21 additions & 74 deletions crates/bevy_sprite/src/sprite.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{BorderRect, Rect};
use crate::TextureSlicer;
use bevy_ecs::component::Component;
use bevy_math::{Rect, Vec2};
use bevy_reflect::Reflect;
Expand All @@ -21,27 +21,27 @@ pub struct Sprite {
pub rect: Option<Rect>,
/// [`Anchor`] point of the sprite in the world
pub anchor: Anchor,
/// Define how the Sprite scales when its dimensions change
pub draw_mode: SpriteDrawMode,
}

/// Defines how the non corner sections of a [`SpriteSlice`] are scaled.
#[derive(Debug, Copy, Clone, Default, Reflect)]
pub enum SliceScaleMode {
/// The sections will stretch
#[derive(Debug, Default, Clone, Reflect)]
pub enum SpriteDrawMode {
/// The entire texture scales when its dimensions change. This is the default option.
#[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,
Simple,
/// The texture will be cut in 9 slices, keeping the texture in proportions on resize
Sliced(TextureSlicer),
/// The texture will be repeated if stretched beyond `stretched_value`
Tiled {
/// Should the image repeat horizontally
tile_x: bool,
/// Should the image repeat vertically
tile_y: bool,
/// The texture will repeat when the ratio between the *drawing dimensions* of texture and the
/// *original texture size* are above this value
stretch_value: f32,
},
}

/// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform).
Expand All @@ -64,62 +64,9 @@ 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 {
#[inline]
#[must_use]
pub fn as_vec(&self) -> Vec2 {
match self {
Anchor::Center => Vec2::ZERO,
Expand Down
Loading

0 comments on commit 9f85d5a

Please sign in to comment.