Skip to content

Commit

Permalink
Sprite draw mode
Browse files Browse the repository at this point in the history
  • Loading branch information
ManevilleF committed Jul 6, 2022
1 parent ffa6d0e commit ff13748
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 114 deletions.
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ 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"
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub mod prelude {
pub use crate::{
bundle::{SpriteBundle, SpriteSheetBundle},
rect::{BorderRect, Rect},
sprite::Sprite,
sprite::{Sprite, SpriteDrawMode},
texture_atlas::{TextureAtlas, TextureAtlasSprite},
texture_slice::{SliceScaleMode, TextureSlicer},
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
Expand Down
1 change: 0 additions & 1 deletion crates/bevy_sprite/src/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ pub struct Rect {
}

/// Struct defining a [`Sprite`](crate::Sprite) border with padding values
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, Reflect)]
pub struct BorderRect {
/// Pixel padding to the left
Expand Down
83 changes: 44 additions & 39 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, TextureSlicer, SPRITE_SHADER_HANDLE,
Rect, Sprite, SpriteDrawMode, TextureSlice, SPRITE_SHADER_HANDLE,
};
use bevy_asset::{AssetEvent, Assets, Handle, HandleId};
use bevy_core_pipeline::core_2d::Transparent2d;
Expand Down Expand Up @@ -224,13 +224,7 @@ pub fn extract_sprites(
mut render_world: ResMut<RenderWorld>,
texture_atlases: Res<Assets<TextureAtlas>>,
images: Res<Assets<Image>>,
sprite_query: Query<(
&Visibility,
&Sprite,
&GlobalTransform,
&Handle<Image>,
Option<&TextureSlicer>,
)>,
sprite_query: Query<(&Visibility, &Sprite, &GlobalTransform, &Handle<Image>)>,
atlas_query: Query<(
&Visibility,
&TextureAtlasSprite,
Expand All @@ -240,43 +234,54 @@ pub fn extract_sprites(
) {
let mut extracted_sprites = render_world.resource_mut::<ExtractedSprites>();
extracted_sprites.sprites.clear();
for (visibility, sprite, transform, handle, slicer) in sprite_query.iter() {
for (visibility, sprite, transform, handle) in sprite_query.iter() {
if !visibility.is_visible {
continue;
}
if let Some(slicer) = slicer {
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 =
slicer.compute_slices(image_size, sprite.custom_size.unwrap_or(image_size));
for slice in slices {
let mut transform: GlobalTransform = *transform;
transform.translation = transform.mul_vec3(slice.offset.extend(0.0));
extracted_sprites.sprites.alloc().init(ExtractedSprite {
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(),
});
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::Simple => vec![TextureSlice {
texture_rect: Rect {
min: Vec2::ZERO,
max: image_size,
},
draw_size: sprite.custom_size.unwrap_or(image_size),
offset: Vec2::ZERO,
}],
SpriteDrawMode::Sliced(slicer) => {
slicer.compute_slices(image_size, sprite.custom_size.unwrap_or(image_size))
}
} 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
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))
}
};
for slice in slices {
let mut transform: GlobalTransform = *transform;
transform.translation = transform.mul_vec3(slice.offset.extend(0.0));
extracted_sprites.sprites.alloc().init(ExtractedSprite {
color: sprite.color,
transform: *transform,
// Use the full texture
rect: None,
// Pass the custom size
custom_size: sprite.custom_size,
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,
Expand Down
22 changes: 22 additions & 0 deletions crates/bevy_sprite/src/sprite.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::TextureSlicer;
use bevy_ecs::component::Component;
use bevy_math::Vec2;
use bevy_reflect::Reflect;
Expand All @@ -17,6 +18,27 @@ pub struct Sprite {
pub custom_size: Option<Vec2>,
/// [`Anchor`] point of the sprite in the world
pub anchor: Anchor,
/// Define how the Sprite scales when its dimensions change
pub draw_mode: SpriteDrawMode,
}

#[derive(Debug, Default, Clone, Reflect)]
pub enum SpriteDrawMode {
/// The entire texture scales when its dimensions change. This is the default option.
#[default]
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 Down
48 changes: 31 additions & 17 deletions crates/bevy_sprite/src/texture_slice.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use crate::{BorderRect, Rect};
use bevy_ecs::component::Component;
use bevy_math::Vec2;
use bevy_reflect::Reflect;

/// Component for [9-sliced](https://en.wikipedia.org/wiki/9-slice_scaling) textures.
/// Slices a texture using the **9-slicing** technique. This allows to reuse an image at various sizes
/// without needing to prepare multiple assets. The associated texture will be split tinto nine portions,
/// so that on resize the different portions scale or tile in different ways to keep the texture in proportion.
///
/// When resizing a 9-sliced texture the corners will remain unscaled while the other sections will be scaled or tiled
#[derive(Debug, Clone, Component, Reflect)]
/// For example, when resizing a 9-sliced texture the corners will remain unscaled while the other
/// sections will be scaled or tiled.
///
/// See [9-sliced](https://en.wikipedia.org/wiki/9-slice_scaling) textures.
#[derive(Debug, Clone, Reflect)]
pub struct TextureSlicer {
/// The sprite borders, defining the 9 sections of the image
pub border: BorderRect,
Expand All @@ -28,7 +32,7 @@ pub enum SliceScaleMode {
Stretch,
/// The slice will be tiled to fit the area
Tile {
/// The will repeat when the ratio between the *drawing dimensions* of texture and the
/// The slice will repeat when the ratio between the *drawing dimensions* of texture and the
/// *original texture size* are above `stretch_value`.
///
/// Note: The value should be inferior or equal to `1.0` to avoid quality loss.
Expand Down Expand Up @@ -108,6 +112,7 @@ impl TextureSlicer {
]
}

/// Computes the 2 horizontal side slices (left and right borders)
pub(crate) fn horizontal_side_slices(
&self,
[tl_corner, tr_corner, bl_corner, br_corner]: &[TextureSlice; 4],
Expand Down Expand Up @@ -142,6 +147,7 @@ impl TextureSlicer {
[left_side, right_side]
}

/// Computes the 2 vertical side slices (top and bottom borders)
pub(crate) fn vertical_side_slices(
&self,
[tl_corner, tr_corner, bl_corner, br_corner]: &[TextureSlice; 4],
Expand Down Expand Up @@ -177,7 +183,7 @@ impl TextureSlicer {
}

pub(crate) fn compute_slices(&self, image_size: Vec2, render_size: Vec2) -> Vec<TextureSlice> {
let mut res = Vec::with_capacity(9);
let mut slices = Vec::with_capacity(9);
// Corners
let corners = self.corner_slices(image_size, render_size);
// Sides
Expand All @@ -199,38 +205,46 @@ impl TextureSlicer {
offset: Vec2::ZERO,
};
// Res
res.extend(corners);
slices.extend(corners);
match self.center_scale_mode {
SliceScaleMode::Stretch => {
res.push(center);
slices.push(center);
}
SliceScaleMode::Tile { stretch_value } => {
res.extend(center.tiled(stretch_value, (true, true)));
slices.extend(center.tiled(stretch_value, (true, true)));
}
}
match self.sides_scale_mode {
SliceScaleMode::Stretch => {
res.extend(horizontal_sides);
res.extend(vertical_sides);
slices.extend(horizontal_sides);
slices.extend(vertical_sides);
}
SliceScaleMode::Tile { stretch_value } => {
res.extend(
slices.extend(
horizontal_sides
.into_iter()
.flat_map(|s| s.tiled(stretch_value, (false, true))),
);
res.extend(
slices.extend(
vertical_sides
.into_iter()
.flat_map(|s| s.tiled(stretch_value, (true, false))),
);
}
}
res
slices
}
}

impl TextureSlice {
/// Transforms the given slice in an collection of tiled subdivisions.
///
/// # Arguments
///
/// * `stretch_value` - The slice will repeat when the ratio between the *drawing dimensions* of texture and the
/// *original texture size* are above `stretch_value`.
/// - `tile_x` - should the slice be tiled horizontally
/// - `tile_y` - should the slice be tiled vertically
pub fn tiled(self, stretch_value: f32, (tile_x, tile_y): (bool, bool)) -> Vec<Self> {
if !tile_x && !tile_y {
return vec![self];
Expand All @@ -249,7 +263,7 @@ impl TextureSlice {
self.draw_size.y
},
);
let mut res = Vec::new();
let mut slices = Vec::new();
let base_offset = -self.draw_size / 2.0;
let mut offset = base_offset;

Expand All @@ -264,7 +278,7 @@ impl TextureSlice {
offset.x += size_x / 2.0;
let draw_size = Vec2::new(size_x, size_y);
let delta = draw_size / expected_size;
res.push(Self {
slices.push(Self {
texture_rect: Rect {
min: self.texture_rect.min,
max: self.texture_rect.min + self.texture_rect.size() * delta,
Expand All @@ -278,7 +292,7 @@ impl TextureSlice {
offset.y += size_y / 2.0;
remaining_columns -= size_y;
}
res
slices
}
}

Expand Down
Loading

0 comments on commit ff13748

Please sign in to comment.