specs_blit/lib.rs
1//! # Specs ECS Rendering System
2//!
3//! This library exposes a 2D rendering system to be used in [specs](https://github.com/amethyst/specs).
4//! It is based around the [blit](https://github.com/tversteeg/blit) library.
5//! All the images will be rendered to a buffer which can be used in various
6//! graphics libraries, e.g. minifb.
7//!
8//! All sprites are loaded onto a big array on the heap.
9//! ```rust
10//! use anyhow::Result;
11//! use blit::{BlitBuffer, Color};
12//! use specs::prelude::*;
13//! use specs_blit::{load, PixelBuffer, RenderSystem, Sprite};
14//! use rotsprite::rotsprite;
15//!
16//! const WIDTH: usize = 800;
17//! const HEIGHT: usize = 800;
18//! const MASK_COLOR: u32 = 0xFF00FF;
19//!
20//! fn main() -> Result<()> {
21//! // Setup the specs world
22//! let mut world = World::new();
23//!
24//! // Load the blit components into the world
25//! world.register::<Sprite>();
26//!
27//! // Add the pixel buffer as a resource so it can be accessed from the RenderSystem later
28//! world.insert(PixelBuffer::new(WIDTH, HEIGHT));
29//!
30//! let sprite_ref = {
31//! // Create a sprite of 4 pixels
32//! let sprite = BlitBuffer::from_buffer(&[0, MASK_COLOR, 0, 0], 2, MASK_COLOR);
33//!
34//! // Load the sprite and get a reference
35//! load(sprite)?
36//! };
37//!
38//! // Create a new sprite entity in the ECS system
39//! world.create_entity()
40//! .with(Sprite::new(sprite_ref))
41//! .build();
42//!
43//! // Setup the dispatcher with the blit system
44//! let mut dispatcher = DispatcherBuilder::new()
45//! .with_thread_local(RenderSystem)
46//! .build();
47//!
48//! Ok(())
49//! }
50//! ```
51
52pub extern crate blit;
53pub extern crate specs;
54
55use anyhow::Result;
56use blit::BlitBuffer;
57use lazy_static::lazy_static;
58#[cfg(feature = "parallel")]
59use rayon::prelude::*;
60use specs::prelude::*;
61use std::sync::RwLock;
62
63// The heap allocated array of sprites
64// It's wrapped in a RwLock so all threads can access it
65lazy_static! {
66 static ref SPRITES: RwLock<Vec<BlitBuffer>> = RwLock::new(vec![]);
67}
68
69/// Specs component representing a sprite that can be drawn.
70///
71/// ```rust
72/// use blit::{BlitBuffer, Color};
73/// use specs::prelude::*;
74/// use specs_blit::{load, Sprite};
75///
76/// const MASK_COLOR: u32 = 0xFF00FF;
77///
78/// # fn main() -> anyhow::Result<()> {
79/// // Setup the specs world
80/// let mut world = World::new();
81///
82/// // Load the blit components into the world
83/// world.register::<Sprite>();
84///
85/// let sprite_ref = {
86/// // Create a sprite of 4 pixels
87/// let sprite = BlitBuffer::from_buffer(&[0, MASK_COLOR, 0, 0], 2, MASK_COLOR);
88///
89/// // Load the sprite and get a reference
90/// load(sprite)?
91/// };
92///
93/// // Create a new sprite entity in the ECS system
94/// world.create_entity()
95/// .with(Sprite::new(sprite_ref))
96/// .build();
97/// # Ok(())
98/// # }
99/// ```
100#[derive(Debug, Clone)]
101pub struct Sprite {
102 /// The reference to the heap allocated array of sprites.
103 pub(crate) reference: SpriteRef,
104 /// Where on the screen the sprite needs to be rendered.
105 pos: (i32, i32),
106 /// The current rotation of the sprite, it will match the nearest rotating divisor of the
107 /// loaded version.
108 rot: i16,
109}
110
111impl Component for Sprite {
112 type Storage = VecStorage<Self>;
113}
114
115impl Sprite {
116 /// Instantiate a new sprite from a loaded sprite index.
117 ///
118 /// ```rust
119 /// use blit::{BlitBuffer, Color};
120 /// use specs_blit::{load, Sprite};
121 ///
122 /// const MASK_COLOR: u32 = 0xFF00FF;
123 ///
124 /// # fn main() -> anyhow::Result<()> {
125 /// let sprite_ref = {
126 /// // Create a sprite of 4 pixels
127 /// let sprite = BlitBuffer::from_buffer(&[0, MASK_COLOR, 0, 0], 2, MASK_COLOR);
128 ///
129 /// // Load the sprite and get a reference
130 /// load(sprite)?
131 /// };
132 ///
133 /// // Create a specs sprite from the image
134 /// let sprite = Sprite::new(sprite_ref);
135 /// # Ok(())
136 /// # }
137 /// ```
138 pub fn new(sprite_reference: SpriteRef) -> Self {
139 Self {
140 reference: sprite_reference,
141 pos: (0, 0),
142 rot: 0,
143 }
144 }
145
146 /// Set the pixel position of where the sprite needs to be rendered.
147 pub fn set_pos(&mut self, x: i32, y: i32) {
148 self.pos.0 = x;
149 self.pos.1 = y;
150 }
151
152 /// Get the pixel position as an (x, y) tuple of where the sprite will be rendered.
153 pub fn pos(&self) -> (i32, i32) {
154 self.pos
155 }
156
157 /// Set the rotation in degrees of the sprite.
158 /// The rotation will attempt to match the nearest degrees of rotation divisor.
159 pub fn set_rot(&mut self, rotation: i16) {
160 let mut rotation = rotation;
161 while rotation < 0 {
162 rotation += 360;
163 }
164 while rotation > 360 {
165 rotation -= 360;
166 }
167
168 self.rot = rotation;
169 }
170
171 /// Get the pixel rotation as degrees.
172 pub fn rot(&self) -> i16 {
173 self.rot
174 }
175
176 /// Get the data needed for rendering this sprite.
177 pub(crate) fn render_info(&self) -> (usize, i32, i32) {
178 self.reference.render_info(self.rot)
179 }
180}
181
182/// Reference to a heap-allocated sprite.
183/// Contains the index of the vector, only this crate is allowed to access this.
184#[derive(Debug, Clone)]
185pub struct SpriteRef {
186 /// Start point of the rotation.
187 rot_range_start: i16,
188 /// In how many degrees the rotation is divided.
189 rot_divisor: f64,
190 /// Array of different rotations sprite references with their position offsets.
191 sprites: Vec<(usize, i32, i32)>,
192}
193
194impl SpriteRef {
195 // Return the reference index and the offsets of the position.
196 pub(crate) fn render_info(&self, rotation: i16) -> (usize, i32, i32) {
197 let rotation_index = (((rotation - self.rot_range_start) % 360) as f64) / self.rot_divisor;
198
199 // Return the proper sprite depending on the rotation
200 *self
201 .sprites
202 .get(rotation_index as usize)
203 // Get the sprite at the index or the first if that's not valid
204 .unwrap_or(&self.sprites[0])
205 }
206}
207
208/// Array of pixels resource that can be written to from the [`RenderSystem`] system.
209///
210/// ```rust
211/// use specs::prelude::*;
212/// use specs_blit::PixelBuffer;
213///
214/// const WIDTH: usize = 800;
215/// const HEIGHT: usize = 800;
216///
217/// // Setup the specs world
218/// let mut world = World::new();
219///
220/// // Add the pixel buffer as a resource so it can be accessed from the RenderSystem later
221/// world.insert(PixelBuffer::new(WIDTH, HEIGHT));
222/// ```
223#[derive(Debug, Default)]
224pub struct PixelBuffer {
225 pub(crate) pixels: Vec<u32>,
226 pub(crate) width: usize,
227 pub(crate) height: usize,
228}
229
230impl PixelBuffer {
231 /// Create a new buffer filled with black pixels.
232 pub fn new(width: usize, height: usize) -> Self {
233 Self {
234 pixels: vec![0; width * height],
235 width,
236 height,
237 }
238 }
239
240 /// Get the array of pixels.
241 pub fn pixels(&self) -> &Vec<u32> {
242 &self.pixels
243 }
244
245 /// Get the array so that it can be mutated manually.
246 pub fn pixels_mut(&mut self) -> &mut Vec<u32> {
247 &mut self.pixels
248 }
249
250 /// Get the width in pixels.
251 pub fn width(&self) -> usize {
252 self.width
253 }
254
255 /// Get the height in pixels.
256 pub fn height(&self) -> usize {
257 self.height
258 }
259
260 /// Set all the pixels to the passed color.
261 pub fn clear(&mut self, color: u32) {
262 for pixel in self.pixels.iter_mut() {
263 *pixel = color;
264 }
265 }
266}
267
268/// Specs system for rendering sprites to a buffer.
269///
270/// *Note*: This can only be used in conjunction with a `.with_thread_local()`
271/// function in specs and not with a normal `.with()` call.
272///
273/// ```rust
274/// use specs::prelude::*;
275/// use specs_blit::RenderSystem;
276///
277/// let mut dispatcher = DispatcherBuilder::new()
278/// // Expose the sprite render system to specs
279/// .with_thread_local(RenderSystem)
280/// .build();
281/// ```
282pub struct RenderSystem;
283impl<'a> System<'a> for RenderSystem {
284 type SystemData = (Write<'a, PixelBuffer>, ReadStorage<'a, Sprite>);
285
286 fn run(&mut self, (mut buffer, sprites): Self::SystemData) {
287 let width = buffer.width;
288
289 for sprite_component in sprites.join() {
290 let (index, x_offset, y_offset) = sprite_component.render_info();
291
292 // Get the sprite from the array
293 let sprite = &SPRITES.read().unwrap()[index];
294
295 let pos = (
296 sprite_component.pos.0 + x_offset,
297 sprite_component.pos.1 + y_offset,
298 );
299
300 // Draw the sprite on the buffer
301 sprite.blit(&mut buffer.pixels, width, pos);
302 }
303 }
304}
305
306/// Load a sprite buffer and place it onto the heap.
307///
308/// Returns an index that can be used in sprite components.
309///
310/// ```rust
311/// use blit::{BlitBuffer, Color};
312/// use specs_blit::load;
313///
314/// const MASK_COLOR: u32 = 0xFF00FF;
315///
316/// # fn main() -> anyhow::Result<()> {
317/// // Create a sprite of 4 pixels
318/// let sprite = BlitBuffer::from_buffer(&[0, MASK_COLOR, 0, 0], 2, MASK_COLOR);
319///
320/// // Load the sprite and get a reference
321/// let sprite_ref = load(sprite)?;
322/// # Ok(())
323/// # }
324/// ```
325pub fn load(sprite: BlitBuffer) -> Result<SpriteRef> {
326 load_rotations(sprite, 1)
327}
328
329/// Load a sprite buffer and place it onto the heap with a set amount of rotations.
330///
331/// Calls `load_rotations_range` with a range of `(0.0, 360.0)`.
332///
333/// Returns an index that can be used in sprite components.
334pub fn load_rotations(sprite: BlitBuffer, rotations: u16) -> Result<SpriteRef> {
335 load_rotations_range(sprite, rotations, (0, 360))
336}
337
338/// Load a sprite buffer and place it onto the heap with a set amount of rotations.
339///
340/// Returns an index that can be used in sprite components.
341///
342/// ```rust
343/// use blit::{BlitBuffer, Color};
344/// use specs_blit::load;
345///
346/// const MASK_COLOR: u32 = 0xFF00FF;
347///
348/// # fn main() -> anyhow::Result<()> {
349/// // Create a sprite of 4 pixels
350/// let sprite = BlitBuffer::from_buffer(&[0, MASK_COLOR, 0, 0], 2, MASK_COLOR);
351///
352/// // Load the sprite in rotations of -15, 0, 15 degrees and get a reference
353/// let sprite_ref = load_rotations_range(sprite, 3, (-15.0, 15.0))?;
354/// # Ok(())
355/// # }
356/// ```
357#[cfg(not(feature = "parallel"))]
358pub fn load_rotations_range(
359 sprite: BlitBuffer,
360 rotations: u16,
361 range: (i16, i16),
362) -> Result<SpriteRef> {
363 let rotations = if rotations == 0 { 1 } else { rotations };
364
365 let rot_divisor = (range.1 - range.0) as f64 / (rotations as f64);
366 let raw_buffer = sprite.to_raw_buffer();
367
368 // Create a rotation sprite for all rotations
369 let sprites = (0..rotations)
370 .into_iter()
371 .map(|r| {
372 let (rotated_width, rotated_height, rotated) = rotsprite::rotsprite(
373 &raw_buffer,
374 &sprite.mask_color().u32(),
375 sprite.size().0 as usize,
376 range.0 as f64 + (r as f64 * rot_divisor),
377 )?;
378
379 let rotated_sprite =
380 BlitBuffer::from_buffer(&rotated, rotated_width as i32, sprite.mask_color());
381
382 let mut sprites_vec = SPRITES.write().unwrap();
383 sprites_vec.push(rotated_sprite);
384
385 let index = sprites_vec.len() - 1;
386
387 let x_offset = (sprite.width() - rotated_width as i32) / 2;
388 let y_offset = (sprite.height() - rotated_height as i32) / 2;
389
390 Ok((index, x_offset, y_offset))
391 })
392 .collect::<Result<Vec<_>>>()?
393 // Return the first error
394 .into_iter()
395 .collect::<_>();
396
397 Ok(SpriteRef {
398 rot_range_start: range.0,
399 rot_divisor,
400 sprites,
401 })
402}
403
404#[cfg(feature = "parallel")]
405pub fn load_rotations_range(
406 sprite: BlitBuffer,
407 rotations: u16,
408 range: (i16, i16),
409) -> Result<SpriteRef> {
410 let rotations = if rotations == 0 { 1 } else { rotations };
411
412 let rot_divisor = (range.1 - range.0) as f64 / (rotations as f64);
413 let raw_buffer = sprite.to_raw_buffer();
414
415 // Create a rotation sprite for all rotations
416 let sprites = (0..rotations)
417 .into_par_iter()
418 .map(|r| {
419 let (rotated_width, rotated_height, rotated) = rotsprite::rotsprite(
420 &raw_buffer,
421 &sprite.mask_color().u32(),
422 sprite.size().0 as usize,
423 range.0 as f64 + (r as f64 * rot_divisor),
424 )?;
425
426 let rotated_sprite =
427 BlitBuffer::from_buffer(&rotated, rotated_width as i32, sprite.mask_color());
428
429 let mut sprites_vec = SPRITES.write().unwrap();
430 sprites_vec.push(rotated_sprite);
431
432 let index = sprites_vec.len() - 1;
433
434 let x_offset = (sprite.width() - rotated_width as i32) / 2;
435 let y_offset = (sprite.height() - rotated_height as i32) / 2;
436
437 Ok((index, x_offset, y_offset))
438 })
439 .collect::<Result<Vec<_>>>()?
440 // Return the first error
441 .into_iter()
442 .collect::<_>();
443
444 Ok(SpriteRef {
445 rot_range_start: range.0,
446 rot_divisor,
447 sprites,
448 })
449}
450
451/// Delete all cached buffers.
452///
453/// Marked unsafe because it will invalidate all sprite references.
454pub unsafe fn clear_all() {
455 SPRITES.write().unwrap().clear();
456}