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

237 lines
5.6 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};
2024-01-05 12:09:59 -08:00
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
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>,
2024-01-05 12:09:59 -08:00
pub timing: Timing,
2024-01-03 07:46:27 -08:00
pub repeat: RepeatMode,
2024-01-05 12:09:59 -08:00
pub random_start_frame: Option<bool>,
}
#[derive(Debug, Deserialize)]
2024-01-07 12:16:07 -08:00
pub enum TimingVariant {
2024-01-05 12:09:59 -08:00
#[serde(rename = "duration")]
Duration(f32),
#[serde(rename = "fps")]
Fps(f32),
2024-01-03 07:46:27 -08:00
}
2024-01-07 12:16:07 -08:00
#[derive(Debug, Deserialize)]
pub struct Timing {
#[serde(flatten)]
pub variant: TimingVariant,
//pub uniform_rng: Option<f32>,
}
2024-01-03 07:46:27 -08:00
}
/// 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,
2024-01-04 21:41:15 -08:00
/// Play this animation in reverse after the last frame
#[serde(rename = "reverse")]
Reverse,
2024-01-03 07:46:27 -08:00
}
impl RepeatMode {
/// Represent this repeatmode as an integer
/// Used to pass this enum into shaders
pub fn as_int(&self) -> u32 {
match self {
2024-01-04 21:41:15 -08:00
Self::Repeat => 0,
Self::Once => 1,
Self::Reverse => 2,
2024-01-03 07:46:27 -08:00
}
}
}
/// 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.
2024-01-07 12:16:07 -08:00
/// This is zero for unanimate sprites.
pub frame_duration: f32,
/// All frames will be sped up/slowed by this factor.
//pub frame_uniform_rng: f32,
2024-01-03 07:46:27 -08:00
/// 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,
2024-01-05 12:09:59 -08:00
/// If true, start on a random frame of this sprite.
pub random_start_frame: bool,
}
impl crate::Build for Sprite {
type InputSyntaxType = HashMap<String, syntax::Sprite>;
2024-01-05 12:09:59 -08:00
fn build(
sprites: Self::InputSyntaxType,
_build_context: &mut ContentBuildContext,
content: &mut Content,
) -> Result<()> {
for (sprite_name, t) in sprites {
2024-01-03 07:46:27 -08:00
match t {
syntax::Sprite::Static(t) => {
2024-01-05 12:09:59 -08:00
let file = content.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-05 12:09:59 -08:00
index: content.sprites.len(),
2024-01-03 07:46:27 -08:00
aspect: dim.0 as f32 / dim.1 as f32,
};
2024-01-05 12:09:59 -08:00
if sprite_name == content.starfield_sprite_name {
if content.starfield_handle.is_none() {
content.starfield_handle = Some(h)
2024-01-03 07:46:27 -08:00
} 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
}
}
2024-01-05 12:09:59 -08:00
content.sprite_index.insert(sprite_name.clone(), h);
2024-01-03 07:46:27 -08:00
2024-01-05 12:09:59 -08:00
content.sprites.push(Self {
name: sprite_name,
frames: vec![t.file],
2024-01-07 12:16:07 -08:00
frame_duration: 0.0,
//frame_uniform_rng: 0.0,
2024-01-03 07:46:27 -08:00
handle: h,
repeat: RepeatMode::Once,
aspect: dim.0 as f32 / dim.1 as f32,
2024-01-05 12:09:59 -08:00
random_start_frame: false,
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 {
2024-01-05 12:09:59 -08:00
let file = content.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-05 12:09:59 -08:00
index: content.sprites.len(),
aspect: dim.0 as f32 / dim.1 as f32,
2024-01-03 07:46:27 -08:00
};
2024-01-05 12:09:59 -08:00
if sprite_name == content.starfield_sprite_name {
2024-01-03 07:46:27 -08:00
unreachable!("Starfield texture may not be animated")
}
2024-01-07 12:16:07 -08:00
let frame_duration = match t.timing.variant {
syntax::TimingVariant::Duration(d) => d / t.frames.len() as f32,
syntax::TimingVariant::Fps(f) => 1.0 / f,
2024-01-05 12:09:59 -08:00
};
2024-01-03 07:46:27 -08:00
2024-01-05 12:09:59 -08:00
content.sprite_index.insert(sprite_name.clone(), h);
content.sprites.push(Self {
name: sprite_name,
frames: t.frames,
2024-01-07 12:16:07 -08:00
frame_duration,
//frame_uniform_rng: t.timing.uniform_rng.unwrap_or(0.0),
2024-01-03 07:46:27 -08:00
handle: h,
repeat: t.repeat,
aspect: dim.0 as f32 / dim.1 as f32,
2024-01-05 12:09:59 -08:00
random_start_frame: t.random_start_frame.unwrap_or(false),
2024-01-03 07:46:27 -08:00
});
}
}
}
2024-01-05 12:09:59 -08:00
if content.starfield_handle.is_none() {
bail!(
"Could not find a starfield texture (name: `{}`)",
2024-01-05 12:09:59 -08:00
content.starfield_sprite_name
)
}
return Ok(());
}
}