bones_framework/render/
sprite.rs

1//! Sprite rendering components.
2
3use crate::prelude::*;
4
5/// Sprite session plugin.
6pub fn sprite_plugin(_session: &mut SessionBuilder) {
7    Sprite::register_schema();
8    AtlasSprite::register_schema();
9}
10
11/// Image component.
12#[derive(Clone, HasSchema, Debug)]
13#[schema(opaque, no_default)]
14#[type_data(asset_loader(["png", "jpg", "jpeg"], ImageAssetLoader))]
15pub enum Image {
16    /// Loaded image data
17    Data(image::DynamicImage),
18    /// A reference to image data stored in the external bones renderer.
19    External(u32),
20}
21
22struct ImageAssetLoader;
23impl AssetLoader for ImageAssetLoader {
24    fn load(&self, _ctx: AssetLoadCtx, bytes: &[u8]) -> BoxedFuture<anyhow::Result<SchemaBox>> {
25        let bytes = bytes.to_vec();
26        Box::pin(async move {
27            Ok(SchemaBox::new(Image::Data(image::load_from_memory(
28                &bytes,
29            )?)))
30        })
31    }
32}
33
34/// Atlas image component.
35#[derive(Clone, HasSchema, Debug, Default)]
36#[repr(C)]
37#[type_data(metadata_asset("atlas"))]
38pub struct Atlas {
39    /// The image for the atlas.
40    pub image: Handle<Image>,
41    /// The size of each tile in the atlas.
42    pub tile_size: Vec2,
43    /// The number of rows in the atlas.
44    pub rows: u32,
45    /// The number of columns in the atlas.
46    pub columns: u32,
47    /// The amount of padding between tiles.
48    pub padding: Vec2,
49    /// The offset of the first tile from the top-left of the image.
50    pub offset: Vec2,
51
52    /// Map tile indices to extra collision metadata. This is optional and
53    /// may not be specified for all tiles, or at all.
54    pub tile_collision: SMap<String, AtlasCollisionTile>,
55}
56
57impl Atlas {
58    /// Get the position in pixels of the top-left corner of the atlas tile with the given index.
59    pub fn tile_pos(&self, idx: u32) -> Vec2 {
60        let row = idx / self.columns;
61        let col = idx % self.columns;
62        uvec2(col, row).as_vec2() * self.tile_size
63    }
64
65    /// Get the size in pixels of the entire atlas image.
66    pub fn size(&self) -> Vec2 {
67        uvec2(self.columns, self.rows).as_vec2() * self.tile_size
68    }
69}
70
71/// Metadata to define collider an atlas's tile.
72#[derive(Copy, Clone, HasSchema, Debug, Default)]
73#[repr(C)]
74pub struct AtlasCollisionTile {
75    /// min point on AABB
76    pub min: Vec2,
77    /// max point on AABB
78    pub max: Vec2,
79}
80
81impl AtlasCollisionTile {
82    /// Clamp values between range (0,0) and (1,1).
83    ///
84    /// How collision metadata is used is implementation specific,
85    /// but if using normalized values to scale with tile size,
86    /// this is useful for enforcing metadata is valid.
87    pub fn clamped_values(&self) -> AtlasCollisionTile {
88        let zero = Vec2::ZERO;
89        let one = Vec2::new(1.0, 1.0);
90        AtlasCollisionTile {
91            min: self.min.clamp(zero, one),
92            max: self.max.clamp(zero, one),
93        }
94    }
95
96    /// Return true if both components of max are greater than min.
97    /// false if equal on either axis
98    pub fn has_area(&self) -> bool {
99        let extent = self.max - self.min;
100        extent.x > 0.0 && extent.y > 0.0
101    }
102}
103
104/// A 2D sprite component
105#[derive(Clone, HasSchema, Debug, Default)]
106#[repr(C)]
107pub struct Sprite {
108    /// The sprite's color tint
109    pub color: Color,
110    /// The sprite image handle.
111    pub image: Handle<Image>,
112    /// Whether or not the flip the sprite horizontally.
113    pub flip_x: bool,
114    /// Whether or not the flip the sprite vertically.
115    pub flip_y: bool,
116}
117
118/// An animated sprite component.
119///
120/// Represents one or more [`Atlas`]s stacked on top of each other, and possibly animated through a
121/// range of frames out of the atlas.
122#[derive(Debug, Default, Clone, HasSchema)]
123#[repr(C)]
124pub struct AtlasSprite {
125    /// The sprite's color tint
126    pub color: Color,
127    /// This is the current index in the animation, with an `idx` of `0` meaning that the index in
128    /// the sprite sheet will be `start`.
129    ///
130    /// If the idx is greater than `end - start`, then the animation will loop around.
131    pub index: u32,
132    /// The atlas handle.
133    pub atlas: Handle<Atlas>,
134    /// Whether or not the flip the sprite horizontally.
135    pub flip_x: bool,
136    /// Whether or not the flip the sprite vertically.
137    pub flip_y: bool,
138}
139
140impl AtlasSprite {
141    /// Create a new [`AtlasSprite`] from the given atlas handle.
142    pub fn new(atlas: Handle<Atlas>) -> Self {
143        Self {
144            atlas,
145            color: Color::WHITE,
146            ..default()
147        }
148    }
149}