use bevy::platform::collections::{HashMap, HashSet};
use bevy::prelude::{Handle, Resource, Scene, StandardMaterial};
#[derive(Resource)]
pub struct ShapeRegistry {
meshes: HashMap<String, Handle<Scene>>,
materials: HashMap<String, Handle<StandardMaterial>>,
pub default_material: Option<Handle<StandardMaterial>>,
stretch_uv_materials: HashSet<String>,
stretch_uv_meshes: HashSet<String>,
round_materials: HashSet<String>,
round_meshes: HashSet<String>,
round_segments: u32,
}
pub const DEFAULT_ROUND_SEGMENTS: u32 = 24;
impl Default for ShapeRegistry {
fn default() -> Self {
Self {
meshes: HashMap::default(),
materials: HashMap::default(),
default_material: None,
stretch_uv_materials: HashSet::default(),
stretch_uv_meshes: HashSet::default(),
round_materials: HashSet::default(),
round_meshes: HashSet::default(),
round_segments: DEFAULT_ROUND_SEGMENTS,
}
}
}
impl ShapeRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register_mesh(&mut self, id: impl Into<String>, handle: Handle<Scene>) {
self.meshes.insert(id.into(), handle);
}
pub fn register_material(&mut self, id: impl Into<String>, handle: Handle<StandardMaterial>) {
self.materials.insert(id.into(), handle);
}
pub fn register_stretch_material(&mut self, id: impl Into<String>) {
self.stretch_uv_materials.insert(id.into());
}
pub fn register_stretch_mesh(&mut self, id: impl Into<String>) {
self.stretch_uv_meshes.insert(id.into());
}
pub fn should_stretch_uvs(&self, mesh_id: &str, mat_id: Option<&str>) -> bool {
self.stretch_uv_meshes.contains(mesh_id)
|| mat_id.is_some_and(|m| self.stretch_uv_materials.contains(m))
}
pub fn register_round_mesh(&mut self, id: impl Into<String>) {
self.round_meshes.insert(id.into());
}
pub fn register_round_material(&mut self, id: impl Into<String>) {
self.round_materials.insert(id.into());
}
pub fn set_round_segments(&mut self, segments: u32) {
self.round_segments = segments.clamp(3, 256);
}
pub fn round_segments(&self) -> u32 {
self.round_segments
}
pub fn round_segments_for(&self, mesh_id: &str, mat_id: Option<&str>) -> u32 {
let round = self.round_meshes.contains(mesh_id)
|| mat_id.is_some_and(|m| self.round_materials.contains(m));
if round { self.round_segments } else { 0 }
}
pub fn get_mesh(&self, id: &str) -> Option<&Handle<Scene>> {
self.meshes.get(id)
}
pub fn resolve_material(&self, id: Option<&str>) -> Option<Handle<StandardMaterial>> {
if let Some(h) = id.and_then(|name| self.materials.get(name)) {
return Some(h.clone());
}
self.default_material.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use bevy::prelude::*;
fn make_app() -> App {
let mut app = App::new();
app.add_plugins(bevy::asset::AssetPlugin::default());
app
}
#[test]
fn register_and_retrieve_mesh() {
let mut app = make_app();
app.init_resource::<Assets<Scene>>();
let world = app.world_mut();
let mut scenes = world.resource_mut::<Assets<Scene>>();
let handle: Handle<Scene> = scenes.add(Scene::new(World::new()));
let mut registry = ShapeRegistry::new();
registry.register_mesh("Window", handle.clone());
assert!(registry.get_mesh("Window").is_some());
assert!(registry.get_mesh("Door").is_none());
}
#[test]
fn round_opt_in_by_mesh_or_material() {
let mut registry = ShapeRegistry::new();
assert_eq!(registry.round_segments_for("Column", None), 0);
registry.register_round_mesh("Column");
registry.register_round_material("Pipe");
assert_eq!(
registry.round_segments_for("Column", None),
DEFAULT_ROUND_SEGMENTS
);
assert_eq!(
registry.round_segments_for("Anything", Some("Pipe")),
DEFAULT_ROUND_SEGMENTS
);
assert_eq!(registry.round_segments_for("Wall", Some("Brick")), 0);
}
#[test]
fn round_segments_are_clamped() {
let mut registry = ShapeRegistry::new();
registry.register_round_mesh("Silo");
registry.set_round_segments(1);
assert_eq!(registry.round_segments(), 3, "clamped up to the minimum");
registry.set_round_segments(9999);
assert_eq!(registry.round_segments(), 256, "clamped to the ceiling");
assert_eq!(registry.round_segments_for("Silo", None), 256);
}
#[test]
fn resolve_material_falls_back_to_default() {
let mut app = make_app();
app.init_resource::<Assets<StandardMaterial>>();
let world = app.world_mut();
let mut mats = world.resource_mut::<Assets<StandardMaterial>>();
let default_mat: Handle<StandardMaterial> = mats.add(StandardMaterial::default());
let mut registry = ShapeRegistry::new();
registry.default_material = Some(default_mat.clone());
let resolved = registry.resolve_material(Some("Brick"));
assert!(resolved.is_some());
let resolved2 = registry.resolve_material(None);
assert!(resolved2.is_some());
}
#[test]
fn resolve_material_returns_registered_handle() {
let mut app = make_app();
app.init_resource::<Assets<StandardMaterial>>();
let world = app.world_mut();
let mut mats = world.resource_mut::<Assets<StandardMaterial>>();
let brick: Handle<StandardMaterial> = mats.add(StandardMaterial {
base_color: bevy::color::Color::srgb(0.8, 0.4, 0.2),
..Default::default()
});
let default_mat: Handle<StandardMaterial> = mats.add(StandardMaterial::default());
let mut registry = ShapeRegistry::new();
registry.register_material("Brick", brick.clone());
registry.default_material = Some(default_mat);
let resolved = registry.resolve_material(Some("Brick"));
assert!(resolved.is_some());
assert_eq!(resolved.unwrap().id(), brick.id());
}
}