From 8c6f26172187220566730f1a0fd21df6f8b29956 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 | Bin 0 -> 2612 bytes assets/voxygen/voxel/pighead.vox | Bin 0 -> 2720 bytes assets/voxygen/voxel/pigleg_l.vox | Bin 0 -> 1144 bytes assets/voxygen/voxel/pigleg_r.vox | Bin 0 -> 1144 bytes 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, 818 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 0000000000000000000000000000000000000000..7fd1aadf10e4922f945f58217ef4efde78255097 GIT binary patch literal 2612 zcmc(fZ)l#?8OHDP=Q-zjn>ngYQI5My3#t(b)A&rD^`8U%48q9B1Hrd=|qH4M8?>sfggtW?b`1={Wi2;4g9#z zxzByB`#SggzS()t{r$(xY}>}My9w4m&uquo_~70Nl_ zuX6MT?q- zBG4z0^W+@b!FUgOU;103hI{B$O}?y44e#Jijjs$fesb|dP2fC{^NnWFrJlVt^JHH5 z3cN3rJTnQ!BRN+hbE=tz=WV`;_#hXULtu`9c?M>i%6;A$m|bdg=!sTpH0Xt1YP9fH z?lZSQ|BP-%H!#!8-N5{FIcK(k*|*RR%rNlT1U?5VddxNO*#u@>vu}ato=4B4>+$59 z?*P8==+W}%rzTGh%@%!I)FwQq;u#OA$(7oK22=%f8z1P>zlBy!ubN(US<@#K9rlSH zjv9xS64)Eh-^4mKYkINId|owVYBTT3oaH9Zo2YFychIILp_PknO6^u+PKmpT z-yQo10W33*T(zy7XYO&{BA3av65pN7=U46#9r+q!<~Nz>TOjWtlTQ#h3q%hYy#xU~ zROBL5tRp0PR1hIRLa%}d0TQ|uL5A)v8>5^2)x5Xh6EKvsKG(tO@X>p zU|r!iL2lOUi8+yj3TpNaJk-p=LF8^ER!#46r}!~E@Yd!oHN;Hb$XSWpH|jm>M9gsC z9Y5S?@$&!vb?eq?W@bj89x-is>4L8B{zCul?$-J9+f@Eu8~gJWnLpB_-iQ0uzjuX( zPOO)WRBd%nh>S)k|7 zeoLEYOxur|etyQZ=L7C;@6(cr0WF&u*4nudEuQMuz@a`3pIWMw?=07%hfK?!LHn3# z!#R`vnEunX?C3XToo*diezi`2ccDJKZLzfV+d68dv+xo83ywZ*I`ccz`O~I%-(){x zIyq|k%YCLxkD12VKfTj*_!06?nKliXcC9l#wb}Fn`R!}^G%-4$>Ft9$_UJ0j{NxrL zdwi45KlfAp`_zP1T;HP&tCs4{9}MczowsOmt7-OUrW3z3o%^k6?UFu?4KLF}Yj4uj z=Jnb++@py#i*@LZ8#K4;W{uywQIikft(hku)WN-vY5EuAI=p{UbElux#kXHlS67#= zU2wJf`}?(WXh3Ukx>4U>HKfrED|OG0*6Z-EuhW@74(Q`|hjr=w6?&1LbEi!2{nhl( zzni8f2KB=1YF&73lRo>$4ow}LuS2hN>&*{(bxBLV_V=$|y;_r7rgZblle+c5KK0J_ z>YYECK77#h>foS;hK97Of4A=1(vi(hYvI&Fojrc}Gu+$9cW22G?OXn$Zol=29y$1| zuHCgr@4tS7&W`u$2s8Wt`Iow)^}vqzH1)W&=lE&OzC5Sjbl%qQ|8`N|d-8y8-*-ZH z{^6W&Ofy;KmX*3C&aI?`JH`LEQeopR`ORm KYlN?S-~I>LK`;gY literal 0 HcmV?d00001 diff --git a/assets/voxygen/voxel/pighead.vox b/assets/voxygen/voxel/pighead.vox new file mode 100644 index 0000000000000000000000000000000000000000..b54551b43fa849bede4a62bb4537c3414cc07935 GIT binary patch literal 2720 zcmc(gU5H-Q6^8fO`|O|deQin1Wa3P0G99CtnG|h)j7d%Hq{)~}G}@Rp)iDiH6di_G zGl-aB2$*T4X_^|+Fj`P!EFtt0qaMa!@gfEZB8W&TrBH&1NUsXLD5ZCApM7#Cn%fS% z?4PyQTJL(-I^XQN=f1vKW6buAqj$5I-?og|IXX7-jeOuQuL)G(zWc{UcLz57%@*7j zMWp8|3u2VO;Tt+G zpYe4?-g|(JcrM%Wdz=$v6R_Mx_($GG_CCjHYRsI;iEqF+@FrmM5tELd>!%1ywa!AeV__O$HKoXBYO+-UADs)co0AYiMj)Mi_+H? zzLq=&?QG`i0_T!g%p|-#I#*3zdJ+OLm>V>Uc|`};tZ0ZXmixrn(hJ|{h-UEiKGJV^ zqh)CJ^bBnam&hM#Q__EOdiZ-Z@aXEO!xJ;*HTMZWM~v94d47poCiH<9aU6Ac?i_JK zbI*)4_uQeqrx&jD$^=2)&OZBP zZL(j(FUOB{Y`8FE*eZ~+9>lLeqLvEy%o9F=etTxs(+8h>PtO9g>GOOtTPgUILb=b< zN0(Q4SJa^x0+~^oebb6^)zQ0_Hb{j2wn%J{O8_pYOx>l!%jfIN_ZI4tA1s!( zeMcvZ(K+x}@C7*Wl+oGW8(lbK^ue2)hmB5+7`=V3(d9>s#yCH<%jnoc+&^iwY0zlT zI-@5y8$Hkcjy1g+AL-ZBjseX+yh_tQy;ZZ1ZqkKc|4jclJ+2kkcWc9{rMmNn1A2JZ zt(w?oH1l(#lTR3(f7WR2l3tAtEz|C`H)(S7dhHtO*7%ymI=bZs&F#5aV?W-gi3ji2 z^y3fc$bm;R^~*6GJ2auWGtcPKdoQZpZtL0w^VQearJ{?)5jYhvr9ZeDpxw;evHo|zuK|BBHk4;Z~VFrdM~LG9_=tGl+=GBZgp1ab+1HH`7k|jF0`~}^9+i^W~(OyE`?CJ|U5<7C&JQ*D zsI>pNGn)D1oPJlor$4-VNw++HShpWMsXJdfubppP((v%G{<&W=3Ftp^myACB^A&zN kbf~d^X1?0~>0^(Hudw;fzHZINue0U*tJ#{sSNAvn1Bx<1MF0Q* literal 0 HcmV?d00001 diff --git a/assets/voxygen/voxel/pigleg_l.vox b/assets/voxygen/voxel/pigleg_l.vox new file mode 100644 index 0000000000000000000000000000000000000000..11848b935e25dced04b57f7151b05ead7397b291 GIT binary patch literal 1144 zcmc(eUr3W-6vof`1CkY*bGBK!rsX!H@=vB^HPe-|tjw}nB@s%FVa15jF$7&kCYG3} zfn^pV8JUhHMHeO^Aw+~l1R)U-T?Ji4bocB1w5up@JMi$F^E}@<@Atjm`r}pP$F@9C^R>v%&L$8D@UcxOyfIFa{wLq{dd9|z(c-@A;jD;q7%(0;W3EfZ z){}!c=Of+)NbeG4+(1;Z5LYcMYZX=x2@zglQBa6cwc(zSSsY1GbrkOU7%q=2rywAd zv(%TIA$L@e{c3%G1k%13 z3F&D{iKc&DEN?c)L(vM_MHo?dtMFN&y+#(LQ17q~KO{O_|VI zq4aY?zD+347S0t2T}m&>G~sbt@RwNWs7xntU;`ah`HWpU$d^73sYwQM(-Yab)kGpVzakU&F9H(NHfQPF&nrR8zF ze6W%cw~;orXMgU$_MA5BaOn$tRZ!d6Pw-BdE1_pxe>%zfGfix1=wav0QA&poZ9YfG%WMYYl z6=Y^1l9AO|QgmSw5w z$_m`uRFmpiA{B0rYpzaI&6vVW)%Wi6xU+Rp)s!FBM8cXTb)v~>O`9>Gi8dgbg6f(m zu8)R#uUBqvF2P`sPaQ%@_bAH@zxZJ=FfvkxUU%IOr$vm@i0P08OG7I5 zo;<`g8}ZFSdKV(2Mq*)W249%grQutt1B>x98e!brdH;(_8$q1Pom+9`}55j=|5RSNCZ zNM|qwnMrKfXrrcb z4Zcz#bWrFyDGXl}auUtB9myQXNyA^1N2SAvH#>p0;^joD((&vlz;|dH!4vywZ8$>U zxQF&;ACdmEj6LfnIy#y~u?w(REM(ZNPZ37Qpn;0kQzrTN0Ru;YzKj|60tZQk) z6f*Jrrto&ZaK~oDZnslqsbyl&z zb#FOC9upnv%>F!o{W(2rU&TxON1^_5KcQO@E`* 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 + } +}