bevy_chair 0.10.0

Chair mesh asset loader plugin
Documentation
use std::collections::HashMap;

use bevy_asset::*;
use bevy_ecs::prelude::*;
use bevy_render::prelude::Image;
use bevy_sprite::{TextureAtlas, TextureAtlasBuilder};
use chair::*;
use log::*;

/// Interface for chair to use a bevy atlas.
/// If using this in a system the system must use the run criteria `run_after_chair` or the startup system set creator `after_chair()`
/// Textures are loaded from `assets/textures` which can be changed with `ChairConfig`
/// # Example
/// ```
/// fn system(atlas: Res<ChairAtlas>) {
/// 	let texture = atlas.texture();
/// }
/// ```
#[derive(Clone, Debug, Resource)]
pub struct ChairAtlas(pub &'static RawChairAtlas);

impl ChairAtlas {
	/// Get a handle to the atlas texture
	pub fn texture(&self) -> Handle<Image> {
		self.0.texture.clone()
	}

	/// Get a handle to the atlas itself
	pub fn atlas(&self) -> Handle<TextureAtlas> {
		self.0.atlas.clone()
	}
}

/// For internal setup only
#[derive(Debug, Resource)]
pub struct MutChairAtlas(pub(crate) &'static mut RawChairAtlas);

impl MutChairAtlas {
	pub fn new(atlas: RawChairAtlas) -> Self {
		Self(Box::leak(Box::new(atlas)))
	}
}

/// In systems use Res<ChairAtlas>, you probably shouldn't touch this directly
#[derive(Debug)]
pub struct RawChairAtlas {
	base: &'static str,
	pub(crate) handles: Vec<HandleUntyped>,
	regions: HashMap<String, AtlasRegion>,
	texture: Handle<Image>,
	atlas: Handle<TextureAtlas>
}

impl RawChairAtlas {
	pub fn new(assets: &AssetServer, base: &'static str) -> std::result::Result<Self, AssetServerError> {
		Ok(Self {
			base,
			handles: assets.load_folder(base)?,
			regions: HashMap::new(),
			texture: Handle::default(),
			atlas: Handle::default()
		})
	}

	pub fn load(&mut self, assets: &AssetServer, textures: &mut Assets<Image>, atlases: &mut Assets<TextureAtlas>) {
		let mut names = vec![];
		let mut builder = TextureAtlasBuilder::default();
		for handle in &self.handles {
			let path = assets.get_handle_path(handle);
			let Some(path) = path else {
				warn!("Texture found with no path");
				continue;
			};
			let path = path.path();

			let handle = handle.typed_weak();
			let Some(texture) = textures.get(&handle) else {
				warn!("Texture {path:?} is not an image asset");
				continue;
			};

			let Ok(name) = path.strip_prefix(self.base) else {
				warn!("Random path {path:?} found in handles, not a prefix of {:?}", self.base);
				continue;
			};
			// for ease of use omit file extension in your mesh texture names
			let name = name.with_extension("");
			let Some(name) = name.as_os_str().to_str() else {
				warn!("Non UTF-8 filename {name:?}");
				continue;
			};

			names.push(name.to_owned());
			builder.add_texture(handle, texture);
		}

		let atlas = builder.finish(textures)
			.expect("Failed to build texture atlas");
		self.texture = atlas.texture.clone();
		let size = atlas.size;
		for (i, name) in names.into_iter().enumerate() {
			let rect = atlas.textures[i];
			let min = rect.min / size;
			let max = rect.max / size;
			trace!("Region {name} at {rect:?}");

			let region = AtlasRegion {
				xy: min.into(),
				x2y: [max.x, min.y],
				x2y2: max.into(),
				xy2: [min.x, max.x]
			};

			if self.regions.contains_key(&name) {
				warn!("Duplicate atlas texture '{name}' found");
			} else {
				self.regions.insert(name, region);
			}
		}

		self.atlas = atlases.add(atlas);

		// never using them again, free up memory
		self.handles = vec![];
	}
}

impl Atlas for RawChairAtlas {
	fn get(&self, name: &str) -> Option<&AtlasRegion> {
		self.regions.get(name)
	}
}