mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
first quadruped changes
Former-commit-id: 71ad084a872c3bf96d5cbab53c376d214f3cdcd0
This commit is contained in:
parent
3a0befee8c
commit
732c43ce11
BIN
assets/voxygen/voxel/pigchest.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/pigchest.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/pighead.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/pighead.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/pigleg_l.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/pigleg_l.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/pigleg_r.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/pigleg_r.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum Body {
|
pub enum Body {
|
||||||
Humanoid(HumanoidBody),
|
Humanoid(HumanoidBody),
|
||||||
|
Quadruped(QuadrupedBody),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
@ -9,5 +9,6 @@ pub use actor::Animation;
|
|||||||
pub use actor::AnimationHistory;
|
pub use actor::AnimationHistory;
|
||||||
pub use actor::Body;
|
pub use actor::Body;
|
||||||
pub use actor::HumanoidBody;
|
pub use actor::HumanoidBody;
|
||||||
|
pub use actor::QuadrupedBody;
|
||||||
pub use agent::{Agent, Control};
|
pub use agent::{Agent, Control};
|
||||||
pub use player::Player;
|
pub use player::Player;
|
||||||
|
@ -194,7 +194,7 @@ fn handle_pet(server: &mut Server, entity: EcsEntity, args: String, action: &Cha
|
|||||||
server
|
server
|
||||||
.create_npc(
|
.create_npc(
|
||||||
"Bungo".to_owned(),
|
"Bungo".to_owned(),
|
||||||
comp::Body::Humanoid(comp::HumanoidBody::random()),
|
comp::Body::Quadruped(comp::QuadrupedBody::random()),
|
||||||
)
|
)
|
||||||
.with(comp::Control::default())
|
.with(comp::Control::default())
|
||||||
.with(comp::Agent::Pet {
|
.with(comp::Agent::Pet {
|
||||||
|
@ -71,6 +71,7 @@ impl Animation for RunAnimation {
|
|||||||
next.r_shoulder.offset = Vec3::new(0.0, -3.0, 2.5);
|
next.r_shoulder.offset = Vec3::new(0.0, -3.0, 2.5);
|
||||||
next.r_shoulder.ori = Quaternion::rotation_x(0.0);
|
next.r_shoulder.ori = Quaternion::rotation_x(0.0);
|
||||||
next.r_shoulder.scale = Vec3::one();
|
next.r_shoulder.scale = Vec3::one();
|
||||||
|
|
||||||
next.torso.offset = Vec3::new(-0.5, -0.2, 0.4);
|
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.ori = Quaternion::rotation_x(-velocity * 0.05 - wavecos * 0.1);
|
||||||
next.torso.scale = Vec3::one() / 11.0;
|
next.torso.scale = Vec3::one() / 11.0;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
pub mod character;
|
pub mod character;
|
||||||
|
pub mod quadruped;
|
||||||
// Library
|
// Library
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
68
voxygen/src/anim/quadruped/mod.rs
Normal file
68
voxygen/src/anim/quadruped/mod.rs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
57
voxygen/src/anim/quadruped/run.rs
Normal file
57
voxygen/src/anim/quadruped/run.rs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -308,7 +308,9 @@ impl FigureMgr {
|
|||||||
state.bone_consts(),
|
state.bone_consts(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Body::Quadruped(body) => {..};
|
||||||
} // TODO: Non-humanoid bodies
|
} // TODO: Non-humanoid bodies
|
||||||
|
|
||||||
},
|
},
|
||||||
// TODO: Non-character actors
|
// TODO: Non-character actors
|
||||||
}
|
}
|
||||||
|
360
voxygen/src/scene/figure/figure.rs
Normal file
360
voxygen/src/scene/figure/figure.rs
Normal file
@ -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<HumanoidBody, (Model<FigurePipeline>, 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<FigurePipeline> {
|
||||||
|
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<f32>) -> Mesh<FigurePipeline> {
|
||||||
|
let fullpath: String = ["/voxygen/voxel/", filename].concat();
|
||||||
|
Segment::from(assets::load_expect::<DotVoxData>(fullpath.as_str()).as_ref())
|
||||||
|
.generate_mesh(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_head(head: Head) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match head {
|
||||||
|
Head::Default => "head.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(-7.0, -5.5, -6.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_chest(chest: Chest) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match chest {
|
||||||
|
Chest::Default => "chest.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(-6.0, -3.5, 0.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_belt(belt: Belt) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match belt {
|
||||||
|
Belt::Default => "belt.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(-5.0, -3.5, 0.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_pants(pants: Pants) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match pants {
|
||||||
|
Pants::Default => "pants.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(-5.0, -3.5, 0.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_left_hand(hand: Hand) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match hand {
|
||||||
|
Hand::Default => "hand.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(2.0, 0.0, -7.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_right_hand(hand: Hand) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match hand {
|
||||||
|
Hand::Default => "hand.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(2.0, 0.0, -7.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_left_foot(foot: Foot) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match foot {
|
||||||
|
Foot::Default => "foot.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(2.5, -3.5, -9.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_right_foot(foot: Foot) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match foot {
|
||||||
|
Foot::Default => "foot.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(2.5, -3.5, -9.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_weapon(weapon: Weapon) -> Mesh<FigurePipeline> {
|
||||||
|
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<FigurePipeline> {
|
||||||
|
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<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match shoulder {
|
||||||
|
Shoulder::Default => "shoulder_r.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(2.5, 0.0, 0.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// fn load_draw(draw: Draw) -> Mesh<FigurePipeline> {
|
||||||
|
// 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<EcsEntity, FigureState<CharacterSkeleton>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<comp::phys::Pos>(),
|
||||||
|
&ecs.read_storage::<comp::phys::Vel>(),
|
||||||
|
&ecs.read_storage::<comp::phys::Dir>(),
|
||||||
|
&ecs.read_storage::<comp::Actor>(),
|
||||||
|
&ecs.read_storage::<comp::AnimationHistory>(),
|
||||||
|
)
|
||||||
|
.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<Globals>,
|
||||||
|
) {
|
||||||
|
let tick = client.get_tick();
|
||||||
|
let ecs = client.state().ecs();
|
||||||
|
|
||||||
|
for (entity, actor) in (&ecs.entities(), &ecs.read_storage::<comp::Actor>()).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<S: Skeleton> {
|
||||||
|
bone_consts: Consts<FigureBoneData>,
|
||||||
|
locals: Consts<FigureLocals>,
|
||||||
|
skeleton: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Skeleton> FigureState<S> {
|
||||||
|
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<f32>, dir: Vec3<f32>) {
|
||||||
|
let mat = Mat4::<f32>::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<FigureLocals> {
|
||||||
|
&self.locals
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bone_consts(&self) -> &Consts<FigureBoneData> {
|
||||||
|
&self.bone_consts
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skeleton_mut(&mut self) -> &mut S {
|
||||||
|
&mut self.skeleton
|
||||||
|
}
|
||||||
|
}
|
290
voxygen/src/scene/figure/figurequad.rs
Normal file
290
voxygen/src/scene/figure/figurequad.rs
Normal file
@ -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<QuadrupedBody, (Model<FigurePipeline>, 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<FigurePipeline> {
|
||||||
|
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<f32>) -> Mesh<FigurePipeline> {
|
||||||
|
let fullpath: String = ["/voxygen/voxel/", filename].concat();
|
||||||
|
Segment::from(assets::load_expect::<DotVoxData>(fullpath.as_str()).as_ref())
|
||||||
|
.generate_mesh(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_head(head: Head) -> Mesh<FigurePipeline> {
|
||||||
|
Self::load_mesh(
|
||||||
|
match head {
|
||||||
|
Head::Default => "pighead.vox",
|
||||||
|
},
|
||||||
|
Vec3::new(0.0, 0.0, 0.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_chest(chest: Chest) -> Mesh<FigurePipeline> {
|
||||||
|
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<FigurePipeline> {
|
||||||
|
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<FigurePipeline> {
|
||||||
|
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<FigurePipeline> {
|
||||||
|
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<FigurePipeline> {
|
||||||
|
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<EcsEntity, FigureState<QuadrupedSkeleton>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<comp::phys::Pos>(),
|
||||||
|
&ecs.read_storage::<comp::phys::Vel>(),
|
||||||
|
&ecs.read_storage::<comp::phys::Dir>(),
|
||||||
|
&ecs.read_storage::<comp::Actor>(),
|
||||||
|
&ecs.read_storage::<comp::AnimationHistory>(),
|
||||||
|
)
|
||||||
|
.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<Globals>,
|
||||||
|
) {
|
||||||
|
let tick = client.get_tick();
|
||||||
|
let ecs = client.state().ecs();
|
||||||
|
|
||||||
|
for (entity, actor) in (&ecs.entities(), &ecs.read_storage::<comp::Actor>()).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<S: Skeleton> {
|
||||||
|
bone_consts: Consts<FigureBoneData>,
|
||||||
|
locals: Consts<FigureLocals>,
|
||||||
|
skeleton: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Skeleton> FigureState<S> {
|
||||||
|
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<f32>, dir: Vec3<f32>) {
|
||||||
|
let mat = Mat4::<f32>::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<FigureLocals> {
|
||||||
|
&self.locals
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bone_consts(&self) -> &Consts<FigureBoneData> {
|
||||||
|
&self.bone_consts
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skeleton_mut(&mut self) -> &mut S {
|
||||||
|
&mut self.skeleton
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user