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}