Merge branch 'gliding' into 'master'

Gliding

See merge request veloren/veloren!140

Former-commit-id: 7e1471af2a250e28557d593944719954e6af4bbe
This commit is contained in:
Joshua Barretto 2019-05-16 22:18:12 +00:00
commit 0cf9a602e2
43 changed files with 1431 additions and 111 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -7,6 +7,7 @@ pub enum InputEvent {
pub struct Input {
pub move_dir: Vec2<f32>,
pub jumping: bool,
pub gliding: bool,
pub events: Vec<InputEvent>,
}
@ -15,6 +16,7 @@ impl Default for Input {
Input {
move_dir: Vec2::zero(),
jumping: false,
gliding: false,
events: Vec::new(),
}
}

View File

@ -19,7 +19,11 @@ use common::{
terrain::TerrainChunk,
};
use specs::Builder;
use std::{collections::HashSet, net::SocketAddr, time::Duration};
use std::{
collections::HashMap,
net::SocketAddr,
time::{Duration, Instant},
};
use threadpool::ThreadPool;
use vek::*;
@ -42,7 +46,7 @@ pub struct Client {
entity: EcsEntity,
view_distance: u64,
pending_chunks: HashSet<Vec3<i32>>,
pending_chunks: HashMap<Vec3<i32>, Instant>,
}
impl Client {
@ -82,7 +86,7 @@ impl Client {
entity,
view_distance,
pending_chunks: HashSet::new(),
pending_chunks: HashMap::new(),
})
}
@ -156,6 +160,7 @@ impl Client {
comp::Control {
move_dir: input.move_dir,
jumping: input.jumping,
gliding: input.gliding,
},
);
@ -202,7 +207,7 @@ impl Client {
if (Vec2::from(chunk_pos) - Vec2::from(key))
.map(|e: i32| e.abs())
.reduce_max()
> 6
> 10
{
chunks_to_remove.push(key);
}
@ -213,18 +218,18 @@ impl Client {
// Request chunks from the server
// TODO: This is really not very efficient
'outer: for dist in 0..9 {
'outer: for dist in 0..10 {
for i in chunk_pos.x - dist..chunk_pos.x + dist + 1 {
for j in chunk_pos.y - dist..chunk_pos.y + dist + 1 {
for k in 0..3 {
for k in 0..6 {
let key = Vec3::new(i, j, k);
if self.state.terrain().get_key(key).is_none()
&& !self.pending_chunks.contains(&key)
&& !self.pending_chunks.contains_key(&key)
{
if self.pending_chunks.len() < 4 {
self.postbox
.send_message(ClientMsg::TerrainChunkRequest { key });
self.pending_chunks.insert(key);
self.pending_chunks.insert(key, Instant::now());
} else {
break 'outer;
}
@ -233,6 +238,11 @@ impl Client {
}
}
}
// If chunks are taking too long, assume they're no longer pending
let now = Instant::now();
self.pending_chunks
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(10));
}
// Finish the tick, pass control back to the frontend (step 6)

View File

@ -69,6 +69,26 @@ pub enum Shoulder {
pub enum Draw {
Default,
}
////
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Pighead {
Default,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Pigchest {
Default,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Pigleg_l {
Default,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Pigleg_r {
Default,
}
////
const ALL_RACES: [Race; 6] = [
Race::Danari,
@ -129,10 +149,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: [Pighead; 1] = [Pighead::Default];
const ALL_QCHESTS: [Pigchest; 1] = [Pigchest::Default];
const ALL_QPIGLEG_LS: [Pigleg_l; 1] = [Pigleg_l::Default];
const ALL_QPIGLEG_RS: [Pigleg_r; 1] = [Pigleg_r::Default];
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct QuadrupedBody {
pub race: Race,
pub body_type: BodyType,
pub pighead: Pighead,
pub pigchest: Pigchest,
pub pigleg_l: Pigleg_l,
pub pigleg_r: Pigleg_r,
}
impl QuadrupedBody {
pub fn random() -> Self {
Self {
race: *thread_rng().choose(&ALL_QRACES).unwrap(),
body_type: *thread_rng().choose(&ALL_QBODY_TYPES).unwrap(),
pighead: *thread_rng().choose(&ALL_QHEADS).unwrap(),
pigchest: *thread_rng().choose(&ALL_QCHESTS).unwrap(),
pigleg_l: *thread_rng().choose(&ALL_QPIGLEG_LS).unwrap(),
pigleg_r: *thread_rng().choose(&ALL_QPIGLEG_RS).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)]
@ -166,6 +223,7 @@ pub enum Animation {
Idle,
Run,
Jump,
Gliding,
}
impl Component for AnimationHistory {

View File

@ -18,6 +18,7 @@ impl Component for Agent {
pub struct Control {
pub move_dir: Vec2<f32>,
pub jumping: bool,
pub gliding: bool,
}
impl Default for Control {
@ -25,6 +26,7 @@ impl Default for Control {
Self {
move_dir: Vec2::zero(),
jumping: false,
gliding: false,
}
}
}

View File

@ -2,6 +2,7 @@ pub mod actor;
pub mod agent;
pub mod phys;
pub mod player;
pub mod stats;
// Reexports
pub use actor::Actor;
@ -9,5 +10,7 @@ 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;
pub use stats::Stats;

View File

@ -7,7 +7,7 @@ use vek::*;
pub struct Pos(pub Vec3<f32>);
impl Component for Pos {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
type Storage = VecStorage<Self>;
}
// Vel
@ -16,7 +16,7 @@ impl Component for Pos {
pub struct Vel(pub Vec3<f32>);
impl Component for Vel {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
type Storage = VecStorage<Self>;
}
// Dir
@ -25,7 +25,7 @@ impl Component for Vel {
pub struct Dir(pub Vec3<f32>);
impl Component for Dir {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
type Storage = VecStorage<Self>;
}
// Dir

38
common/src/comp/stats.rs Normal file
View File

@ -0,0 +1,38 @@
use specs::{Component, FlaggedStorage, VecStorage};
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Health {
pub current: u32,
pub maximum: u32,
pub last_change: Option<(i32, f64)>,
}
impl Health {
pub fn change_by(&mut self, amount: i32, current_time: f64) {
self.current = (self.current as i32 + amount).max(0) as u32;
self.last_change = Some((amount, current_time));
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Stats {
pub hp: Health,
pub xp: u32,
}
impl Default for Stats {
fn default() -> Self {
Self {
hp: Health {
current: 100,
maximum: 100,
last_change: None,
},
xp: 0,
}
}
}
impl Component for Stats {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}

View File

@ -9,7 +9,7 @@ pub enum ClientMsg {
},
Character {
name: String,
body: comp::HumanoidBody,
body: comp::Body,
},
RequestState(ClientState),
Ping,

View File

@ -11,6 +11,7 @@ sphynx::sum_type! {
Dir(comp::phys::Dir),
Actor(comp::Actor),
Player(comp::Player),
Stats(comp::Stats),
}
}
// Automatically derive From<T> for Phantom for each variant Phantom::T(PhantomData<T>)
@ -22,6 +23,7 @@ sphynx::sum_type! {
Dir(PhantomData<comp::phys::Dir>),
Actor(PhantomData<comp::Actor>),
Player(PhantomData<comp::Player>),
Stats(PhantomData<comp::Stats>),
}
}
impl sphynx::Packet for EcsPacket {

View File

@ -97,6 +97,7 @@ impl State {
// Register synced components
ecs.register_synced::<comp::Actor>();
ecs.register_synced::<comp::Player>();
ecs.register_synced::<comp::Stats>();
// Register unsynched (or synced by other means) components
ecs.register::<comp::phys::Pos>();

View File

@ -41,7 +41,7 @@ impl<'a> System<'a> for Sys {
let dist = tgt_pos.distance(pos.0);
control.move_dir = if dist > 5.0 {
Vec2::from(tgt_pos - pos.0).normalized()
} else if dist < 1.5 && pos.0 != tgt_pos {
} else if dist < 1.5 && dist > 0.0 {
Vec2::from(pos.0 - tgt_pos).normalized()
} else {
Vec2::zero()

View File

@ -41,27 +41,55 @@ impl<'a> System<'a> for Sys {
.unwrap_or(false)
&& vel.0.z <= 0.0;
if on_ground {
let (gliding, friction) = if on_ground {
// TODO: Don't hard-code this
// Apply physics to the player: acceleration and non-linear decceleration
vel.0 += control.move_dir * 4.0 - vel.0.map(|e| e * vel.0.magnitude() + e) * 0.05;
vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 200.0;
if control.jumping {
vel.0.z += 16.0;
}
(false, 0.15)
} else {
// TODO: Don't hard-code this
// Apply physics to the player: acceleration and non-linear decceleration
vel.0 += control.move_dir * 0.2 - vel.0.map(|e| e * e.abs() + e) * 0.002;
vel.0 += Vec2::broadcast(dt.0) * control.move_dir * 10.0;
if control.gliding && vel.0.z < 0.0 {
// TODO: Don't hard-code this
let anti_grav = 9.81 * 3.95 + vel.0.z.powf(2.0) * 0.2;
vel.0.z +=
dt.0 * anti_grav * Vec2::<f32>::from(vel.0 * 0.15).magnitude().min(1.0);
(true, 0.008)
} else {
(false, 0.015)
}
};
// Friction
vel.0 -= Vec2::broadcast(dt.0)
* 50.0
* vel.0.map(|e| {
(e.abs() * friction * (vel.0.magnitude() * 0.1 + 0.5))
.min(e.abs())
.copysign(e)
})
* Vec3::new(1.0, 1.0, 0.0);
if vel.0.magnitude_squared() != 0.0 {
dir.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0);
}
let animation = if on_ground {
if control.move_dir.magnitude() > 0.01 {
dir.0 = vel.0.normalized() * Vec3::new(1.0, 1.0, 0.0);
Animation::Run
} else {
Animation::Idle
}
} else if gliding {
Animation::Gliding
} else {
Animation::Jump
};

View File

@ -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 {

View File

@ -125,6 +125,7 @@ impl Server {
.with(comp::phys::Dir(Vec3::unit_y()))
.with(comp::AnimationHistory::new(comp::Animation::Idle))
.with(comp::Actor::Character { name, body })
.with(comp::Stats::default())
}
pub fn create_player_character(
@ -132,15 +133,10 @@ impl Server {
entity: EcsEntity,
client: &mut Client,
name: String,
body: comp::HumanoidBody,
body: comp::Body,
) {
state.write_component(
entity,
comp::Actor::Character {
name,
body: comp::Body::Humanoid(body),
},
);
state.write_component(entity, comp::Actor::Character { name, body });
state.write_component(entity, comp::Stats::default());
state.write_component(entity, comp::phys::Pos(Vec3::new(0.0, 0.0, 64.0)));
state.write_component(entity, comp::phys::Vel(Vec3::zero()));
state.write_component(entity, comp::phys::Dir(Vec3::unit_y()));

View File

@ -10,6 +10,7 @@ flat in uint f_bone_idx;
layout (std140)
uniform u_locals {
mat4 model_mat;
vec4 model_col;
};
struct BoneData {
@ -36,5 +37,5 @@ void main() {
float sun_diffuse = dot(sun_dir, world_norm) * 0.5;
tgt_color = vec4(f_col * (ambient + sun_diffuse), 1.0);
tgt_color = model_col * vec4(f_col * (ambient + sun_diffuse), 1.0);
}

View File

@ -10,6 +10,7 @@ in uint v_bone_idx;
layout (std140)
uniform u_locals {
mat4 model_mat;
vec4 model_col;
};
struct BoneData {

View File

@ -0,0 +1,107 @@
// Standard
use std::{f32::consts::PI, ops::Mul};
// Library
use vek::*;
// Local
use super::{super::Animation, CharacterSkeleton, SCALE};
//
pub struct GlidingAnimation;
impl Animation for GlidingAnimation {
type Skeleton = CharacterSkeleton;
type Dependency = f64;
fn update_skeleton(
skeleton: &Self::Skeleton,
global_time: f64,
anim_time: f64,
) -> Self::Skeleton {
let mut next = (*skeleton).clone();
let wave = (anim_time as f32 * 14.0).sin();
let waveslow = (anim_time as f32 * 7.0).sin();
let wavecos_slow = (anim_time as f32 * 7.0).cos();
let arcwave = (1.0f32.ln_1p() - 1.5).abs();
let wavetest = (wave.cbrt());
let fuzzwave = (anim_time as f32 * 12.0).sin();
let wavecos = (anim_time as f32 * 14.0).cos();
let wave_stop = (anim_time as f32 * 1.5).min(PI / 2.0).sin();
let wave_stopalt = (anim_time as f32 * 5.0).min(PI / 2.0).sin();
let waveveryslow = (anim_time as f32 * 3.0).sin();
let waveveryslowalt = (anim_time as f32 * 2.5).sin();
let waveveryslowcos = (anim_time as f32 * 3.0).cos();
let wave_slowtest = (anim_time as f32).min(PI / 2.0).sin();
let head_look = Vec2::new(
((global_time + anim_time) as f32 / 4.0)
.floor()
.mul(7331.0)
.sin()
* 0.5,
((global_time + anim_time) as f32 / 4.0)
.floor()
.mul(1337.0)
.sin()
* 0.25,
);
next.head.offset = Vec3::new(5.5, 2.0, 12.0);
next.head.ori = Quaternion::rotation_x(0.35 - waveveryslow * 0.10 + head_look.y)
* Quaternion::rotation_z(head_look.x + waveveryslowcos * 0.15);
next.head.scale = Vec3::one();
next.chest.offset = Vec3::new(5.5, 0.0, 8.0);
next.chest.ori = Quaternion::rotation_z(waveveryslowcos * 0.15);
next.chest.scale = Vec3::one();
next.belt.offset = Vec3::new(5.5, 0.0, 6.0);
next.belt.ori = Quaternion::rotation_z(waveveryslowcos * 0.20);
next.belt.scale = Vec3::one();
next.shorts.offset = Vec3::new(5.5, 0.0, 3.0);
next.shorts.ori = Quaternion::rotation_z(waveveryslowcos * 0.25);
next.shorts.scale = Vec3::one();
next.l_hand.offset = Vec3::new(-8.0, -10.0 + waveveryslow * 2.5, 18.5 + waveveryslow * 1.0);
next.l_hand.ori = Quaternion::rotation_x(0.9 - waveveryslow * 0.10);
next.l_hand.scale = Vec3::one();
next.r_hand.offset = Vec3::new(11.0, -10.0 + waveveryslow * 2.5, 18.5 + waveveryslow * 1.0);
next.r_hand.ori = Quaternion::rotation_x(0.9 - waveveryslow * 0.10);
next.r_hand.scale = Vec3::one();
next.l_foot.offset = Vec3::new(-3.4, 1.0, 8.0);
next.l_foot.ori =
Quaternion::rotation_x(wave_stop * -0.7 - wavecos_slow * -0.21 + waveveryslow * 0.19);
next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(3.4, 1.0, 8.0);
next.r_foot.ori =
Quaternion::rotation_x(wave_stop * -0.8 + waveslow * -0.25 + waveveryslowalt * 0.13);
next.r_foot.scale = Vec3::one();
next.weapon.offset = Vec3::new(-5.0, -6.0, 19.0);
next.weapon.ori = Quaternion::rotation_y(2.5);
next.weapon.scale = Vec3::one();
next.l_shoulder.offset = Vec3::new(-10.0, -3.0, 2.5);
next.l_shoulder.ori = Quaternion::rotation_x(0.0);
next.l_shoulder.scale = Vec3::one();
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.0);
next.torso.ori = Quaternion::rotation_x(-0.8 + waveveryslow * 0.10);
next.torso.scale = Vec3::one() / 11.0;
next.draw.offset = Vec3::new(13.5, 3.0, -1.0);
next.draw.ori = Quaternion::rotation_y(waveveryslowcos * 0.05);
next.draw.scale = Vec3::one();
next
}
}

View File

@ -100,9 +100,9 @@ impl Animation for IdleAnimation {
next.torso.ori = Quaternion::rotation_x(0.0);
next.torso.scale = Vec3::one() / 11.0;
next.draw.offset = Vec3::new(0.0, 1.0, -8.0);
next.draw.ori = Quaternion::rotation_y(-0.2);
next.draw.scale = Vec3::one();
next.draw.offset = Vec3::new(13.5, 0.0, 0.0);
next.draw.ori = Quaternion::rotation_y(0.0);
next.draw.scale = Vec3::one() * 0.0;
next
}
}

View File

@ -80,6 +80,9 @@ impl Animation for JumpAnimation {
next.torso.ori = Quaternion::rotation_x(-0.2);
next.torso.scale = Vec3::one() / 11.0;
next.draw.offset = Vec3::new(13.5, 0.0, 0.0);
next.draw.ori = Quaternion::rotation_y(0.0);
next.draw.scale = Vec3::one() * 0.0;
next
}
}

View File

@ -1,8 +1,10 @@
pub mod gliding;
pub mod idle;
pub mod jump;
pub mod run;
// Reexports
pub use self::gliding::GlidingAnimation;
pub use self::idle::IdleAnimation;
pub use self::jump::JumpAnimation;
pub use self::run::RunAnimation;
@ -27,8 +29,8 @@ pub struct CharacterSkeleton {
weapon: Bone,
l_shoulder: Bone,
r_shoulder: Bone,
torso: Bone,
draw: Bone,
torso: Bone,
}
impl CharacterSkeleton {
@ -45,8 +47,8 @@ impl CharacterSkeleton {
weapon: Bone::default(),
l_shoulder: Bone::default(),
r_shoulder: Bone::default(),
torso: Bone::default(),
draw: Bone::default(),
torso: Bone::default(),
}
}
}
@ -68,8 +70,8 @@ impl Skeleton for CharacterSkeleton {
FigureBoneData::new(torso_mat * chest_mat * self.weapon.compute_base_matrix()),
FigureBoneData::new(torso_mat * chest_mat * self.l_shoulder.compute_base_matrix()),
FigureBoneData::new(torso_mat * chest_mat * self.r_shoulder.compute_base_matrix()),
FigureBoneData::new(torso_mat),
FigureBoneData::new(torso_mat * l_hand_mat * self.draw.compute_base_matrix()),
FigureBoneData::new(torso_mat),
FigureBoneData::default(),
FigureBoneData::default(),
FigureBoneData::default(),
@ -88,7 +90,7 @@ impl Skeleton for CharacterSkeleton {
self.weapon.interpolate(&target.weapon);
self.l_shoulder.interpolate(&target.l_shoulder);
self.r_shoulder.interpolate(&target.r_shoulder);
self.torso.interpolate(&target.torso);
self.draw.interpolate(&target.draw);
self.torso.interpolate(&target.torso);
}
}

View File

@ -71,13 +71,14 @@ 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;
next.draw.offset = Vec3::new(0.0, 1.0, -8.0);
next.draw.ori = Quaternion::rotation_y(-0.2);
next.draw.scale = Vec3::one();
next.draw.offset = Vec3::new(13.5, 0.0, 0.0);
next.draw.ori = Quaternion::rotation_y(0.0);
next.draw.scale = Vec3::one() * 0.0;
next
}

View File

@ -1,5 +1,5 @@
pub mod character;
pub mod quadruped;
// Library
use vek::*;

View File

@ -0,0 +1,73 @@
// Standard
use std::{f32::consts::PI, ops::Mul};
// Library
use vek::*;
// Local
use super::{super::Animation, QuadrupedSkeleton, SCALE};
pub struct IdleAnimation;
impl Animation for IdleAnimation {
type Skeleton = QuadrupedSkeleton;
type Dependency = (f64);
fn update_skeleton(
skeleton: &Self::Skeleton,
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 waveultra_slow = (anim_time as f32 * 1.0 + PI).sin();
let waveultracos_slow = (anim_time as f32 * 1.0 + PI).cos();
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 * 3.5 + PI).sin();
let wavecos_slow = (anim_time as f32 * 3.5 + PI).cos();
let wave_dip = (wave_slow.abs() - 0.5).abs();
let pighead_look = Vec2::new(
((global_time + anim_time) as f32 / 8.0)
.floor()
.mul(7331.0)
.sin()
* 0.5,
((global_time + anim_time) as f32 / 8.0)
.floor()
.mul(1337.0)
.sin()
* 0.25,
);
next.pighead.offset = Vec3::new(0.0, -2.0, -1.5 + wave * 0.2) / 11.0;
next.pighead.ori = Quaternion::rotation_z(pighead_look.x)
* Quaternion::rotation_x(pighead_look.y + wavecos_slow * 0.03);
next.pighead.scale = Vec3::one() / 10.5;
next.pigchest.offset = Vec3::new(wave_slow * 0.05, -9.0, 1.5 + wavecos_slow * 0.4) / 11.0;
next.pigchest.ori = Quaternion::rotation_y(wave_slow * 0.05);
next.pigchest.scale = Vec3::one() / 11.0;
next.piglf_leg.offset = Vec3::new(-4.5, 2.0, 1.5) / 11.0;
next.piglf_leg.ori = Quaternion::rotation_x(wave_slow * 0.08);
next.piglf_leg.scale = Vec3::one() / 11.0;
next.pigrf_leg.offset = Vec3::new(2.5, 2.0, 1.5) / 11.0;
next.pigrf_leg.ori = Quaternion::rotation_x(wavecos_slow * 0.08);
next.pigrf_leg.scale = Vec3::one() / 11.0;
next.piglb_leg.offset = Vec3::new(-4.5, -3.0, 1.5) / 11.0;
next.piglb_leg.ori = Quaternion::rotation_x(wavecos_slow * 0.08);
next.piglb_leg.scale = Vec3::one() / 11.0;
next.pigrb_leg.offset = Vec3::new(2.5, -3.0, 1.5) / 11.0;
next.pigrb_leg.ori = Quaternion::rotation_x(wave_slow * 0.08);
next.pigrb_leg.scale = Vec3::one() / 11.0;
next
}
}

View File

@ -0,0 +1,58 @@
// Standard
use std::f32::consts::PI;
// Library
use vek::*;
// Local
use super::{super::Animation, QuadrupedSkeleton, SCALE};
pub struct JumpAnimation;
impl Animation for JumpAnimation {
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();
let wave_stop = (anim_time as f32 * 4.5).min(PI / 2.0).sin();
next.pighead.offset = Vec3::new(0.0, 0.0, -1.5) / 11.0;
next.pighead.ori = Quaternion::rotation_x(wave_stop * 0.4);
next.pighead.scale = Vec3::one() / 10.5;
next.pigchest.offset = Vec3::new(0.0, -9.0, 1.5) / 11.0;
next.pigchest.ori = Quaternion::rotation_x(0.0);
next.pigchest.scale = Vec3::one() / 11.0;
next.piglf_leg.offset = Vec3::new(-4.5, 3.0, 1.5) / 11.0;
next.piglf_leg.ori = Quaternion::rotation_x(wave_stop * 0.6 - wave_slow * 0.3);
next.piglf_leg.scale = Vec3::one() / 11.0;
next.pigrf_leg.offset = Vec3::new(2.5, 3.0, 1.5) / 11.0;
next.pigrf_leg.ori = Quaternion::rotation_x(wave_stop * 0.6 - wave_slow * 0.3);
next.pigrf_leg.scale = Vec3::one() / 11.0;
next.piglb_leg.offset = Vec3::new(-4.5, -4.0, 2.0) / 11.0;
next.piglb_leg.ori = Quaternion::rotation_x(wave_stop * -0.6 + wave_slow * 0.3);
next.piglb_leg.scale = Vec3::one() / 11.0;
next.pigrb_leg.offset = Vec3::new(2.5, -4.0, 2.0) / 11.0;
next.pigrb_leg.ori = Quaternion::rotation_x(wave_stop * -0.6 + wave_slow * 0.3);
next.pigrb_leg.scale = Vec3::one() / 11.0;
next
}
}

View File

@ -0,0 +1,71 @@
pub mod idle;
pub mod jump;
pub mod run;
// Reexports
pub use self::idle::IdleAnimation;
pub use self::jump::JumpAnimation;
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 {
pighead: Bone,
pigchest: Bone,
piglf_leg: Bone,
pigrf_leg: Bone,
piglb_leg: Bone,
pigrb_leg: Bone,
}
impl QuadrupedSkeleton {
pub fn new() -> Self {
Self {
pighead: Bone::default(),
pigchest: Bone::default(),
piglf_leg: Bone::default(),
pigrf_leg: Bone::default(),
piglb_leg: Bone::default(),
pigrb_leg: Bone::default(),
}
}
}
impl Skeleton for QuadrupedSkeleton {
fn compute_matrices(&self) -> [FigureBoneData; 16] {
[
FigureBoneData::new(self.pighead.compute_base_matrix()),
FigureBoneData::new(self.pigchest.compute_base_matrix()),
FigureBoneData::new(self.piglf_leg.compute_base_matrix()),
FigureBoneData::new(self.pigrf_leg.compute_base_matrix()),
FigureBoneData::new(self.piglb_leg.compute_base_matrix()),
FigureBoneData::new(self.pigrb_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.pighead.interpolate(&target.pighead);
self.pigchest.interpolate(&target.pigchest);
self.piglf_leg.interpolate(&target.piglf_leg);
self.pigrf_leg.interpolate(&target.pigrf_leg);
self.piglb_leg.interpolate(&target.piglb_leg);
self.pigrb_leg.interpolate(&target.pigrb_leg);
}
}

View File

@ -0,0 +1,64 @@
// 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 wavequick = (anim_time as f32 * 20.0).sin();
let wavequickcos = (anim_time as f32 * 20.0).cos();
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.pighead.offset = Vec3::new(0.0, 0.0, -1.5 + wave * 1.5) / 11.0;
next.pighead.ori =
Quaternion::rotation_x(0.2 + wave * 0.05) * Quaternion::rotation_y(wavecos * 0.03);
next.pighead.scale = Vec3::one() / 10.5;
next.pigchest.offset = Vec3::new(0.0, -9.0, 1.5 + wavecos * 1.2) / 11.0;
next.pigchest.ori = Quaternion::rotation_x(wave * 0.1);
next.pigchest.scale = Vec3::one() / 11.0;
next.piglf_leg.offset =
Vec3::new(-4.5, 2.0 + wavequick * 0.8, 2.5 + wavequickcos * 1.5) / 11.0;
next.piglf_leg.ori = Quaternion::rotation_x(wavequick * 0.3);
next.piglf_leg.scale = Vec3::one() / 11.0;
next.pigrf_leg.offset =
Vec3::new(2.5, 2.0 - wavequickcos * 0.8, 2.5 + wavequick * 1.5) / 11.0;
next.pigrf_leg.ori = Quaternion::rotation_x(wavequickcos * -0.3);
next.pigrf_leg.scale = Vec3::one() / 11.0;
next.piglb_leg.offset =
Vec3::new(-4.5, -3.0 - wavequickcos * 0.8, 2.5 + wavequick * 1.5) / 11.0;
next.piglb_leg.ori = Quaternion::rotation_x(wavequickcos * -0.3);
next.piglb_leg.scale = Vec3::one() / 11.0;
next.pigrb_leg.offset =
Vec3::new(2.5, -3.0 + wavequick * 0.8, 2.5 + wavequickcos * 1.5) / 11.0;
next.pigrb_leg.ori = Quaternion::rotation_x(wavequick * 0.3);
next.pigrb_leg.scale = Vec3::one() / 11.0;
next
}
}

View File

@ -6,6 +6,7 @@ pub struct KeyState {
pub up: bool,
pub down: bool,
pub jump: bool,
pub glide: bool,
}
impl KeyState {
@ -16,6 +17,7 @@ impl KeyState {
up: false,
down: false,
jump: false,
glide: false,
}
}

View File

@ -7,7 +7,7 @@ use crate::{
Direction, GlobalState, PlayState, PlayStateResult,
};
use client::{self, Client};
use common::{clock::Clock, msg::ClientMsg};
use common::{clock::Clock, comp, msg::ClientMsg};
use scene::Scene;
use std::{cell::RefCell, rc::Rc, time::Duration};
use ui::CharSelectionUi;
@ -78,7 +78,7 @@ impl PlayState for CharSelectionState {
.postbox
.send_message(ClientMsg::Character {
name: self.char_selection_ui.character_name.clone(),
body: self.char_selection_ui.character_body,
body: comp::Body::Humanoid(self.char_selection_ui.character_body), //body: comp::Body::Quadruped(comp::QuadrupedBody::random()),
});
return PlayStateResult::Switch(Box::new(SessionState::new(
&mut global_state.window,

View File

@ -13,7 +13,7 @@ use crate::{
},
};
use client::Client;
use common::{comp::HumanoidBody, figure::Segment};
use common::{comp, figure::Segment};
use vek::*;
struct Skybox {
@ -99,8 +99,12 @@ impl Scene {
);
self.figure_state.skeleton_mut().interpolate(&tgt_skeleton);
self.figure_state
.update(renderer, Vec3::zero(), -Vec3::unit_y());
self.figure_state.update(
renderer,
Vec3::zero(),
-Vec3::unit_y(),
Rgba::broadcast(1.0),
);
}
pub fn render(&mut self, renderer: &mut Renderer, client: &Client) {
@ -108,7 +112,7 @@ impl Scene {
let model = self.figure_model_cache.get_or_create_model(
renderer,
HumanoidBody::random(),
comp::Body::Humanoid(comp::HumanoidBody::random()),
client.get_tick(),
);
renderer.render_figure(

View File

@ -27,6 +27,7 @@ gfx_defines! {
constant Locals {
model_mat: [[f32; 4]; 4] = "model_mat",
model_col: [f32; 4] = "model_col",
}
constant BoneData {
@ -62,13 +63,17 @@ impl Vertex {
}
impl Locals {
pub fn new(model_mat: Mat4<f32>) -> Self {
pub fn new(model_mat: Mat4<f32>, col: Rgba<f32>) -> Self {
Self {
model_mat: arr_to_mat(model_mat.into_col_array()),
model_col: col.into_array(),
}
}
pub fn default() -> Self {
Self::new(Mat4::identity())
}
impl Default for Locals {
fn default() -> Self {
Self::new(Mat4::identity(), Rgba::broadcast(1.0))
}
}

View File

@ -1,6 +1,7 @@
use crate::{
anim::{
character::{CharacterSkeleton, IdleAnimation, JumpAnimation, RunAnimation},
character::{self, CharacterSkeleton},
quadruped::{self, QuadrupedSkeleton},
Animation, Skeleton,
},
mesh::Meshable,
@ -14,8 +15,11 @@ use common::{
assets,
comp::{
self,
actor::{Belt, Chest, Foot, Hand, Head, Pants, Shoulder, Weapon},
Body, HumanoidBody,
actor::{
Belt, Chest, Draw, Foot, Hand, Head, Pants, Pigchest, Pighead, Pigleg_l, Pigleg_r,
Shoulder, Weapon,
},
Body, HumanoidBody, QuadrupedBody,
},
figure::Segment,
msg,
@ -26,7 +30,7 @@ use std::{collections::HashMap, f32};
use vek::*;
pub struct FigureModelCache {
models: HashMap<HumanoidBody, (Model<FigurePipeline>, u64)>,
models: HashMap<Body, (Model<FigurePipeline>, u64)>,
}
impl FigureModelCache {
@ -39,7 +43,7 @@ impl FigureModelCache {
pub fn get_or_create_model(
&mut self,
renderer: &mut Renderer,
body: HumanoidBody,
body: Body,
tick: u64,
) -> &Model<FigurePipeline> {
match self.models.get_mut(&body) {
@ -51,24 +55,44 @@ impl FigureModelCache {
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 bone_meshes = match body {
Body::Humanoid(body) => [
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)),
Some(Self::load_draw(body.draw)),
None,
None,
None,
None,
],
Body::Quadruped(body) => [
Some(Self::load_pighead(body.pighead)),
Some(Self::load_pigchest(body.pigchest)),
Some(Self::load_piglf_leg(body.pigleg_l)),
Some(Self::load_pigrf_leg(body.pigleg_r)),
Some(Self::load_piglb_leg(body.pigleg_l)),
Some(Self::load_pigrb_leg(body.pigleg_r)),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
],
};
let mut mesh = Mesh::new();
bone_meshes
@ -205,29 +229,82 @@ impl FigureModelCache {
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)
//
//
// )
// }
fn load_draw(draw: Draw) -> Mesh<FigurePipeline> {
Self::load_mesh(
match draw {
Draw::Default => "glider.vox",
},
Vec3::new(-26.0, -26.0, -5.0),
)
}
fn load_pighead(pighead: Pighead) -> Mesh<FigurePipeline> {
Self::load_mesh(
match pighead {
Pighead::Default => "pighead.vox",
},
Vec3::new(-6.0, 4.5, 3.0),
)
}
fn load_pigchest(pigchest: Pigchest) -> Mesh<FigurePipeline> {
Self::load_mesh(
match pigchest {
Pigchest::Default => "pigchest.vox",
},
Vec3::new(-5.0, 4.5, 0.0),
)
}
fn load_piglf_leg(pigleg_l: Pigleg_l) -> Mesh<FigurePipeline> {
Self::load_mesh(
match pigleg_l {
Pigleg_l::Default => "pigleg_l.vox",
},
Vec3::new(0.0, -1.0, -1.5),
)
}
fn load_pigrf_leg(pigleg_r: Pigleg_r) -> Mesh<FigurePipeline> {
Self::load_mesh(
match pigleg_r {
Pigleg_r::Default => "pigleg_r.vox",
},
Vec3::new(0.0, -1.0, -1.5),
)
}
fn load_piglb_leg(pigleg_l: Pigleg_l) -> Mesh<FigurePipeline> {
Self::load_mesh(
match pigleg_l {
Pigleg_l::Default => "pigleg_l.vox",
},
Vec3::new(0.0, -1.0, -1.5),
)
}
fn load_pigrb_leg(pigleg_r: Pigleg_r) -> Mesh<FigurePipeline> {
Self::load_mesh(
match pigleg_r {
Pigleg_r::Default => "pigleg_r.vox",
},
Vec3::new(0.0, -1.0, -1.5),
)
}
}
pub struct FigureMgr {
model_cache: FigureModelCache,
states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
character_states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
quadruped_states: HashMap<EcsEntity, FigureState<QuadrupedSkeleton>>,
}
impl FigureMgr {
pub fn new() -> Self {
Self {
model_cache: FigureModelCache::new(),
states: HashMap::new(),
character_states: HashMap::new(),
quadruped_states: HashMap::new(),
}
}
@ -238,51 +315,98 @@ impl FigureMgr {
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 (
for (entity, pos, vel, dir, actor, animation_history, stats) 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>(),
ecs.read_storage::<comp::Stats>().maybe(),
)
.join()
{
match actor {
comp::Actor::Character { body, .. } => match body {
Body::Humanoid(body) => {
let state = self.states.entry(entity).or_insert_with(|| {
let state = self.character_states.entry(entity).or_insert_with(|| {
FigureState::new(renderer, CharacterSkeleton::new())
});
let target_skeleton = match animation_history.current {
comp::Animation::Idle => IdleAnimation::update_skeleton(
comp::Animation::Idle => character::IdleAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_history.time,
),
comp::Animation::Run => RunAnimation::update_skeleton(
comp::Animation::Run => character::RunAnimation::update_skeleton(
state.skeleton_mut(),
(vel.0.magnitude(), time),
animation_history.time,
),
comp::Animation::Jump => JumpAnimation::update_skeleton(
comp::Animation::Jump => character::JumpAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_history.time,
),
comp::Animation::Gliding => {
character::GlidingAnimation::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
state.update(renderer, pos.0, dir.0, Rgba::white());
}
Body::Quadruped(body) => {
let state = self.quadruped_states.entry(entity).or_insert_with(|| {
FigureState::new(renderer, QuadrupedSkeleton::new())
});
let target_skeleton = match animation_history.current {
comp::Animation::Run => quadruped::RunAnimation::update_skeleton(
state.skeleton_mut(),
(vel.0.magnitude(), time),
animation_history.time,
),
comp::Animation::Idle => quadruped::IdleAnimation::update_skeleton(
state.skeleton_mut(),
time,
animation_history.time,
),
comp::Animation::Jump => quadruped::JumpAnimation::update_skeleton(
state.skeleton_mut(),
(vel.0.magnitude(), time),
animation_history.time,
),
// TODO!
_ => state.skeleton_mut().clone(),
};
state.skeleton.interpolate(&target_skeleton);
// Change in health as color!
let col = stats
.and_then(|stats| stats.hp.last_change)
.map(|(change_by, change_time)| Rgba::new(1.0, 0.7, 0.7, 1.0))
.unwrap_or(Rgba::broadcast(1.0));
state.update(renderer, pos.0, dir.0, col);
}
},
// TODO: Non-character actors
}
}
self.states
// Clear states that have dead entities
self.character_states
.retain(|entity, _| ecs.entities().is_alive(*entity));
self.quadruped_states
.retain(|entity, _| ecs.entities().is_alive(*entity));
}
@ -297,20 +421,22 @@ impl FigureMgr {
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
comp::Actor::Character { body, .. } => {
if let Some((locals, bone_consts)) = match body {
Body::Humanoid(_) => self
.character_states
.get(&entity)
.map(|state| (state.locals(), state.bone_consts())),
Body::Quadruped(_) => self
.quadruped_states
.get(&entity)
.map(|state| (state.locals(), state.bone_consts())),
} {
let model = self.model_cache.get_or_create_model(renderer, *body, tick);
renderer.render_figure(model, globals, locals, bone_consts);
}
}
}
}
}
@ -333,12 +459,18 @@ impl<S: Skeleton> FigureState<S> {
}
}
pub fn update(&mut self, renderer: &mut Renderer, pos: Vec3<f32>, dir: Vec3<f32>) {
pub fn update(
&mut self,
renderer: &mut Renderer,
pos: Vec3<f32>,
dir: Vec3<f32>,
col: Rgba<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);
let locals = FigureLocals::new(mat, col);
renderer.update_consts(&mut self.locals, &[locals]).unwrap();
renderer

View 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
}
}

View 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
}
}

View File

@ -157,8 +157,7 @@ impl Terrain {
// Queue the worker thread
client.thread_pool().execute(move || {
send.send(mesh_worker(pos, current_tick, volume, aabb))
.expect("Failed to send chunk mesh to main thread");
let _ = send.send(mesh_worker(pos, current_tick, volume, aabb));
});
todo.active_worker = true;
}

View File

@ -66,6 +66,7 @@ impl SessionState {
Input {
move_dir,
jumping: self.key_state.jump,
gliding: self.key_state.glide,
events: input_events,
},
dt,
@ -145,12 +146,14 @@ impl PlayState for SessionState {
self.input_events.push(InputEvent::Jump);
self.key_state.jump = true;
}
Event::KeyDown(Key::Glide) => self.key_state.glide = true,
// Movement Key Released
Event::KeyUp(Key::MoveForward) => self.key_state.up = false,
Event::KeyUp(Key::MoveBack) => self.key_state.down = false,
Event::KeyUp(Key::MoveLeft) => self.key_state.left = false,
Event::KeyUp(Key::MoveRight) => self.key_state.right = false,
Event::KeyUp(Key::Jump) => self.key_state.jump = false,
Event::KeyUp(Key::Glide) => self.key_state.glide = false,
// Pass all other events to the scene
event => {
self.scene.handle_input_event(event);

View File

@ -25,6 +25,7 @@ pub struct ControlSettings {
pub move_back: VirtualKeyCode,
pub move_right: VirtualKeyCode,
pub jump: VirtualKeyCode,
pub glide: VirtualKeyCode,
pub map: VirtualKeyCode,
pub bag: VirtualKeyCode,
pub quest_log: VirtualKeyCode,
@ -60,6 +61,7 @@ impl Default for Settings {
move_back: VirtualKeyCode::S,
move_right: VirtualKeyCode::D,
jump: VirtualKeyCode::Space,
glide: VirtualKeyCode::LShift,
map: VirtualKeyCode::M,
bag: VirtualKeyCode::B,
quest_log: VirtualKeyCode::L,

View File

@ -46,6 +46,7 @@ impl Window {
key_map.insert(settings.controls.move_back, Key::MoveBack);
key_map.insert(settings.controls.move_right, Key::MoveRight);
key_map.insert(settings.controls.jump, Key::Jump);
key_map.insert(settings.controls.glide, Key::Glide);
key_map.insert(settings.controls.map, Key::Map);
key_map.insert(settings.controls.bag, Key::Bag);
key_map.insert(settings.controls.quest_log, Key::QuestLog);
@ -182,6 +183,7 @@ pub enum Key {
MoveLeft,
MoveRight,
Jump,
Glide,
Enter,
Escape,
Map,

View File

@ -42,7 +42,7 @@ impl World {
let chaos_freq = 1.0 / 100.0;
let freq = 1.0 / 128.0;
let ampl = 32.0;
let ampl = 75.0;
let small_freq = 1.0 / 32.0;
let small_ampl = 6.0;
let offs = 32.0;
@ -55,7 +55,7 @@ impl World {
let height = perlin_nz.get(Vec2::from(wposf * freq).into_array()) * ampl * chaos
+ perlin_nz.get((wposf * small_freq).into_array())
* small_ampl
* 2.0
* 3.0
* chaos.powf(2.0)
+ offs;
let temp = (temp_nz.get(Vec2::from(wposf * (1.0 / 64.0)).into_array()) + 1.0) * 0.5;