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 ef8daef828
43 changed files with 1446 additions and 111 deletions

BIN
assets/voxygen/voxel/glider.vox (Stored with Git LFS) Normal file

Binary file not shown.

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

Binary file not shown.

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

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;