veloren/voxygen/src/scene/figure/mod.rs
2019-09-26 00:15:07 +01:00

569 lines
22 KiB
Rust

mod cache;
mod load;
pub use cache::FigureModelCache;
pub use load::load_mesh; // TODO: Don't make this public.
use crate::{
anim::{
self, character::CharacterSkeleton, object::ObjectSkeleton, quadruped::QuadrupedSkeleton,
quadrupedmedium::QuadrupedMediumSkeleton, Animation, Skeleton,
},
render::{Consts, FigureBoneData, FigureLocals, Globals, Light, Renderer, Shadow},
scene::camera::{Camera, CameraMode},
};
use client::Client;
use common::{
comp::{
ActionState::*, Body, CharacterState, Last, MovementState::*, Ori, Pos, Scale, Stats, Vel,
},
terrain::TerrainChunk,
vol::RectRasterableVol,
};
use hashbrown::HashMap;
use log::debug;
use specs::{Entity as EcsEntity, Join};
use vek::*;
const DAMAGE_FADE_COEFFICIENT: f64 = 5.0;
pub struct FigureMgr {
model_cache: FigureModelCache,
character_states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
quadruped_states: HashMap<EcsEntity, FigureState<QuadrupedSkeleton>>,
quadruped_medium_states: HashMap<EcsEntity, FigureState<QuadrupedMediumSkeleton>>,
object_states: HashMap<EcsEntity, FigureState<ObjectSkeleton>>,
}
impl FigureMgr {
pub fn new() -> Self {
Self {
model_cache: FigureModelCache::new(),
character_states: HashMap::new(),
quadruped_states: HashMap::new(),
quadruped_medium_states: HashMap::new(),
object_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 tick = client.get_tick();
let ecs = client.state().ecs();
let view_distance = client.view_distance().unwrap_or(1);
let dt = client.state().get_delta_time();
// Get player position.
let player_pos = ecs
.read_storage::<Pos>()
.get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
for (entity, pos, vel, ori, scale, body, character, last_character, stats) in (
&ecs.entities(),
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<Vel>(),
&ecs.read_storage::<Ori>(),
ecs.read_storage::<Scale>().maybe(),
&ecs.read_storage::<Body>(),
ecs.read_storage::<CharacterState>().maybe(),
ecs.read_storage::<Last<CharacterState>>().maybe(),
ecs.read_storage::<Stats>().maybe(),
)
.join()
{
// Don't process figures outside the vd
let vd_frac = Vec2::from(pos.0 - player_pos)
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
d.abs() as f32 / sz as f32
})
.magnitude()
/ view_distance as f32;
// Keep from re-adding/removing entities on the border of the vd
if vd_frac > 1.2 {
match body {
Body::Humanoid(_) => {
self.character_states.remove(&entity);
}
Body::Quadruped(_) => {
self.quadruped_states.remove(&entity);
}
Body::QuadrupedMedium(_) => {
self.quadruped_medium_states.remove(&entity);
}
Body::Object(_) => {
self.object_states.remove(&entity);
}
}
continue;
} else if vd_frac > 1.0 {
continue;
}
// Change in health as color!
let col = stats
.and_then(|stats| stats.health.last_change)
.map(|(_, time, _)| {
Rgba::broadcast(1.0)
+ Rgba::new(2.0, 2.0, 2.0, 0.0)
.map(|c| (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time)) as f32)
})
.unwrap_or(Rgba::broadcast(1.0));
let scale = scale.map(|s| s.0).unwrap_or(1.0);
let skeleton_attr = &self
.model_cache
.get_or_create_model(
renderer,
*body,
stats.map(|s| &s.equipment),
tick,
CameraMode::default(),
None,
)
.1;
let mut movement_animation_rate = 1.0;
let mut action_animation_rate = 1.0;
match body {
Body::Humanoid(_) => {
let state = self
.character_states
.entry(entity)
.or_insert_with(|| FigureState::new(renderer, CharacterSkeleton::new()));
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => continue,
};
if !character.is_same_movement(&last_character.0) {
state.movement_time = 0.0;
}
if !character.is_same_action(&last_character.0) {
state.action_time = 0.0;
}
let target_base = match &character.movement {
Stand => anim::character::StandAnimation::update_skeleton(
&CharacterSkeleton::new(),
time,
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Run => anim::character::RunAnimation::update_skeleton(
&CharacterSkeleton::new(),
(vel.0, ori.0, state.last_ori, time),
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Jump => anim::character::JumpAnimation::update_skeleton(
&CharacterSkeleton::new(),
time,
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Roll { .. } => anim::character::RollAnimation::update_skeleton(
&CharacterSkeleton::new(),
time,
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Glide => anim::character::GlidingAnimation::update_skeleton(
&CharacterSkeleton::new(),
(vel.0, ori.0, state.last_ori, time),
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Swim => anim::character::SwimAnimation::update_skeleton(
&CharacterSkeleton::new(),
(vel.0.magnitude(), ori.0.magnitude(), time),
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Climb => anim::character::ClimbAnimation::update_skeleton(
&CharacterSkeleton::new(),
(vel.0, ori.0, time),
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Sit => anim::character::SitAnimation::update_skeleton(
&CharacterSkeleton::new(),
time,
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
};
let target_bones = match (&character.movement, &character.action) {
(Stand, Wield { .. }) => anim::character::CidleAnimation::update_skeleton(
&target_base,
time,
state.action_time,
&mut action_animation_rate,
skeleton_attr,
),
(Stand, Block { .. }) => {
anim::character::BlockIdleAnimation::update_skeleton(
&target_base,
time,
state.action_time,
&mut action_animation_rate,
skeleton_attr,
)
}
(_, Attack { .. }) => anim::character::AttackAnimation::update_skeleton(
&target_base,
time,
state.action_time,
&mut action_animation_rate,
skeleton_attr,
),
(_, Wield { .. }) => anim::character::WieldAnimation::update_skeleton(
&target_base,
(vel.0.magnitude(), time),
state.action_time,
&mut action_animation_rate,
skeleton_attr,
),
(_, Block { .. }) => anim::character::BlockAnimation::update_skeleton(
&target_base,
time,
state.action_time,
&mut action_animation_rate,
skeleton_attr,
),
_ => target_base,
};
state.skeleton.interpolate(&target_bones, dt);
state.update(
renderer,
pos.0,
ori.0,
scale,
col,
dt,
movement_animation_rate,
action_animation_rate,
);
}
Body::Quadruped(_) => {
let state = self
.quadruped_states
.entry(entity)
.or_insert_with(|| FigureState::new(renderer, QuadrupedSkeleton::new()));
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => continue,
};
if !character.is_same_movement(&last_character.0) {
state.movement_time = 0.0;
}
let target_base = match character.movement {
Stand => anim::quadruped::IdleAnimation::update_skeleton(
&QuadrupedSkeleton::new(),
time,
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Run => anim::quadruped::RunAnimation::update_skeleton(
&QuadrupedSkeleton::new(),
(vel.0.magnitude(), time),
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Jump => anim::quadruped::JumpAnimation::update_skeleton(
&QuadrupedSkeleton::new(),
(vel.0.magnitude(), time),
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
// TODO!
_ => state.skeleton_mut().clone(),
};
state.skeleton.interpolate(&target_base, dt);
state.update(
renderer,
pos.0,
ori.0,
scale,
col,
dt,
movement_animation_rate,
action_animation_rate,
);
}
Body::QuadrupedMedium(_) => {
let state = self
.quadruped_medium_states
.entry(entity)
.or_insert_with(|| {
FigureState::new(renderer, QuadrupedMediumSkeleton::new())
});
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => continue,
};
if !character.is_same_movement(&last_character.0) {
state.movement_time = 0.0;
}
let target_base = match character.movement {
Stand => anim::quadrupedmedium::IdleAnimation::update_skeleton(
&QuadrupedMediumSkeleton::new(),
time,
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Run => anim::quadrupedmedium::RunAnimation::update_skeleton(
&QuadrupedMediumSkeleton::new(),
(vel.0.magnitude(), time),
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
Jump => anim::quadrupedmedium::JumpAnimation::update_skeleton(
&QuadrupedMediumSkeleton::new(),
(vel.0.magnitude(), time),
state.movement_time,
&mut movement_animation_rate,
skeleton_attr,
),
// TODO!
_ => state.skeleton_mut().clone(),
};
state.skeleton.interpolate(&target_base, dt);
state.update(
renderer,
pos.0,
ori.0,
scale,
col,
dt,
movement_animation_rate,
action_animation_rate,
);
}
Body::Object(_) => {
let state = self
.object_states
.entry(entity)
.or_insert_with(|| FigureState::new(renderer, ObjectSkeleton::new()));
state.skeleton = state.skeleton_mut().clone();
state.update(
renderer,
pos.0,
ori.0,
scale,
col,
dt,
movement_animation_rate,
action_animation_rate,
);
}
}
}
// 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));
self.quadruped_medium_states
.retain(|entity, _| ecs.entities().is_alive(*entity));
self.object_states
.retain(|entity, _| ecs.entities().is_alive(*entity));
}
pub fn render(
&mut self,
renderer: &mut Renderer,
client: &mut Client,
globals: &Consts<Globals>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
camera: &Camera,
) {
let tick = client.get_tick();
let ecs = client.state().ecs();
let frustum = camera.frustum(client);
let character_state_storage = client
.state()
.read_storage::<common::comp::CharacterState>();
let character_state = character_state_storage.get(client.entity());
for (entity, _, _, _, body, stats, _) in (
&ecs.entities(),
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<Vel>(),
&ecs.read_storage::<Ori>(),
&ecs.read_storage::<Body>(),
ecs.read_storage::<Stats>().maybe(),
ecs.read_storage::<Scale>().maybe(),
)
.join()
// Don't render figures outside of frustum (camera viewport, max draw distance is farplane)
.filter(|(_, pos, _, _, _, _, scale)| {
frustum.sphere_intersecting(
&pos.0.x,
&pos.0.y,
&pos.0.z,
&(scale.unwrap_or(&Scale(1.0)).0 * 2.0),
)
})
// Don't render dead entities
.filter(|(_, _, _, _, _, stats, _)| stats.map_or(true, |s| !s.is_dead))
{
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())),
Body::QuadrupedMedium(_) => self
.quadruped_medium_states
.get(&entity)
.map(|state| (state.locals(), state.bone_consts())),
Body::Object(_) => self
.object_states
.get(&entity)
.map(|state| (state.locals(), state.bone_consts())),
} {
let is_player = entity == client.entity();
let player_camera_mode = if is_player {
camera.get_mode()
} else {
CameraMode::default()
};
let model = &self
.model_cache
.get_or_create_model(
renderer,
*body,
stats.map(|s| &s.equipment),
tick,
player_camera_mode,
if is_player { character_state } else { None },
)
.0;
renderer.render_figure(model, globals, locals, bone_consts, lights, shadows);
} else {
debug!("Body has no saved figure");
}
}
}
}
pub struct FigureState<S: Skeleton> {
bone_consts: Consts<FigureBoneData>,
locals: Consts<FigureLocals>,
movement_time: f64,
action_time: f64,
skeleton: S,
pos: Vec3<f32>,
ori: Vec3<f32>,
last_ori: Vec3<f32>,
}
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(),
movement_time: 0.0,
action_time: 0.0,
skeleton,
pos: Vec3::zero(),
ori: Vec3::zero(),
last_ori: Vec3::zero(),
}
}
pub fn update(
&mut self,
renderer: &mut Renderer,
pos: Vec3<f32>,
ori: Vec3<f32>,
scale: f32,
col: Rgba<f32>,
dt: f32,
movement_rate: f32,
action_rate: f32,
) {
self.last_ori = Lerp::lerp(self.last_ori, ori, 15.0 * dt);
// Update interpolation values
if self.pos.distance_squared(pos) < 64.0 * 64.0 {
self.pos = Lerp::lerp(self.pos, pos, 15.0 * dt);
self.ori = Slerp::slerp(self.ori, ori, 7.5 * dt);
} else {
self.pos = pos;
self.ori = ori;
}
self.movement_time += (dt * movement_rate) as f64;
self.action_time += (dt * action_rate) as f64;
let mat = Mat4::<f32>::identity()
* Mat4::translation_3d(self.pos)
* Mat4::rotation_z(-ori.x.atan2(ori.y))
* Mat4::scaling_3d(Vec3::from(0.8 * scale));
let locals = FigureLocals::new(mat, col);
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
}
}