use thiserror::Error;
use crate::atlas::{Atlas, AtlasRegion};
use crate::data::attachment::{
BoundingBoxAttachment, ClippingAttachment, MeshAttachment, PathAttachment, PointAttachment,
RegionAttachment, Sequence, TextureRegionRef,
};
#[derive(Debug, Error, PartialEq, Eq)]
pub enum AttachmentLoaderError {
#[error("atlas region not found: {path:?} (attachment {attachment:?})")]
RegionNotFound { path: String, attachment: String },
#[error("attachment {attachment:?} is unsupported by this loader")]
Unsupported { attachment: String },
}
pub trait AttachmentLoader {
fn new_region_attachment(
&mut self,
skin: &str,
placeholder: &str,
name: &str,
path: &str,
sequence: Sequence,
) -> Result<Option<RegionAttachment>, AttachmentLoaderError>;
fn new_mesh_attachment(
&mut self,
skin: &str,
placeholder: &str,
name: &str,
path: &str,
sequence: Sequence,
) -> Result<Option<MeshAttachment>, AttachmentLoaderError>;
fn new_bounding_box_attachment(
&mut self,
skin: &str,
placeholder: &str,
name: &str,
) -> Result<Option<BoundingBoxAttachment>, AttachmentLoaderError> {
let _ = (skin, placeholder);
Ok(Some(BoundingBoxAttachment::new(name)))
}
fn new_path_attachment(
&mut self,
skin: &str,
placeholder: &str,
name: &str,
) -> Result<Option<PathAttachment>, AttachmentLoaderError> {
let _ = (skin, placeholder);
Ok(Some(PathAttachment::new(name)))
}
fn new_point_attachment(
&mut self,
skin: &str,
placeholder: &str,
name: &str,
) -> Result<Option<PointAttachment>, AttachmentLoaderError> {
let _ = (skin, placeholder);
Ok(Some(PointAttachment::new(name)))
}
fn new_clipping_attachment(
&mut self,
skin: &str,
placeholder: &str,
name: &str,
) -> Result<Option<ClippingAttachment>, AttachmentLoaderError> {
let _ = (skin, placeholder);
Ok(Some(ClippingAttachment::new(name)))
}
}
pub struct AtlasAttachmentLoader<'atlas> {
atlas: &'atlas Atlas,
}
impl<'atlas> AtlasAttachmentLoader<'atlas> {
#[must_use]
pub fn new(atlas: &'atlas Atlas) -> Self {
Self { atlas }
}
fn find_regions(&self, base_path: &str, sequence: &mut Sequence) {
for i in 0..sequence.regions.len() {
let path = sequence.path(base_path, i);
sequence.regions[i] = self
.atlas
.find_region(&path)
.map(|r| region_ref(self.atlas, r));
}
}
}
#[must_use]
pub fn region_ref(atlas: &Atlas, r: &AtlasRegion) -> TextureRegionRef {
let page = &atlas.pages[r.page as usize];
let (packed_width, packed_height) = if r.degrees == 90 {
(r.height as f32, r.width as f32)
} else {
(r.width as f32, r.height as f32)
};
TextureRegionRef {
page_index: page.index,
atlas: true,
u: r.u,
v: r.v,
u2: r.u2,
v2: r.v2,
packed_width,
packed_height,
original_width: r.original_width as f32,
original_height: r.original_height as f32,
offset_x: r.offset_x,
offset_y: r.offset_y,
degrees: r.degrees,
page_width: page.width as f32,
page_height: page.height as f32,
}
}
impl AttachmentLoader for AtlasAttachmentLoader<'_> {
fn new_region_attachment(
&mut self,
_skin: &str,
_placeholder: &str,
name: &str,
path: &str,
mut sequence: Sequence,
) -> Result<Option<RegionAttachment>, AttachmentLoaderError> {
self.find_regions(path, &mut sequence);
Ok(Some(RegionAttachment::new(name, sequence)))
}
fn new_mesh_attachment(
&mut self,
_skin: &str,
_placeholder: &str,
name: &str,
path: &str,
mut sequence: Sequence,
) -> Result<Option<MeshAttachment>, AttachmentLoaderError> {
self.find_regions(path, &mut sequence);
Ok(Some(MeshAttachment::new(name, sequence)))
}
}
#[cfg(test)]
mod tests {
use super::*;
const ATLAS: &str = "\
page.png
size: 64, 32
filter: Linear, Linear
body
bounds: 0, 0, 16, 16
run1
bounds: 16, 0, 16, 16
run2
bounds: 32, 0, 16, 8
rotate: 90
";
#[test]
fn resolves_region_and_leaves_missing_frames_empty() {
let atlas = Atlas::parse(ATLAS).unwrap();
let mut loader = AtlasAttachmentLoader::new(&atlas);
let body = loader
.new_region_attachment("default", "body", "body", "body", Sequence::new(1, false))
.unwrap()
.unwrap();
assert!(body.sequence.region(0).is_some());
let mut seq = Sequence::new(3, true);
seq.start = 1;
let run = loader
.new_mesh_attachment("default", "run", "run", "run", seq)
.unwrap()
.unwrap();
assert!(run.sequence.region(0).is_some());
let r2 = run.sequence.region(1).unwrap();
assert_eq!((r2.packed_width, r2.packed_height), (8.0, 16.0));
assert!(run.sequence.region(2).is_none());
}
}