outfly/src/skeleton.rs

360 lines
13 KiB
Rust
Raw Normal View History

// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
2024-04-23 15:33:36 +00:00
//
// This module manages model loading and animation.
2024-04-23 01:49:47 +00:00
use crate::world;
2024-04-22 19:01:27 +00:00
use bevy::ecs::system::EntityCommands;
use bevy::prelude::*;
2024-04-22 19:01:27 +00:00
2024-04-22 21:09:50 +00:00
pub struct SkeletonPlugin;
impl Plugin for SkeletonPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, animate_skeleton_parts);
2024-04-23 17:08:27 +00:00
app.add_systems(Update, play_animations);
2024-04-22 21:09:50 +00:00
}
}
2024-04-22 19:01:27 +00:00
pub fn asset_name_to_path(name: &str) -> &'static str {
match name {
2024-04-24 01:03:57 +00:00
"suitv2" => "models/suit_v2/suit_v2.glb#Scene0",
2024-04-24 01:03:18 +00:00
"suit_ar_chefhat" => "models/suit_v2/ar_chefhat.glb#Scene0",
2024-04-22 19:01:27 +00:00
"asteroid1" => "models/asteroid.glb#Scene0",
"asteroid2" => "models/asteroid2.glb#Scene0",
"asteroid_lum" => "models/asteroid_lum.glb#Scene0",
2024-05-01 01:31:30 +00:00
"hollow_asteroid" => "models/hollow_asteroid.glb#Scene0",
2024-04-22 19:01:27 +00:00
"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",
2024-05-01 01:31:30 +00:00
"crate" => "models/crate.glb#Scene0",
2024-04-22 19:01:27 +00:00
"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",
2024-04-22 21:36:19 +00:00
"clippy" => "models/clippy/clippy.glb#Scene0",
"clippy_ar" => "models/clippy/ar_happy.glb#Scene0",
2024-04-22 19:01:27 +00:00
"whale" => "models/whale.glb#Scene0",
"marker_satellites" => "models/marker_satellites.glb#Scene0",
"marker_planets" => "models/marker_planets.glb#Scene0",
2024-04-22 19:01:27 +00:00
"point_of_interest" => "models/point_of_interest.glb#Scene0",
_ => "models/error.glb#Scene0",
}
}
2024-04-22 19:09:05 +00:00
pub fn skeleton_name_to_skeletondef(name: &str) -> Option<SkeletonDef> {
2024-04-22 21:25:32 +00:00
// x: positive: left, negative: right
// y: positive: upward, negative: downward
// z: positive: forward, negative: backward
2024-04-22 19:09:05 +00:00
match name {
"suitv1" => Some(SkeletonDef::Human(HumanDef {
2024-04-22 21:36:19 +00:00
collider: "models/suit_v1/collider.glb#Scene0".into(),
base: "models/suit_v1/base.glb#Scene0".into(),
limbs: vec![
LimbDef {
class: Limb::Head,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/head.glb#Scene0".into(),
2024-04-22 21:28:22 +00:00
pos: Vec3::new(0.0, 0.46, 0.0),
..default()
},
LimbDef {
class: Limb::UpperArmLeft,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/upper_arm.glb#Scene0".into(),
2024-04-22 21:11:41 +00:00
pos: Vec3::new(0.22, 0.3, 0.0),
mirror: true,
children: vec![LimbDef {
class: Limb::LowerArmLeft,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/lower_arm.glb#Scene0".into(),
pos: Vec3::new(-0.33, 0.0, 0.0),
..default()
}],
..default()
},
LimbDef {
class: Limb::UpperArmRight,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/upper_arm.glb#Scene0".into(),
2024-04-22 21:11:41 +00:00
pos: Vec3::new(-0.22, 0.3, 0.0),
children: vec![LimbDef {
class: Limb::LowerArmRight,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/lower_arm.glb#Scene0".into(),
pos: Vec3::new(-0.33, 0.0, 0.0),
..default()
}],
..default()
},
LimbDef {
class: Limb::UpperLegLeft,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/upper_leg.glb#Scene0".into(),
2024-04-22 21:25:32 +00:00
pos: Vec3::new(0.15, -0.25, 0.1),
mirror: true,
children: vec![LimbDef {
class: Limb::LowerLegLeft,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/lower_leg.glb#Scene0".into(),
pos: Vec3::new(0.0, -0.3, 0.0),
..default()
}],
..default()
},
LimbDef {
class: Limb::UpperLegRight,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/upper_leg.glb#Scene0".into(),
2024-04-22 21:25:32 +00:00
pos: Vec3::new(-0.15, -0.25, 0.1),
children: vec![LimbDef {
class: Limb::LowerLegRight,
2024-04-22 21:36:19 +00:00
path: "models/suit_v1/lower_leg.glb#Scene0".into(),
pos: Vec3::new(0.0, -0.3, 0.0),
..default()
}],
..default()
},
],
})),
2024-04-22 19:09:05 +00:00
_ => None,
}
2024-04-22 19:01:27 +00:00
}
2024-04-22 19:01:27 +00:00
#[derive(Component)] pub struct SkeletonLimb;
2024-04-22 21:09:50 +00:00
#[derive(Component)] pub struct MirroredLimb;
pub enum SkeletonDef {
Human(HumanDef)
2024-04-22 19:09:05 +00:00
}
2024-04-22 19:01:27 +00:00
pub struct HumanDef {
collider: String,
base: String,
limbs: Vec<LimbDef>,
}
#[derive(Default)]
pub struct LimbDef {
path: String,
pos: Vec3,
class: Limb,
mirror: bool,
children: Vec<LimbDef>,
}
#[derive(Component, Default)]
pub enum Limb {
#[default]
Base,
Head,
UpperArmRight,
UpperArmLeft,
LowerArmRight,
LowerArmLeft,
UpperLegRight,
UpperLegLeft,
LowerLegRight,
LowerLegLeft,
}
2024-04-22 19:01:27 +00:00
2024-04-22 21:19:42 +00:00
#[derive(Component)]
pub enum Animation {
HumanFloat,
}
2024-04-22 19:01:27 +00:00
pub fn load(
name: &str,
entity_commands: &mut EntityCommands,
asset_server: &AssetServer,
) {
2024-04-22 19:09:05 +00:00
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,
2024-04-22 21:19:42 +00:00
Animation::HumanFloat,
2024-04-23 01:49:47 +00:00
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,
2024-04-22 21:19:42 +00:00
Animation::HumanFloat,
2024-04-23 01:49:47 +00:00
world::DespawnOnPlayerDeath,
SceneBundle {
scene: load_scene_by_path(limb.path.as_str(), asset_server),
transform: Transform::from_translation(limb.pos).with_rotation(rot),
..default()
}
));
2024-04-22 21:09:50 +00:00
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
};
2024-04-22 21:09:50 +00:00
let mut entity_commands = parent.spawn((
child_limb.class,
2024-04-22 21:19:42 +00:00
Animation::HumanFloat,
2024-04-23 01:49:47 +00:00
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()
}
));
2024-04-22 21:09:50 +00:00
if child_limb.mirror {
entity_commands.insert(MirroredLimb);
}
}
});
}
}
});
}
}
2024-04-22 19:09:05 +00:00
} else {
2024-04-22 19:01:27 +00:00
entity_commands.insert(load_scene_by_path(asset_name_to_path(name), asset_server));
}
2024-04-22 19:01:27 +00:00
}
#[inline]
pub fn load_scene_by_path(
path: &str,
asset_server: &AssetServer
) -> Handle<Scene> {
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(
2024-04-22 21:09:50 +00:00
time: Res<Time>,
2024-04-22 21:19:42 +00:00
mut q_limb: Query<(&mut Transform, &Limb, &Animation, Option<&MirroredLimb>)>,
2024-04-22 19:01:27 +00:00
) {
2024-04-22 21:09:50 +00:00
let t = time.elapsed_seconds();
2024-04-22 21:19:42 +00:00
for (mut trans, limb, animation, mirror) in &mut q_limb {
2024-04-22 21:09:50 +00:00
let mirror = mirror.is_some();
2024-04-22 21:19:42 +00:00
match animation {
Animation::HumanFloat =>
animate_human_float(&mut trans, &limb, mirror, t),
2024-04-22 21:09:50 +00:00
}
}
}
2024-04-22 21:19:42 +00:00
fn rot(trans: &mut Transform, x: f32, y: f32, z: f32) {
trans.rotation = Quat::from_euler(
EulerRot::XYZ,
x.to_radians(),
y.to_radians(),
z.to_radians()
);
}
pub fn animate_human_float(mut trans: &mut Transform, limb: &Limb, mirror: bool, t: f32) {
// x: lean head forward/backward
// y: close/open arms together in front of the torso
// z: spread legs sidewards
let m = {|angle| if mirror { 180f32 + angle } else { angle }};
match limb {
Limb::UpperArmRight => rot(&mut trans,
0.0,
m(40.0 + 5.0 * (t * 0.5).sin()),
20.0 + 10.0 * (t * 0.5).sin(),
),
Limb::UpperArmLeft => rot(&mut trans,
0.0,
m(-(40.0 + 5.0 * (t * 0.5).sin())),
20.0 + 10.0 * (t * 0.5).sin(),
),
Limb::LowerArmRight => rot(&mut trans,
0.0,
m(20.0),
-20.0,
),
Limb::LowerArmLeft => rot(&mut trans,
0.0,
m(-20.0),
-20.0,
),
Limb::UpperLegRight => rot(&mut trans,
-30.0 + 10.0 * (t * 0.5).sin(),
0.0,
2024-04-22 21:25:32 +00:00
-20.0 + 2.5 * (t * 0.5).cos(),
2024-04-22 21:19:42 +00:00
),
Limb::UpperLegLeft => rot(&mut trans,
-30.0 + 10.0 * (t * 0.5).sin(),
0.0,
2024-04-22 21:25:32 +00:00
20.0 - 2.5 * (t * 0.5).cos(),
2024-04-22 21:19:42 +00:00
),
Limb::LowerLegRight => rot(&mut trans,
2024-04-22 21:25:32 +00:00
35.0 + 5.0 * (t * 0.5).sin(),
2024-04-22 21:19:42 +00:00
0.0,
0.0,
),
Limb::LowerLegLeft => rot(&mut trans,
2024-04-22 21:25:32 +00:00
35.0 + 5.0 * (t * 0.5).sin(),
2024-04-22 21:19:42 +00:00
0.0,
0.0,
),
_ => {},
}
}
2024-04-23 17:08:27 +00:00
fn play_animations(
mut players: Query<&mut AnimationPlayer, Added<AnimationPlayer>>,
asset_server: Res<AssetServer>,
) {
for mut player in &mut players {
2024-04-24 01:03:57 +00:00
let animation = asset_server.load("models/suit_v2/suit_v2.glb#Animation0");
2024-04-23 17:08:27 +00:00
player.play(animation.clone()).repeat();
}
}
//fn play_animations(
// players: Query<(Entity, &Parent, &AnimationPlayer), Added<AnimationPlayer>>,
// q_parents: Query<(Entity, &Parent)>,
// world: &World,
//) {
// for (entity, parent, player) in &players {
// info!("Got player!");
//// dbg!(world.inspect_entity(entity));
//// let parent_entity = q_parents.get(parent.get());
//// if let Ok((parent_entity, parent_parents)) = parent_entity {
//// //dbg!(world.inspect_entity(parent_entity));
//// let parent_entity = q_parents.get(parent_parents.get());
//// if let Ok((parent_entity, parent_parents)) = parent_entity {
//// dbg!(world.inspect_entity(parent_entity));
//// }
//// }
// //dbg!(player);
// //player.play(animations.0[0].clone_weak()).repeat();
// }
//}