// ▄████████▄ + ███ + ▄█████████ ███ + // ███▀ ▀███ + + ███ ███▀ + ███ + + // ███ + ███ ███ ███ █████████ ███ ███ ███ ███ // ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███ // ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███ // ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███ // ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████ // + + + ███ // + ▀████████████████████████████████████████████████████▀ // // This module manages model loading and animation. use crate::world; use bevy::ecs::system::EntityCommands; use bevy::prelude::*; pub struct SkeletonPlugin; impl Plugin for SkeletonPlugin { fn build(&self, app: &mut App) { app.add_systems(Update, animate_skeleton_parts); app.add_systems(Update, play_animations); } } pub fn asset_name_to_path(name: &str) -> &'static str { match name { "suitv2" => "models/suit_v2/suit_v2.glb#Scene0", "suit_ar_chefhat" => "models/suit_v2/ar_chefhat.glb#Scene0", "asteroid1" => "models/asteroid.glb#Scene0", "asteroid2" => "models/asteroid2.glb#Scene0", "asteroid_lum" => "models/asteroid_lum.glb#Scene0", "hollow_asteroid" => "models/hollow_asteroid.glb#Scene0", "moonlet" => "models/moonlet.glb#Scene0", "monolith" => "models/monolith_neon.glb#Scene0", "lightorb" => "models/lightorb.glb#Scene0", "orb_busstop" => "models/orb_busstop.glb#Scene0", "orb_busstop_dim" => "models/orb_busstop_dim.glb#Scene0", "crate" => "models/crate.glb#Scene0", "MeteorAceGT" => "models/MeteorAceGT.glb#Scene0", "satellite" => "models/satellite.glb#Scene0", "pizzeria" => "models/pizzeria2.glb#Scene0", "pizzasign" => "models/pizzasign.glb#Scene0", "selectagon" => "models/selectagon.glb#Scene0", "orbitring" => "models/orbitring.glb#Scene0", "clippy" => "models/clippy/clippy.glb#Scene0", "clippy_ar" => "models/clippy/ar_happy.glb#Scene0", "whale" => "models/whale.glb#Scene0", "marker_satellites" => "models/marker_satellites.glb#Scene0", "point_of_interest" => "models/point_of_interest.glb#Scene0", _ => "models/error.glb#Scene0", } } pub fn skeleton_name_to_skeletondef(name: &str) -> Option { // x: positive: left, negative: right // y: positive: upward, negative: downward // z: positive: forward, negative: backward match name { "suitv1" => Some(SkeletonDef::Human(HumanDef { collider: "models/suit_v1/collider.glb#Scene0".into(), base: "models/suit_v1/base.glb#Scene0".into(), limbs: vec![ LimbDef { class: Limb::Head, path: "models/suit_v1/head.glb#Scene0".into(), pos: Vec3::new(0.0, 0.46, 0.0), ..default() }, LimbDef { class: Limb::UpperArmLeft, path: "models/suit_v1/upper_arm.glb#Scene0".into(), pos: Vec3::new(0.22, 0.3, 0.0), mirror: true, children: vec![LimbDef { class: Limb::LowerArmLeft, path: "models/suit_v1/lower_arm.glb#Scene0".into(), pos: Vec3::new(-0.33, 0.0, 0.0), ..default() }], ..default() }, LimbDef { class: Limb::UpperArmRight, path: "models/suit_v1/upper_arm.glb#Scene0".into(), pos: Vec3::new(-0.22, 0.3, 0.0), children: vec![LimbDef { class: Limb::LowerArmRight, path: "models/suit_v1/lower_arm.glb#Scene0".into(), pos: Vec3::new(-0.33, 0.0, 0.0), ..default() }], ..default() }, LimbDef { class: Limb::UpperLegLeft, path: "models/suit_v1/upper_leg.glb#Scene0".into(), pos: Vec3::new(0.15, -0.25, 0.1), mirror: true, children: vec![LimbDef { class: Limb::LowerLegLeft, path: "models/suit_v1/lower_leg.glb#Scene0".into(), pos: Vec3::new(0.0, -0.3, 0.0), ..default() }], ..default() }, LimbDef { class: Limb::UpperLegRight, path: "models/suit_v1/upper_leg.glb#Scene0".into(), pos: Vec3::new(-0.15, -0.25, 0.1), children: vec![LimbDef { class: Limb::LowerLegRight, path: "models/suit_v1/lower_leg.glb#Scene0".into(), pos: Vec3::new(0.0, -0.3, 0.0), ..default() }], ..default() }, ], })), _ => None, } } #[derive(Component)] pub struct SkeletonLimb; #[derive(Component)] pub struct MirroredLimb; pub enum SkeletonDef { Human(HumanDef) } pub struct HumanDef { collider: String, base: String, limbs: Vec, } #[derive(Default)] pub struct LimbDef { path: String, pos: Vec3, class: Limb, mirror: bool, children: Vec, } #[derive(Component, Default)] pub enum Limb { #[default] Base, Head, UpperArmRight, UpperArmLeft, LowerArmRight, LowerArmLeft, UpperLegRight, UpperLegLeft, LowerLegRight, LowerLegLeft, } #[derive(Component)] pub enum Animation { HumanFloat, } pub fn load( name: &str, entity_commands: &mut EntityCommands, asset_server: &AssetServer, ) { if let Some(skel) = skeleton_name_to_skeletondef(name) { match skel { SkeletonDef::Human(human) => { entity_commands.insert(load_scene_by_path(human.collider.as_str(), asset_server)); entity_commands.with_children(|parent| { parent.spawn(( Limb::Base, Animation::HumanFloat, world::DespawnOnPlayerDeath, SceneBundle { scene: load_scene_by_path(human.base.as_str(), asset_server), ..default() } )); for limb in human.limbs { let rot = if limb.mirror { Quat::from_rotation_y(180.0f32.to_radians()) } else { Quat::IDENTITY }; let mut parent_limb = parent.spawn(( limb.class, Animation::HumanFloat, world::DespawnOnPlayerDeath, SceneBundle { scene: load_scene_by_path(limb.path.as_str(), asset_server), transform: Transform::from_translation(limb.pos).with_rotation(rot), ..default() } )); if limb.mirror { parent_limb.insert(MirroredLimb); } if !limb.children.is_empty() { parent_limb.with_children(|parent| { for child_limb in limb.children { let rot = if child_limb.mirror { Quat::from_rotation_y(180.0f32.to_radians()) } else { Quat::IDENTITY }; let mut entity_commands = parent.spawn(( child_limb.class, Animation::HumanFloat, world::DespawnOnPlayerDeath, SceneBundle { scene: load_scene_by_path(child_limb.path.as_str(), asset_server), transform: Transform::from_translation(child_limb.pos).with_rotation(rot), ..default() } )); if child_limb.mirror { entity_commands.insert(MirroredLimb); } } }); } } }); } } } else { entity_commands.insert(load_scene_by_path(asset_name_to_path(name), asset_server)); } } #[inline] pub fn load_scene_by_path( path: &str, asset_server: &AssetServer ) -> Handle { let path_string = path.to_string(); if let Some(handle) = asset_server.get_handle(&path_string) { handle } else { asset_server.load(&path_string) } } pub fn _build_body( _name: String, mut _entity_commands: EntityCommands, ) { } pub fn animate_skeleton_parts( time: Res