From 732c43ce11f76dc573f372e471dc2b0e1d27a25b Mon Sep 17 00:00:00 2001 From: jshipsey Date: Mon, 13 May 2019 19:43:10 -0400 Subject: [PATCH] first quadruped changes Former-commit-id: 71ad084a872c3bf96d5cbab53c376d214f3cdcd0 --- assets/voxygen/voxel/pigchest.vox | 3 + assets/voxygen/voxel/pighead.vox | 3 + assets/voxygen/voxel/pigleg_l.vox | 3 + assets/voxygen/voxel/pigleg_r.vox | 3 + common/src/comp/actor.rs | 37 +++ common/src/comp/mod.rs | 1 + server/src/cmd.rs | 2 +- voxygen/src/anim/character/run.rs | 1 + voxygen/src/anim/mod.rs | 2 +- voxygen/src/anim/quadruped/mod.rs | 68 +++++ voxygen/src/anim/quadruped/run.rs | 57 ++++ voxygen/src/scene/figure.rs | 2 + voxygen/src/scene/figure/figure.rs | 360 +++++++++++++++++++++++++ voxygen/src/scene/figure/figurequad.rs | 290 ++++++++++++++++++++ 14 files changed, 830 insertions(+), 2 deletions(-) create mode 100644 assets/voxygen/voxel/pigchest.vox create mode 100644 assets/voxygen/voxel/pighead.vox create mode 100644 assets/voxygen/voxel/pigleg_l.vox create mode 100644 assets/voxygen/voxel/pigleg_r.vox create mode 100644 voxygen/src/anim/quadruped/mod.rs create mode 100644 voxygen/src/anim/quadruped/run.rs create mode 100644 voxygen/src/scene/figure/figure.rs create mode 100644 voxygen/src/scene/figure/figurequad.rs diff --git a/assets/voxygen/voxel/pigchest.vox b/assets/voxygen/voxel/pigchest.vox new file mode 100644 index 0000000000..4063be81c1 --- /dev/null +++ b/assets/voxygen/voxel/pigchest.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64a54c644c5963ebbfdbddbeeb0efe42f32546c6c7f83e9a94782111119da1ed +size 2612 diff --git a/assets/voxygen/voxel/pighead.vox b/assets/voxygen/voxel/pighead.vox new file mode 100644 index 0000000000..39ac9d39b6 --- /dev/null +++ b/assets/voxygen/voxel/pighead.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a56643b54eb4d39ad58b3bcab88390ddea58cf41351b39ad32b83612900b71c1 +size 2720 diff --git a/assets/voxygen/voxel/pigleg_l.vox b/assets/voxygen/voxel/pigleg_l.vox new file mode 100644 index 0000000000..d016d1b2b0 --- /dev/null +++ b/assets/voxygen/voxel/pigleg_l.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1fd44f8c1bd1eb9cab7a4d0632b6db52e9aada97b01a7fca8ea4f2f98186685 +size 1144 diff --git a/assets/voxygen/voxel/pigleg_r.vox b/assets/voxygen/voxel/pigleg_r.vox new file mode 100644 index 0000000000..bf5a48a400 --- /dev/null +++ b/assets/voxygen/voxel/pigleg_r.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79a83692fb7a5791cef6848ad14e27a79585f7a0cf872f20b2b556b9cd18838e +size 1144 diff --git a/common/src/comp/actor.rs b/common/src/comp/actor.rs index 4302749f12..c1e5ca48fc 100644 --- a/common/src/comp/actor.rs +++ b/common/src/comp/actor.rs @@ -129,10 +129,47 @@ impl HumanoidBody { } } } +const ALL_QRACES: [Race; 6] = [ + Race::Danari, + Race::Dwarf, + Race::Elf, + Race::Human, + Race::Orc, + Race::Undead,]; +const ALL_QBODY_TYPES: [BodyType; 3] = [BodyType::Female, BodyType::Male, BodyType::Unspecified]; +const ALL_QHEADS: [Head; 1] = [Head::Default]; +const ALL_QCHESTS: [Chest; 1] = [Chest::Default]; +const ALL_QHANDS: [Hand; 1] = [Hand::Default]; +const ALL_QFEET: [Foot; 1] = [Foot::Default]; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct QuadrupedBody { + pub race: Race, + pub body_type: BodyType, + pub head: Head, + pub chest: Chest, + pub hand: Hand, + pub foot: Foot, + +} + +impl QuadrupedBody { + pub fn random() -> Self { + Self { + race: *thread_rng().choose(&ALL_QRACES).unwrap(), + body_type: *thread_rng().choose(&ALL_QBODY_TYPES).unwrap(), + head: *thread_rng().choose(&ALL_QHEADS).unwrap(), + chest: *thread_rng().choose(&ALL_QCHESTS).unwrap(), + hand: *thread_rng().choose(&ALL_QHANDS).unwrap(), + foot: *thread_rng().choose(&ALL_QFEET).unwrap(), + } + } +} #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Body { Humanoid(HumanoidBody), + Quadruped(QuadrupedBody), } #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 1d14704377..e3c35e5ab8 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -9,5 +9,6 @@ pub use actor::Animation; pub use actor::AnimationHistory; pub use actor::Body; pub use actor::HumanoidBody; +pub use actor::QuadrupedBody; pub use agent::{Agent, Control}; pub use player::Player; diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b12ecafe17..58a668a6b1 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -194,7 +194,7 @@ fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &Cha server .create_npc( "Bungo".to_owned(), - comp::Body::Humanoid(comp::HumanoidBody::random()), + comp::Body::Quadruped(comp::QuadrupedBody::random()), ) .with(comp::Control::default()) .with(comp::Agent::Pet { diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index b79751c20b..22b738e27c 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -71,6 +71,7 @@ impl Animation for RunAnimation { next.r_shoulder.offset = Vec3::new(0.0, -3.0, 2.5); next.r_shoulder.ori = Quaternion::rotation_x(0.0); next.r_shoulder.scale = Vec3::one(); + next.torso.offset = Vec3::new(-0.5, -0.2, 0.4); next.torso.ori = Quaternion::rotation_x(-velocity * 0.05 - wavecos * 0.1); next.torso.scale = Vec3::one() / 11.0; diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index 38452e6c65..1c200a4114 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -1,5 +1,5 @@ pub mod character; - +pub mod quadruped; // Library use vek::*; diff --git a/voxygen/src/anim/quadruped/mod.rs b/voxygen/src/anim/quadruped/mod.rs new file mode 100644 index 0000000000..a22fe2c19e --- /dev/null +++ b/voxygen/src/anim/quadruped/mod.rs @@ -0,0 +1,68 @@ + +pub mod run; + +// Reexports +pub use self::run::RunAnimation; +// Crate +use crate::render::FigureBoneData; + +// Local +use super::{Bone, Skeleton}; + +const SCALE: f32 = 11.0; + +#[derive(Clone)] +pub struct QuadrupedSkeleton { + head: Bone, + chest: Bone, + lf_leg: Bone, + rf_leg: Bone, + lb_leg: Bone, + rb_leg: Bone, + +} + +impl QuadrupedSkeleton { + pub fn new() -> Self { + Self { + head: Bone::default(), + chest: Bone::default(), + lf_leg: Bone::default(), + rf_leg: Bone::default(), + lb_leg: Bone::default(), + rb_leg: Bone::default(), + } + } +} + +impl Skeleton for QuadrupedSkeleton { + fn compute_matrices(&self) -> [FigureBoneData; 16] { + [ + FigureBoneData::new(self.head.compute_base_matrix()), + FigureBoneData::new(self.chest.compute_base_matrix()), + FigureBoneData::new(self.lf_leg.compute_base_matrix()), + FigureBoneData::new(self.rf_leg.compute_base_matrix()), + FigureBoneData::new(self.lb_leg.compute_base_matrix()), + FigureBoneData::new(self.rb_leg.compute_base_matrix()), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + ] + } + + fn interpolate(&mut self, target: &Self) { + self.head.interpolate(&target.head); + self.chest.interpolate(&target.chest); + self.lf_leg.interpolate(&target.lf_leg); + self.rf_leg.interpolate(&target.rf_leg); + self.lb_leg.interpolate(&target.lb_leg); + self.rb_leg.interpolate(&target.rb_leg); + } +} diff --git a/voxygen/src/anim/quadruped/run.rs b/voxygen/src/anim/quadruped/run.rs new file mode 100644 index 0000000000..1e8a0913e3 --- /dev/null +++ b/voxygen/src/anim/quadruped/run.rs @@ -0,0 +1,57 @@ +// Standard +use std::f32::consts::PI; + +// Library +use vek::*; + +// Local +use super::{super::Animation, QuadrupedSkeleton, SCALE}; + +pub struct RunAnimation; + +impl Animation for RunAnimation { + type Skeleton = QuadrupedSkeleton; + type Dependency = (f32, f64); + + fn update_skeleton( + skeleton: &Self::Skeleton, + (velocity, global_time): Self::Dependency, + anim_time: f64, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + let wave = (anim_time as f32 * 14.0).sin(); + let wavetest = (wave.cbrt()); + let fuzzwave = (anim_time as f32 * 12.0).sin(); + let wavecos = (anim_time as f32 * 14.0).cos(); + let wave_slow = (anim_time as f32 * 7.0 + PI).sin(); + let wavecos_slow = (anim_time as f32 * 8.0 + PI).cos(); + let wave_dip = (wave_slow.abs() - 0.5).abs(); + + next.head.offset = Vec3::new(5.5, 2.0, 11.0 + wavecos * 1.3); + next.head.ori = Quaternion::rotation_x(0.15); + next.head.scale = Vec3::one(); + + next.chest.offset = Vec3::new(5.5, 0.0, 7.0 + wavecos * 1.1); + next.chest.ori = Quaternion::rotation_z(wave * 0.1); + next.chest.scale = Vec3::one(); + + next.lf_leg.offset = Vec3::new(5.5, 0.0, 5.0 + wavecos * 1.1); + next.lf_leg.ori = Quaternion::rotation_z(wave * 0.25); + next.lf_leg.scale = Vec3::one(); + + next.rf_leg.offset = Vec3::new(5.5, 0.0, 2.0 + wavecos * 1.1); + next.rf_leg.ori = Quaternion::rotation_z(wave * 0.6); + next.rf_leg.scale = Vec3::one(); + + next.lb_leg.offset = Vec3::new(-6.0, 0.0 + wavecos * 2.5, 11.0 - wave * 1.5); + next.lb_leg.ori = Quaternion::rotation_x(wavecos * 0.9); + next.lb_leg.scale = Vec3::one(); + + next.rb_leg.offset = Vec3::new(9.0, 0.0 - wavecos * 2.5, 11.0 + wave * 1.5); + next.rb_leg.ori = Quaternion::rotation_x(wavecos * -0.9); + next.rb_leg.scale = Vec3::one(); + + next + } +} diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 9ef9dad08c..8beb0b3661 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -308,7 +308,9 @@ impl FigureMgr { state.bone_consts(), ); } + Body::Quadruped(body) => {..}; } // TODO: Non-humanoid bodies + }, // TODO: Non-character actors } diff --git a/voxygen/src/scene/figure/figure.rs b/voxygen/src/scene/figure/figure.rs new file mode 100644 index 0000000000..9ef9dad08c --- /dev/null +++ b/voxygen/src/scene/figure/figure.rs @@ -0,0 +1,360 @@ +use crate::{ + anim::{ + character::{CharacterSkeleton, IdleAnimation, JumpAnimation, RunAnimation}, + Animation, Skeleton, + }, + mesh::Meshable, + render::{ + Consts, FigureBoneData, FigureLocals, FigurePipeline, Globals, Mesh, Model, Renderer, + }, + Error, +}; +use client::Client; +use common::{ + assets, + comp::{ + self, + actor::{Belt, Chest, Foot, Hand, Head, Pants, Shoulder, Weapon}, + Body, HumanoidBody, + }, + figure::Segment, + msg, +}; +use dot_vox::DotVoxData; +use specs::{Component, Entity as EcsEntity, Join, VecStorage}; +use std::{collections::HashMap, f32}; +use vek::*; + +pub struct FigureModelCache { + models: HashMap, u64)>, +} + +impl FigureModelCache { + pub fn new() -> Self { + Self { + models: HashMap::new(), + } + } + + pub fn get_or_create_model( + &mut self, + renderer: &mut Renderer, + body: HumanoidBody, + tick: u64, + ) -> &Model { + match self.models.get_mut(&body) { + Some((model, last_used)) => { + *last_used = tick; + } + None => { + self.models.insert( + body, + ( + { + let bone_meshes = [ + Some(Self::load_head(body.head)), + Some(Self::load_chest(body.chest)), + Some(Self::load_belt(body.belt)), + Some(Self::load_pants(body.pants)), + Some(Self::load_left_hand(body.hand)), + Some(Self::load_right_hand(body.hand)), + Some(Self::load_left_foot(body.foot)), + Some(Self::load_right_foot(body.foot)), + Some(Self::load_weapon(body.weapon)), + Some(Self::load_left_shoulder(body.shoulder)), + Some(Self::load_right_shoulder(body.shoulder)), + None, + None, + None, + None, + None, + ]; + + let mut mesh = Mesh::new(); + bone_meshes + .iter() + .enumerate() + .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm))) + .for_each(|(i, bone_mesh)| { + mesh.push_mesh_map(bone_mesh, |vert| { + vert.with_bone_idx(i as u8) + }) + }); + + renderer.create_model(&mesh).unwrap() + }, + tick, + ), + ); + } + } + + &self.models[&body].0 + } + + pub fn clean(&mut self, tick: u64) { + // TODO: Don't hard-code this + self.models + .retain(|_, (_, last_used)| *last_used + 60 > tick); + } + + // TODO: Don't make this public + pub fn load_mesh(filename: &str, position: Vec3) -> Mesh { + let fullpath: String = ["/voxygen/voxel/", filename].concat(); + Segment::from(assets::load_expect::(fullpath.as_str()).as_ref()) + .generate_mesh(position) + } + + fn load_head(head: Head) -> Mesh { + Self::load_mesh( + match head { + Head::Default => "head.vox", + }, + Vec3::new(-7.0, -5.5, -6.0), + ) + } + + fn load_chest(chest: Chest) -> Mesh { + Self::load_mesh( + match chest { + Chest::Default => "chest.vox", + }, + Vec3::new(-6.0, -3.5, 0.0), + ) + } + + fn load_belt(belt: Belt) -> Mesh { + Self::load_mesh( + match belt { + Belt::Default => "belt.vox", + }, + Vec3::new(-5.0, -3.5, 0.0), + ) + } + + fn load_pants(pants: Pants) -> Mesh { + Self::load_mesh( + match pants { + Pants::Default => "pants.vox", + }, + Vec3::new(-5.0, -3.5, 0.0), + ) + } + + fn load_left_hand(hand: Hand) -> Mesh { + Self::load_mesh( + match hand { + Hand::Default => "hand.vox", + }, + Vec3::new(2.0, 0.0, -7.0), + ) + } + + fn load_right_hand(hand: Hand) -> Mesh { + Self::load_mesh( + match hand { + Hand::Default => "hand.vox", + }, + Vec3::new(2.0, 0.0, -7.0), + ) + } + + fn load_left_foot(foot: Foot) -> Mesh { + Self::load_mesh( + match foot { + Foot::Default => "foot.vox", + }, + Vec3::new(2.5, -3.5, -9.0), + ) + } + + fn load_right_foot(foot: Foot) -> Mesh { + Self::load_mesh( + match foot { + Foot::Default => "foot.vox", + }, + Vec3::new(2.5, -3.5, -9.0), + ) + } + + fn load_weapon(weapon: Weapon) -> Mesh { + Self::load_mesh( + match weapon { + Weapon::Sword => "sword.vox", + // TODO actually match against other weapons and set the right model + _ => "sword.vox", + }, + Vec3::new(0.0, 0.0, -4.0), + ) + } + + fn load_left_shoulder(shoulder: Shoulder) -> Mesh { + Self::load_mesh( + match shoulder { + Shoulder::Default => "shoulder_l.vox", + }, + Vec3::new(2.5, 0.0, 0.0), + ) + } + + fn load_right_shoulder(shoulder: Shoulder) -> Mesh { + Self::load_mesh( + match shoulder { + Shoulder::Default => "shoulder_r.vox", + }, + Vec3::new(2.5, 0.0, 0.0), + ) + } + // fn load_draw(draw: Draw) -> Mesh { + // Self::load_mesh( + // match draw { + // //Draw::DefaultDraw => "sword.vox", + // + // }, + // Vec3::new(0.0, 0.0, -2.0) + // + // + // ) + // } +} + +pub struct FigureMgr { + model_cache: FigureModelCache, + states: HashMap>, +} + +impl FigureMgr { + pub fn new() -> Self { + Self { + model_cache: FigureModelCache::new(), + states: HashMap::new(), + } + } + + pub fn clean(&mut self, tick: u64) { + self.model_cache.clean(tick); + } + + pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { + let time = client.state().get_time(); + let ecs = client.state().ecs(); + for (entity, pos, vel, dir, actor, animation_history) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ) + .join() + { + match actor { + comp::Actor::Character { body, .. } => match body { + Body::Humanoid(body) => { + let state = self.states.entry(entity).or_insert_with(|| { + FigureState::new(renderer, CharacterSkeleton::new()) + }); + + let target_skeleton = match animation_history.current { + comp::Animation::Idle => IdleAnimation::update_skeleton( + state.skeleton_mut(), + time, + animation_history.time, + ), + comp::Animation::Run => RunAnimation::update_skeleton( + state.skeleton_mut(), + (vel.0.magnitude(), time), + animation_history.time, + ), + comp::Animation::Jump => JumpAnimation::update_skeleton( + state.skeleton_mut(), + time, + animation_history.time, + ), + }; + + state.skeleton.interpolate(&target_skeleton); + + state.update(renderer, pos.0, dir.0); + } // TODO: Non-humanoid bodies + }, + // TODO: Non-character actors + } + } + + self.states + .retain(|entity, _| ecs.entities().is_alive(*entity)); + } + + pub fn render( + &mut self, + renderer: &mut Renderer, + client: &mut Client, + globals: &Consts, + ) { + let tick = client.get_tick(); + let ecs = client.state().ecs(); + + for (entity, actor) in (&ecs.entities(), &ecs.read_storage::()).join() { + match actor { + comp::Actor::Character { body, .. } => match body { + Body::Humanoid(body) => { + if let Some(state) = self.states.get(&entity) { + let model = self.model_cache.get_or_create_model(renderer, *body, tick); + renderer.render_figure( + model, + globals, + &state.locals(), + state.bone_consts(), + ); + } + } // TODO: Non-humanoid bodies + }, + // TODO: Non-character actors + } + } + } +} + +pub struct FigureState { + bone_consts: Consts, + locals: Consts, + skeleton: S, +} + +impl FigureState { + pub fn new(renderer: &mut Renderer, skeleton: S) -> Self { + Self { + bone_consts: renderer + .create_consts(&skeleton.compute_matrices()) + .unwrap(), + locals: renderer.create_consts(&[FigureLocals::default()]).unwrap(), + skeleton, + } + } + + pub fn update(&mut self, renderer: &mut Renderer, pos: Vec3, dir: Vec3) { + let mat = Mat4::::identity() + * Mat4::translation_3d(pos) + * Mat4::rotation_z(-dir.x.atan2(dir.y)); // + f32::consts::PI / 2.0); + + let locals = FigureLocals::new(mat); + renderer.update_consts(&mut self.locals, &[locals]).unwrap(); + + renderer + .update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices()) + .unwrap(); + } + + pub fn locals(&self) -> &Consts { + &self.locals + } + + pub fn bone_consts(&self) -> &Consts { + &self.bone_consts + } + + pub fn skeleton_mut(&mut self) -> &mut S { + &mut self.skeleton + } +} diff --git a/voxygen/src/scene/figure/figurequad.rs b/voxygen/src/scene/figure/figurequad.rs new file mode 100644 index 0000000000..aa76298aa1 --- /dev/null +++ b/voxygen/src/scene/figure/figurequad.rs @@ -0,0 +1,290 @@ +use crate::{ + anim::{ + quadruped::{QuadrupedSkeleton, RunAnimation}, + Animation, Skeleton, + }, + mesh::Meshable, + render::{ + Consts, FigureBoneData, FigureLocals, FigurePipeline, Globals, Mesh, Model, Renderer, + }, + Error, +}; +use client::Client; +use common::{ + assets, + comp::{ + self, + actor::{Head, Chest, l_leg, r_leg}, + Body, QuadrupedBody, + }, + figure::Segment, + msg, +}; +use dot_vox::DotVoxData; +use specs::{Component, Entity as EcsEntity, Join, VecStorage}; +use std::{collections::HashMap, f32}; +use vek::*; + +pub struct FigureModelCache { + models: HashMap, u64)>, +} + +impl FigureModelCache { + pub fn new() -> Self { + Self { + models: HashMap::new(), + } + } + + pub fn get_or_create_model( + &mut self, + renderer: &mut Renderer, + body: QuadrupedBody, + tick: u64, + ) -> &Model { + match self.models.get_mut(&body) { + Some((model, last_used)) => { + *last_used = tick; + } + None => { + self.models.insert( + body, + ( + { + let bone_meshes = [ + Some(Self::load_head(body.head)), + Some(Self::load_chest(body.chest)), + Some(Self::load_lf_leg(body.leg_l)), + Some(Self::load_rf_leg(body.leg_r)), + Some(Self::load_lb_leg(body.leg_l)), + Some(Self::load_rb_leg(body.leg_r)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ]; + + let mut mesh = Mesh::new(); + bone_meshes + .iter() + .enumerate() + .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm))) + .for_each(|(i, bone_mesh)| { + mesh.push_mesh_map(bone_mesh, |vert| { + vert.with_bone_idx(i as u8) + }) + }); + + renderer.create_model(&mesh).unwrap() + }, + tick, + ), + ); + } + } + + &self.models[&body].0 + } + + pub fn clean(&mut self, tick: u64) { + // TODO: Don't hard-code this + self.models + .retain(|_, (_, last_used)| *last_used + 60 > tick); + } + + // TODO: Don't make this public + pub fn load_mesh(filename: &str, position: Vec3) -> Mesh { + let fullpath: String = ["/voxygen/voxel/", filename].concat(); + Segment::from(assets::load_expect::(fullpath.as_str()).as_ref()) + .generate_mesh(position) + } + + fn load_head(head: Head) -> Mesh { + Self::load_mesh( + match head { + Head::Default => "pighead.vox", + }, + Vec3::new(0.0, 0.0, 0.0), + ) + } + + fn load_chest(chest: Chest) -> Mesh { + Self::load_mesh( + match chest { + Chest::Default => "pigchest.vox", + }, + Vec3::new(0.0, 0.0, 0.0), + ) + } + + fn load_lf_leg(leg_l: Leg_l) -> Mesh { + Self::load_mesh( + match belt { + Belt::Default => "pigleg_l.vox", + }, + Vec3::new(0.0, 0.0, 0.0), + ) + } + + fn load_rf_leg(leg_R: Leg_r) -> Mesh { + Self::load_mesh( + match pants { + Pants::Default => "pigleg_r.vox", + }, + Vec3::new(0.0, 0.0, 0.0), + ) + } + + fn load_lb_leg(leg_l: Leg_l) -> Mesh { + Self::load_mesh( + match hand { + Hand::Default => "pigleg_l.vox", + }, + Vec3::new(0.0, 0.0, 0.0), + ) + } + + fn load_rb_leg(leg_R: Leg_r) -> Mesh { + Self::load_mesh( + match hand { + Hand::Default => "pigleg_r.vox", + }, + Vec3::new(0.0, 0.0, 0.0), + ) + } +} + +pub struct FigureMgr { + model_cache: FigureModelCache, + states: HashMap>, +} + +impl FigureMgr { + pub fn new() -> Self { + Self { + model_cache: FigureModelCache::new(), + states: HashMap::new(), + } + } + + pub fn clean(&mut self, tick: u64) { + self.model_cache.clean(tick); + } + + pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { + let time = client.state().get_time(); + let ecs = client.state().ecs(); + for (entity, pos, vel, dir, actor, animation_history) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ) + .join() + { + match actor { + comp::Actor::Quadruped { body, .. } => match body { + Body::Humanoid(body) => { + let state = self.states.entry(entity).or_insert_with(|| { + FigureState::new(renderer, QuadrupedSkeleton::new()) + }); + comp::Animation::Run => RunAnimation::update_skeleton( + state.skeleton_mut(), + (vel.0.magnitude(), time), + animation_history.time, + ), + }; + + state.skeleton.interpolate(&target_skeleton); + + state.update(renderer, pos.0, dir.0); + } // TODO: Non-humanoid bodies + }, + // TODO: Non-character actors + } + } + + self.states + .retain(|entity, _| ecs.entities().is_alive(*entity)); + } + + pub fn render( + &mut self, + renderer: &mut Renderer, + client: &mut Client, + globals: &Consts, + ) { + let tick = client.get_tick(); + let ecs = client.state().ecs(); + + for (entity, actor) in (&ecs.entities(), &ecs.read_storage::()).join() { + match actor { + comp::Actor::Quadruped { body, .. } => match body { + Body::Humanoid(body) => { + if let Some(state) = self.states.get(&entity) { + let model = self.model_cache.get_or_create_model(renderer, *body, tick); + renderer.render_figure( + model, + globals, + &state.locals(), + state.bone_consts(), + ); + } + } // TODO: Non-humanoid bodies + }, + // TODO: Non-character actors + } + } + } +} + +pub struct FigureState { + bone_consts: Consts, + locals: Consts, + skeleton: S, +} + +impl FigureState { + pub fn new(renderer: &mut Renderer, skeleton: S) -> Self { + Self { + bone_consts: renderer + .create_consts(&skeleton.compute_matrices()) + .unwrap(), + locals: renderer.create_consts(&[FigureLocals::default()]).unwrap(), + skeleton, + } + } + + pub fn update(&mut self, renderer: &mut Renderer, pos: Vec3, dir: Vec3) { + let mat = Mat4::::identity() + * Mat4::translation_3d(pos) + * Mat4::rotation_z(-dir.x.atan2(dir.y)); // + f32::consts::PI / 2.0); + + let locals = FigureLocals::new(mat); + renderer.update_consts(&mut self.locals, &[locals]).unwrap(); + + renderer + .update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices()) + .unwrap(); + } + + pub fn locals(&self) -> &Consts { + &self.locals + } + + pub fn bone_consts(&self) -> &Consts { + &self.bone_consts + } + + pub fn skeleton_mut(&mut self) -> &mut S { + &mut self.skeleton + } +}