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