131 lines
3.1 KiB
Rust
131 lines
3.1 KiB
Rust
|
use image::io::Reader as ImageReader;
|
||
|
use sdl2::{
|
||
|
rect::{Point, Rect},
|
||
|
render::{Canvas, Texture, TextureCreator},
|
||
|
video::{Window, WindowContext},
|
||
|
};
|
||
|
|
||
|
use std::collections::HashMap;
|
||
|
|
||
|
use crate::physics::PhysVec;
|
||
|
|
||
|
/// A handle for a sprite inside a SpriteAtlas
|
||
|
pub struct Sprite<'a> {
|
||
|
texture: &'a Texture<'a>,
|
||
|
rect: Rect,
|
||
|
scale: f64,
|
||
|
}
|
||
|
|
||
|
impl<'a> Sprite<'a> {
|
||
|
/// Draw this sprite on the screen.
|
||
|
///
|
||
|
/// Position represents the center of the sprite
|
||
|
/// on-screen position, NOT in the world.
|
||
|
pub fn draw(
|
||
|
&self,
|
||
|
canvas: &mut Canvas<Window>,
|
||
|
position: PhysVec,
|
||
|
angle: f64,
|
||
|
scale: f64,
|
||
|
) -> Result<(), String> {
|
||
|
let win_size = PhysVec::from(canvas.window().size());
|
||
|
let scale = scale * self.scale;
|
||
|
|
||
|
// Post-scale dimensions on the screen
|
||
|
let width = self.rect.width() as f64 * scale;
|
||
|
let height = self.rect.height() as f64 * scale;
|
||
|
|
||
|
// Don't draw if we're not on the screen.
|
||
|
// An offset is included to ensure we're completely
|
||
|
// off the screen. We add the whole width intentionally.
|
||
|
if position.x < -1.0 * (width as f64)
|
||
|
|| position.x > win_size.x + width as f64
|
||
|
|| position.y < -1.0 * (height as f64)
|
||
|
|| position.y > win_size.y + height as f64
|
||
|
{
|
||
|
return Ok(());
|
||
|
}
|
||
|
|
||
|
let mut dest = Rect::new(0, 0, width as u32, height as u32);
|
||
|
dest.center_on(Point::new((position.x) as i32, (position.y) as i32));
|
||
|
|
||
|
// copy the frame to the canvas
|
||
|
canvas.copy_ex(
|
||
|
&self.texture,
|
||
|
Some(self.rect),
|
||
|
Some(dest),
|
||
|
angle, // angle
|
||
|
Point::new((width / 2.0) as i32, (height / 2.0) as i32), // Rotation center
|
||
|
false,
|
||
|
false,
|
||
|
)?;
|
||
|
|
||
|
return Ok(());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// A cache of textures we use when drawing the screen.
|
||
|
///
|
||
|
/// This is implemented very carefully, since SDL2 textures have tricky lifetimes.
|
||
|
pub struct SpriteAtlas<'a> {
|
||
|
data: HashMap<String, (Texture<'a>, Rect, f64)>,
|
||
|
}
|
||
|
|
||
|
impl<'a> SpriteAtlas<'a> {
|
||
|
pub fn new(texture_creator: &'a TextureCreator<WindowContext>) -> Result<Self, String> {
|
||
|
let mut b = Self {
|
||
|
data: HashMap::new(),
|
||
|
};
|
||
|
|
||
|
b.load_one(texture_creator, "gypsum.png", 0.75)?;
|
||
|
b.load_one(texture_creator, "a0.png", 1.0)?;
|
||
|
b.load_one(texture_creator, "small.png", 1.0)?;
|
||
|
b.load_one(texture_creator, "earth.png", 1.0)?;
|
||
|
|
||
|
return Ok(b);
|
||
|
}
|
||
|
|
||
|
pub fn get(&'a self, name: &str) -> Sprite<'a> {
|
||
|
let (texture, rect, scale) = self.data.get(name).unwrap();
|
||
|
return Sprite {
|
||
|
texture,
|
||
|
scale: scale.clone(),
|
||
|
rect: rect.clone(),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
fn load_one(
|
||
|
&mut self,
|
||
|
texture_creator: &'a TextureCreator<WindowContext>,
|
||
|
s: &str,
|
||
|
scale: f64,
|
||
|
) -> Result<(), String> {
|
||
|
let im = ImageReader::open(format!("assets/{s}"))
|
||
|
.unwrap()
|
||
|
.decode()
|
||
|
.unwrap();
|
||
|
let width = im.width();
|
||
|
let height = im.height();
|
||
|
let mut im = im.as_bytes().to_vec();
|
||
|
|
||
|
let surface = sdl2::surface::Surface::from_data(
|
||
|
&mut im,
|
||
|
width,
|
||
|
height,
|
||
|
width * 4,
|
||
|
sdl2::pixels::PixelFormatEnum::RGBA32,
|
||
|
)?;
|
||
|
|
||
|
let texture = texture_creator
|
||
|
.create_texture_from_surface(&surface)
|
||
|
.map_err(|e| e.to_string())?;
|
||
|
|
||
|
self.data.insert(
|
||
|
s.to_owned(),
|
||
|
(texture, Rect::new(0, 0, width, height), scale),
|
||
|
);
|
||
|
|
||
|
return Ok(());
|
||
|
}
|
||
|
}
|