Galactica/crates/content/src/part/sprite.rs

198 lines
4.5 KiB
Rust
Raw Normal View History

use anyhow::{bail, Context, Result};
use image::io::Reader;
2024-01-03 07:46:27 -08:00
use serde::Deserialize;
use std::{collections::HashMap, path::PathBuf};
use crate::{handle::SpriteHandle, Content};
2023-12-30 16:57:03 -08:00
pub(crate) mod syntax {
2024-01-03 07:46:27 -08:00
use serde::Deserialize;
use std::path::PathBuf;
2024-01-03 07:46:27 -08:00
use super::RepeatMode;
// Raw serde syntax structs.
// These are never seen by code outside this crate.
#[derive(Debug, Deserialize)]
2024-01-03 07:46:27 -08:00
#[serde(untagged)]
pub enum Sprite {
Static(StaticSprite),
Frames(FrameSprite),
2024-01-03 07:46:27 -08:00
}
#[derive(Debug, Deserialize)]
pub struct StaticSprite {
2024-01-03 07:46:27 -08:00
pub file: PathBuf,
}
#[derive(Debug, Deserialize)]
pub struct FrameSprite {
2024-01-03 07:46:27 -08:00
pub frames: Vec<PathBuf>,
pub duration: f32,
pub repeat: RepeatMode,
}
}
/// How to replay a texture's animation
#[derive(Debug, Deserialize, Clone, Copy)]
pub enum RepeatMode {
/// Play this animation once, and stop at the last frame
#[serde(rename = "once")]
Once,
/// After the first frame, jump to the last frame
#[serde(rename = "repeat")]
Repeat,
}
impl RepeatMode {
/// Represent this repeatmode as an integer
/// Used to pass this enum into shaders
pub fn as_int(&self) -> u32 {
match self {
Self::Once => 0,
Self::Repeat => 1,
}
}
}
/// Represents a sprite that may be used in the game.
#[derive(Debug, Clone)]
pub struct Sprite {
/// The name of this sprite
pub name: String,
/// This sprite's handle
pub handle: SpriteHandle,
/// The file names of frames of this sprite.
/// unanimated sprites have one frame.
2024-01-03 07:46:27 -08:00
pub frames: Vec<PathBuf>,
/// The speed of this sprite's animation.
/// unanimated sprites have zero fps.
2024-01-03 07:46:27 -08:00
pub fps: f32,
/// How to replay this sprite's animation
2024-01-03 07:46:27 -08:00
pub repeat: RepeatMode,
/// Aspect ratio of this sprite (width / height)
pub aspect: f32,
}
impl crate::Build for Sprite {
type InputSyntaxType = HashMap<String, syntax::Sprite>;
fn build(sprites: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
for (sprite_name, t) in sprites {
2024-01-03 07:46:27 -08:00
match t {
syntax::Sprite::Static(t) => {
let file = ct.image_root.join(&t.file);
2024-01-03 07:46:27 -08:00
let reader = Reader::open(&file).with_context(|| {
format!(
"Failed to read file `{}` in sprite `{}`",
file.display(),
sprite_name,
2024-01-03 07:46:27 -08:00
)
})?;
let dim = reader.into_dimensions().with_context(|| {
format!(
"Failed to get dimensions of file `{}` in sprite `{}`",
file.display(),
sprite_name,
2024-01-03 07:46:27 -08:00
)
})?;
let h = SpriteHandle {
2024-01-04 18:15:30 -08:00
index: ct.sprites.len() as u32,
2024-01-03 07:46:27 -08:00
aspect: dim.0 as f32 / dim.1 as f32,
};
if sprite_name == ct.starfield_sprite_name {
2024-01-03 07:46:27 -08:00
if ct.starfield_handle.is_none() {
ct.starfield_handle = Some(h)
} else {
// This can't happen, since this is a hashmap.
unreachable!("Found two starfield sprites! Something is very wrong.")
2024-01-03 07:46:27 -08:00
}
}
ct.sprite_index.insert(sprite_name.clone(), h);
2024-01-03 07:46:27 -08:00
ct.sprites.push(Self {
name: sprite_name,
frames: vec![t.file],
2024-01-03 07:46:27 -08:00
fps: 0.0,
handle: h,
repeat: RepeatMode::Once,
aspect: dim.0 as f32 / dim.1 as f32,
2024-01-03 07:46:27 -08:00
});
}
syntax::Sprite::Frames(t) => {
2024-01-03 07:46:27 -08:00
let mut dim = None;
for f in &t.frames {
let file = ct.image_root.join(f);
2024-01-03 07:46:27 -08:00
let reader = Reader::open(&file).with_context(|| {
format!(
"Failed to read file `{}` in sprite `{}`",
file.display(),
sprite_name,
2024-01-03 07:46:27 -08:00
)
})?;
let d = reader.into_dimensions().with_context(|| {
format!(
"Failed to get dimensions of file `{}` in sprite `{}`",
file.display(),
sprite_name,
2024-01-03 07:46:27 -08:00
)
})?;
match dim {
None => dim = Some(d),
Some(e) => {
if d != e {
bail!(
"Failed to load frames of sprite `{}` because frames have different sizes.",
sprite_name,
2024-01-03 07:46:27 -08:00
)
}
}
}
}
let dim = dim.unwrap();
2024-01-03 07:46:27 -08:00
let h = SpriteHandle {
2024-01-04 18:15:30 -08:00
index: ct.sprites.len() as u32,
aspect: dim.0 as f32 / dim.1 as f32,
2024-01-03 07:46:27 -08:00
};
if sprite_name == ct.starfield_sprite_name {
2024-01-03 07:46:27 -08:00
unreachable!("Starfield texture may not be animated")
}
2024-01-03 07:46:27 -08:00
let fps = t.duration / t.frames.len() as f32;
ct.sprite_index.insert(sprite_name.clone(), h);
ct.sprites.push(Self {
name: sprite_name,
frames: t.frames,
2024-01-03 07:46:27 -08:00
fps,
handle: h,
repeat: t.repeat,
aspect: dim.0 as f32 / dim.1 as f32,
2024-01-03 07:46:27 -08:00
});
}
}
}
if ct.starfield_handle.is_none() {
bail!(
"Could not find a starfield texture (name: `{}`)",
ct.starfield_sprite_name
)
}
return Ok(());
}
}