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}