Character improvements including animation blending, interpolation & delta encoding
This commit is contained in:
parent
374fdb1077
commit
bdd2e2eefc
@ -25,6 +25,8 @@ set(COMMON_SOURCES
|
|||||||
"src/collision/motionstate.hpp"
|
"src/collision/motionstate.hpp"
|
||||||
"src/collision/trianglemesh.hpp"
|
"src/collision/trianglemesh.hpp"
|
||||||
"src/collision/trianglemesh.cpp"
|
"src/collision/trianglemesh.cpp"
|
||||||
|
"src/game/character_anim_state.hpp"
|
||||||
|
"src/game/character_anim_state.cpp"
|
||||||
"src/game/player_input.hpp"
|
"src/game/player_input.hpp"
|
||||||
"src/game/skeletoninstance.hpp"
|
"src/game/skeletoninstance.hpp"
|
||||||
"src/game/skeletoninstance.cpp"
|
"src/game/skeletoninstance.cpp"
|
||||||
|
|||||||
@ -54,14 +54,27 @@ int assets::Skeleton::GetBoneIndex(const std::string& name) const
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assets::AnimIdx assets::Skeleton::GetAnimationIdx(const std::string& name) const
|
||||||
|
{
|
||||||
|
auto it = anim_idxs_.find(name);
|
||||||
|
if (it != anim_idxs_.end())
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return NO_ANIM;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assets::Animation* assets::Skeleton::GetAnimation(AnimIdx idx) const
|
||||||
|
{
|
||||||
|
if (idx >= anims_.size())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return anims_[idx].get();
|
||||||
|
}
|
||||||
|
|
||||||
const assets::Animation* assets::Skeleton::GetAnimation(const std::string& name) const
|
const assets::Animation* assets::Skeleton::GetAnimation(const std::string& name) const
|
||||||
{
|
{
|
||||||
auto it = anims_.find(name);
|
return GetAnimation(GetAnimationIdx(name));
|
||||||
if (it != anims_.end())
|
|
||||||
{
|
|
||||||
return it->second.get();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void assets::Skeleton::AddBone(const std::string& name, const std::string& parent_name, const Transform& transform)
|
void assets::Skeleton::AddBone(const std::string& name, const std::string& parent_name, const Transform& transform)
|
||||||
@ -76,3 +89,9 @@ void assets::Skeleton::AddBone(const std::string& name, const std::string& paren
|
|||||||
|
|
||||||
bone_map_[bone.name] = index;
|
bone_map_[bone.name] = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void assets::Skeleton::AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim)
|
||||||
|
{
|
||||||
|
anim_idxs_[name] = anims_.size();
|
||||||
|
anims_.push_back(anim);
|
||||||
|
}
|
||||||
|
|||||||
@ -19,6 +19,9 @@ struct Bone
|
|||||||
glm::mat4 inv_bind_matrix;
|
glm::mat4 inv_bind_matrix;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using AnimIdx = uint8_t;
|
||||||
|
constexpr AnimIdx NO_ANIM = 255;
|
||||||
|
|
||||||
class Skeleton
|
class Skeleton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -30,17 +33,20 @@ public:
|
|||||||
size_t GetNumBones() const { return bones_.size(); }
|
size_t GetNumBones() const { return bones_.size(); }
|
||||||
const Bone& GetBone(size_t idx) const { return bones_[idx]; }
|
const Bone& GetBone(size_t idx) const { return bones_[idx]; }
|
||||||
|
|
||||||
|
AnimIdx GetAnimationIdx(const std::string& name) const;
|
||||||
|
const Animation* GetAnimation(AnimIdx idx) const;
|
||||||
const Animation* GetAnimation(const std::string& name) const;
|
const Animation* GetAnimation(const std::string& name) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AddBone(const std::string& name, const std::string& parent_name, const Transform& transform);
|
void AddBone(const std::string& name, const std::string& parent_name, const Transform& transform);
|
||||||
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim) { anims_[name] = anim; }
|
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Bone> bones_;
|
std::vector<Bone> bones_;
|
||||||
std::map<std::string, int> bone_map_;
|
std::map<std::string, int> bone_map_;
|
||||||
|
|
||||||
std::map<std::string, std::shared_ptr<const Animation>> anims_;
|
std::vector<std::shared_ptr<const Animation>> anims_;
|
||||||
|
std::map<std::string, AnimIdx> anim_idxs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace assets
|
} // namespace assets
|
||||||
@ -8,18 +8,21 @@ collision::DynamicsWorld::DynamicsWorld(std::shared_ptr<const assets::Map> map)
|
|||||||
{
|
{
|
||||||
bt_world_.setGravity(btVector3(0, 0, -9.81f));
|
bt_world_.setGravity(btVector3(0, 0, -9.81f));
|
||||||
|
|
||||||
|
bt_broadphase_.getOverlappingPairCache()->setInternalGhostPairCallback(&bt_ghost_pair_cb_);
|
||||||
|
|
||||||
AddMapCollision();
|
AddMapCollision();
|
||||||
|
|
||||||
btTransform t;
|
// btTransform t;
|
||||||
t.setIdentity();
|
// t.setIdentity();
|
||||||
t.setOrigin(btVector3(0,0,-12));
|
// t.setOrigin(btVector3(0,0,-12));
|
||||||
|
|
||||||
|
|
||||||
// TODO: remove
|
// TODO: remove
|
||||||
static btDefaultMotionState motion(t);
|
// static btDefaultMotionState motion(t);
|
||||||
static btBoxShape box(btVector3(100, 100, 2));
|
// static btBoxShape box(btVector3(100, 100, 2));
|
||||||
btRigidBody::btRigidBodyConstructionInfo rbInfo(0.0f, &motion, &box, btVector3(0,0,0));
|
// btRigidBody::btRigidBodyConstructionInfo rbInfo(0.0f, &motion, &box, btVector3(0,0,0));
|
||||||
static btRigidBody body(rbInfo);
|
// static btRigidBody body(rbInfo);
|
||||||
bt_world_.addRigidBody(&body);
|
// bt_world_.addRigidBody(&body);
|
||||||
}
|
}
|
||||||
|
|
||||||
void collision::DynamicsWorld::AddMapCollision()
|
void collision::DynamicsWorld::AddMapCollision()
|
||||||
@ -36,11 +39,13 @@ void collision::DynamicsWorld::AddMapCollision()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add static objects
|
// add static objects
|
||||||
|
for (const auto& chunks = map_->GetChunks(); const auto& chunk : chunks)
|
||||||
// for (const auto& sobjs = map_->GetStaticObjects(); const auto& sobj : sobjs)
|
{
|
||||||
// {
|
for (const auto& obj : chunk.objs)
|
||||||
// AddModelInstance(*sobj.model, sobj.node.local);
|
{
|
||||||
// }
|
AddModelInstance(*obj.model, obj.node.local);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void collision::DynamicsWorld::AddModelInstance(const assets::Model& model, const Transform& trans)
|
void collision::DynamicsWorld::AddModelInstance(const assets::Model& model, const Transform& trans)
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include <btBulletDynamicsCommon.h>
|
#include <btBulletDynamicsCommon.h>
|
||||||
|
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
|
||||||
|
|
||||||
#include "assets/map.hpp"
|
#include "assets/map.hpp"
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ private:
|
|||||||
|
|
||||||
btDefaultCollisionConfiguration bt_cfg_;
|
btDefaultCollisionConfiguration bt_cfg_;
|
||||||
btCollisionDispatcher bt_dispatcher_;
|
btCollisionDispatcher bt_dispatcher_;
|
||||||
|
btGhostPairCallback bt_ghost_pair_cb_;
|
||||||
btDbvtBroadphase bt_broadphase_;
|
btDbvtBroadphase bt_broadphase_;
|
||||||
btSequentialImpulseConstraintSolver bt_solver_;
|
btSequentialImpulseConstraintSolver bt_solver_;
|
||||||
btDiscreteDynamicsWorld bt_world_;
|
btDiscreteDynamicsWorld bt_world_;
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
#include "character.hpp"
|
#include "character.hpp"
|
||||||
#include "world.hpp"
|
#include "world.hpp"
|
||||||
#include "net/utils.hpp"
|
#include "net/utils.hpp"
|
||||||
|
#include "assets/cache.hpp"
|
||||||
|
#include "utils/math.hpp"
|
||||||
|
|
||||||
game::Character::Character(World& world, const CharacterInfo& info)
|
game::Character::Character(World& world, const CharacterInfo& info)
|
||||||
: Super(world, net::ET_CHARACTER), shape_(info.shape), bt_shape_(shape_.radius, shape_.height),
|
: Super(world, net::ET_CHARACTER), shape_(info.shape), bt_shape_(shape_.radius, shape_.height),
|
||||||
@ -14,12 +16,14 @@ game::Character::Character(World& world, const CharacterInfo& info)
|
|||||||
|
|
||||||
|
|
||||||
btDynamicsWorld& bt_world = world_.GetBtWorld();
|
btDynamicsWorld& bt_world = world_.GetBtWorld();
|
||||||
static btGhostPairCallback ghostpaircb;
|
bt_world.addCollisionObject(&bt_ghost_, btBroadphaseProxy::CharacterFilter,
|
||||||
bt_world.getBroadphase()->getOverlappingPairCache()->setInternalGhostPairCallback(&ghostpaircb);
|
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
||||||
// bt_world.addCollisionObject(&bt_ghost_, btBroadphaseProxy::CharacterFilter,
|
// bt_world.addCollisionObject(&bt_ghost_);
|
||||||
// btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
|
||||||
bt_world.addCollisionObject(&bt_ghost_);
|
|
||||||
bt_world.addAction(&bt_character_);
|
bt_world.addAction(&bt_character_);
|
||||||
|
|
||||||
|
sk_ = SkeletonInstance(assets::CacheManager::GetSkeleton("data/human.sk"), &root_);
|
||||||
|
animstate_.idle_anim_idx = GetAnim("idle");
|
||||||
|
animstate_.walk_anim_idx = GetAnim("walk");
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool Turn(float& angle, float target, float step)
|
static bool Turn(float& angle, float target, float step)
|
||||||
@ -51,12 +55,21 @@ void game::Character::Update()
|
|||||||
root_.local.position.z -= shape_.height * 0.5f + shape_.radius - 0.05f; // foot pos
|
root_.local.position.z -= shape_.height * 0.5f + shape_.radius - 0.05f; // foot pos
|
||||||
|
|
||||||
UpdateMovement();
|
UpdateMovement();
|
||||||
|
|
||||||
|
sync_current_ = 1 - sync_current_;
|
||||||
|
UpdateSyncState();
|
||||||
SendUpdateMsg();
|
SendUpdateMsg();
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Character::SendInitData(Player& player, net::OutMessage& msg) const
|
void game::Character::SendInitData(Player& player, net::OutMessage& msg) const
|
||||||
{
|
{
|
||||||
Super::SendInitData(player, msg);
|
Super::SendInitData(player, msg);
|
||||||
|
|
||||||
|
// write state against default
|
||||||
|
static const CharacterSyncState default_state;
|
||||||
|
size_t fields_pos = msg.Reserve<CharacterSyncFieldFlags>();
|
||||||
|
auto fields = WriteState(msg, default_state);
|
||||||
|
msg.WriteAt(fields_pos, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Character::SetInput(CharacterInputType type, bool enable)
|
void game::Character::SetInput(CharacterInputType type, bool enable)
|
||||||
@ -110,7 +123,7 @@ game::Character::~Character()
|
|||||||
void game::Character::UpdateMovement()
|
void game::Character::UpdateMovement()
|
||||||
{
|
{
|
||||||
constexpr float dt = 1.0f / 25.0f;
|
constexpr float dt = 1.0f / 25.0f;
|
||||||
|
bool walking = false;
|
||||||
glm::vec2 movedir(0.0f);
|
glm::vec2 movedir(0.0f);
|
||||||
|
|
||||||
if (in_ & (1 << CIN_FORWARD))
|
if (in_ & (1 << CIN_FORWARD))
|
||||||
@ -129,6 +142,7 @@ void game::Character::UpdateMovement()
|
|||||||
|
|
||||||
if (movedir.x != 0.0f || movedir.y != 0.0f)
|
if (movedir.x != 0.0f || movedir.y != 0.0f)
|
||||||
{
|
{
|
||||||
|
walking = true;
|
||||||
float target_yaw = forward_yaw_ + std::atan2(movedir.y, movedir.x);
|
float target_yaw = forward_yaw_ + std::atan2(movedir.y, movedir.x);
|
||||||
Turn(yaw_, target_yaw, 4.0f * dt);
|
Turn(yaw_, target_yaw, 4.0f * dt);
|
||||||
|
|
||||||
@ -142,16 +156,96 @@ void game::Character::UpdateMovement()
|
|||||||
{
|
{
|
||||||
bt_character_.jump(btVector3(0.0f, 0.0f, 10.0f));
|
bt_character_.jump(btVector3(0.0f, 0.0f, 10.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update anim
|
||||||
|
float run_blend_target = walking ? 0.5f : 0.0f;
|
||||||
|
MoveToward(animstate_.loco_blend, run_blend_target, dt * 2.0f);
|
||||||
|
float anim_speed = glm::mix(0.5f, 1.5f, UnMix(0.0f, 0.5f, animstate_.loco_blend));
|
||||||
|
animstate_.loco_phase = glm::mod(animstate_.loco_phase + anim_speed * dt, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Character::UpdateSyncState()
|
||||||
|
{
|
||||||
|
auto& state = sync_[sync_current_];
|
||||||
|
|
||||||
|
// transform
|
||||||
|
net::EncodePosition(root_.local.position, state.pos);
|
||||||
|
state.yaw.Encode(yaw_);
|
||||||
|
|
||||||
|
// idle
|
||||||
|
state.idle_anim = animstate_.idle_anim_idx;
|
||||||
|
|
||||||
|
// loco
|
||||||
|
state.walk_anim = animstate_.walk_anim_idx;
|
||||||
|
state.run_anim = animstate_.run_anim_idx;
|
||||||
|
state.loco_phase.Encode(animstate_.loco_phase);
|
||||||
|
state.loco_blend.Encode(animstate_.loco_blend);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Character::SendUpdateMsg()
|
void game::Character::SendUpdateMsg()
|
||||||
{
|
{
|
||||||
net::PositionQ posq;
|
|
||||||
net::EncodePosition(root_.local.position, posq);
|
|
||||||
|
|
||||||
auto msg = BeginEntMsg(net::EMSG_UPDATE);
|
auto msg = BeginEntMsg(net::EMSG_UPDATE);
|
||||||
net::WritePositionQ(msg, posq);
|
auto fields_pos = msg.Reserve<CharacterSyncFieldFlags>();
|
||||||
msg.Write<net::PositiveAngleQ>(yaw_);
|
auto fields = WriteState(msg, sync_[1 - sync_current_]);
|
||||||
|
|
||||||
|
// TODO: allow this
|
||||||
|
// if (fields == 0)
|
||||||
|
// {
|
||||||
|
// DiscardMsg();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
msg.WriteAt(fields_pos, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
game::CharacterSyncFieldFlags game::Character::WriteState(net::OutMessage& msg, const CharacterSyncState& base) const
|
||||||
|
{
|
||||||
|
const auto& curr = sync_[sync_current_];
|
||||||
|
|
||||||
|
game::CharacterSyncFieldFlags fields = 0;
|
||||||
|
|
||||||
|
// transform
|
||||||
|
if (curr.pos.x.value != base.pos.x.value || curr.pos.y.value != base.pos.y.value ||
|
||||||
|
curr.pos.z.value != base.pos.z.value || curr.yaw.value != base.yaw.value)
|
||||||
|
{
|
||||||
|
fields |= CSF_TRANSFORM;
|
||||||
|
|
||||||
|
net::WriteDelta(msg, curr.pos.x, base.pos.x);
|
||||||
|
net::WriteDelta(msg, curr.pos.y, base.pos.y);
|
||||||
|
net::WriteDelta(msg, curr.pos.z, base.pos.z);
|
||||||
|
|
||||||
|
net::WriteDelta(msg, curr.yaw, base.yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// idle
|
||||||
|
if (curr.idle_anim != base.idle_anim)
|
||||||
|
{
|
||||||
|
fields |= CSF_IDLE_ANIM;
|
||||||
|
|
||||||
|
msg.Write(curr.idle_anim);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loco anims
|
||||||
|
if (curr.walk_anim != base.walk_anim || curr.run_anim != base.run_anim)
|
||||||
|
{
|
||||||
|
fields |= CSF_LOCO_ANIMS;
|
||||||
|
|
||||||
|
msg.Write(curr.walk_anim);
|
||||||
|
msg.Write(curr.run_anim);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loco vals
|
||||||
|
if (curr.loco_blend.value != base.loco_blend.value || curr.loco_phase.value != base.loco_phase.value)
|
||||||
|
{
|
||||||
|
fields |= CSF_LOCO_VALS;
|
||||||
|
|
||||||
|
net::WriteDelta(msg, curr.loco_blend, base.loco_blend);
|
||||||
|
net::WriteDelta(msg, curr.loco_phase, base.loco_phase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Character::Move(glm::vec3& velocity, float t)
|
void game::Character::Move(glm::vec3& velocity, float t)
|
||||||
@ -188,3 +282,8 @@ void game::Character::Move(glm::vec3& velocity, float t)
|
|||||||
// velocity -= glm::dot(velocity, hit_normal) * hit_normal; // Adjust the velocity
|
// velocity -= glm::dot(velocity, hit_normal) * hit_normal; // Adjust the velocity
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assets::AnimIdx game::Character::GetAnim(const std::string& name) const
|
||||||
|
{
|
||||||
|
return sk_.GetSkeleton()->GetAnimationIdx(name);
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
#include "entity.hpp"
|
#include "entity.hpp"
|
||||||
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
|
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
|
||||||
#include "BulletDynamics/Character/btKinematicCharacterController.h"
|
#include "BulletDynamics/Character/btKinematicCharacterController.h"
|
||||||
|
#include "character_anim_state.hpp"
|
||||||
|
#include "character_sync.hpp"
|
||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
@ -52,10 +54,14 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateMovement();
|
void UpdateMovement();
|
||||||
|
void UpdateSyncState();
|
||||||
void SendUpdateMsg();
|
void SendUpdateMsg();
|
||||||
|
CharacterSyncFieldFlags WriteState(net::OutMessage& msg, const CharacterSyncState& base) const;
|
||||||
|
|
||||||
void Move(glm::vec3& velocity, float t);
|
void Move(glm::vec3& velocity, float t);
|
||||||
|
|
||||||
|
assets::AnimIdx GetAnim(const std::string& name) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CapsuleShape shape_;
|
CapsuleShape shape_;
|
||||||
|
|
||||||
@ -74,6 +80,11 @@ private:
|
|||||||
|
|
||||||
float walk_speed_ = 2.0f;
|
float walk_speed_ = 2.0f;
|
||||||
|
|
||||||
|
SkeletonInstance sk_;
|
||||||
|
CharacterAnimState animstate_;
|
||||||
|
|
||||||
|
CharacterSyncState sync_[2];
|
||||||
|
size_t sync_current_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
44
src/game/character_anim_state.cpp
Normal file
44
src/game/character_anim_state.cpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "character_anim_state.hpp"
|
||||||
|
#include "utils/math.hpp"
|
||||||
|
|
||||||
|
void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const
|
||||||
|
{
|
||||||
|
const auto& skeleton = sk.GetSkeleton();
|
||||||
|
const assets::Animation* idle_anim = skeleton->GetAnimation(idle_anim_idx);
|
||||||
|
const assets::Animation* walk_anim = skeleton->GetAnimation(walk_anim_idx);
|
||||||
|
const assets::Animation* run_anim = skeleton->GetAnimation(run_anim_idx);
|
||||||
|
|
||||||
|
if (!idle_anim)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!walk_anim)
|
||||||
|
walk_anim = idle_anim;
|
||||||
|
|
||||||
|
if (!run_anim)
|
||||||
|
run_anim = walk_anim;
|
||||||
|
|
||||||
|
if (loco_blend == 0.0f) // idle
|
||||||
|
{
|
||||||
|
sk.ApplySkelAnim(*idle_anim, loco_phase, 1.0f);
|
||||||
|
}
|
||||||
|
else if (loco_blend < 0.5f) // idle-walk
|
||||||
|
{
|
||||||
|
sk.ApplySkelAnim(*idle_anim, loco_phase, 1.0f);
|
||||||
|
sk.ApplySkelAnim(*walk_anim, loco_phase, UnMix(0.0f, 0.5f, loco_blend));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (loco_blend == 0.5f) // walk
|
||||||
|
{
|
||||||
|
sk.ApplySkelAnim(*walk_anim, loco_phase, 1.0f);
|
||||||
|
}
|
||||||
|
else if (loco_blend < 1.0f) // walk-run
|
||||||
|
{
|
||||||
|
sk.ApplySkelAnim(*walk_anim, loco_phase, 1.0f);
|
||||||
|
sk.ApplySkelAnim(*run_anim, loco_phase, UnMix(0.5f, 1.0f, loco_blend));
|
||||||
|
}
|
||||||
|
else // run
|
||||||
|
{
|
||||||
|
sk.ApplySkelAnim(*run_anim, loco_phase, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
src/game/character_anim_state.hpp
Normal file
21
src/game/character_anim_state.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "skeletoninstance.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
struct CharacterAnimState
|
||||||
|
{
|
||||||
|
assets::AnimIdx idle_anim_idx = assets::NO_ANIM;
|
||||||
|
assets::AnimIdx walk_anim_idx = assets::NO_ANIM;
|
||||||
|
assets::AnimIdx run_anim_idx = assets::NO_ANIM;
|
||||||
|
|
||||||
|
float loco_blend = 0.0f;
|
||||||
|
float loco_phase = 0.0f;
|
||||||
|
|
||||||
|
void ApplyToSkeleton(SkeletonInstance& sk) const;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
} // namespace game
|
||||||
41
src/game/character_sync.hpp
Normal file
41
src/game/character_sync.hpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "net/defs.hpp"
|
||||||
|
#include "assets/skeleton.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
|
||||||
|
struct CharacterSyncState
|
||||||
|
{
|
||||||
|
// transform
|
||||||
|
net::PositionQ pos;
|
||||||
|
net::PositiveAngleQ yaw;
|
||||||
|
|
||||||
|
// idle
|
||||||
|
assets::AnimIdx idle_anim = assets::NO_ANIM;
|
||||||
|
|
||||||
|
// loco anim
|
||||||
|
assets::AnimIdx walk_anim = assets::NO_ANIM;
|
||||||
|
assets::AnimIdx run_anim = assets::NO_ANIM;
|
||||||
|
net::AnimBlendQ loco_blend;
|
||||||
|
net::AnimTimeQ loco_phase;
|
||||||
|
//assets::AnimIdx strafe_left_anim = assets::NO_ANIM;
|
||||||
|
//assets::AnimIdx strafe_right_anim = assets::NO_ANIM;
|
||||||
|
|
||||||
|
// TODO: action
|
||||||
|
};
|
||||||
|
|
||||||
|
using CharacterSyncFieldFlags = uint8_t;
|
||||||
|
|
||||||
|
enum CharacterSyncFieldFlag
|
||||||
|
{
|
||||||
|
CSF_TRANSFORM = 0x01,
|
||||||
|
CSF_IDLE_ANIM = 0x02,
|
||||||
|
CSF_LOCO_ANIMS = 0x04,
|
||||||
|
CSF_LOCO_VALS = 0x08,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
@ -2,9 +2,16 @@
|
|||||||
#include "assets/cache.hpp"
|
#include "assets/cache.hpp"
|
||||||
#include "assets/model.hpp"
|
#include "assets/model.hpp"
|
||||||
#include "net/utils.hpp"
|
#include "net/utils.hpp"
|
||||||
|
#include "worldview.hpp"
|
||||||
|
|
||||||
|
|
||||||
game::view::CharacterView::CharacterView(WorldView& world, net::InMessage& msg) : EntityView(world, msg), ubo_(sk_)
|
game::view::CharacterView::CharacterView(WorldView& world, net::InMessage& msg) : EntityView(world, msg), ubo_(sk_)
|
||||||
{
|
{
|
||||||
|
if (!ReadState(msg))
|
||||||
|
throw EntityInitError();
|
||||||
|
|
||||||
|
states_[0] = states_[1]; // lerp from the read state to avoid jump
|
||||||
|
|
||||||
basemodel_ = assets::CacheManager::GetModel("data/human.mdl");
|
basemodel_ = assets::CacheManager::GetModel("data/human.mdl");
|
||||||
sk_ = SkeletonInstance(basemodel_->GetSkeleton(), &root_);
|
sk_ = SkeletonInstance(basemodel_->GetSkeleton(), &root_);
|
||||||
ubo_.Update();
|
ubo_.Update();
|
||||||
@ -25,8 +32,17 @@ bool game::view::CharacterView::ProcessMsg(net::EntMsgType type, net::InMessage&
|
|||||||
|
|
||||||
void game::view::CharacterView::Update(const UpdateInfo& info)
|
void game::view::CharacterView::Update(const UpdateInfo& info)
|
||||||
{
|
{
|
||||||
auto anim = sk_.GetSkeleton()->GetAnimation("walk");
|
// interpolate states
|
||||||
sk_.ApplySkelAnim(*anim, info.time, 1.0f);
|
float tps = 25.0f;
|
||||||
|
float t = (info.time - update_time_) * tps * 0.8f; // assume some jitter, interpolate for longer
|
||||||
|
t = glm::clamp(t, 0.0f, 2.0f);
|
||||||
|
|
||||||
|
root_.local = Transform::Lerp(states_[0].trans, states_[1].trans, t);
|
||||||
|
animstate_.loco_blend = glm::mix(states_[0].loco_blend, states_[1].loco_blend, t);
|
||||||
|
animstate_.loco_phase = glm::mod(glm::mix(states_[0].loco_phase, states_[1].loco_phase, t), 1.0f);
|
||||||
|
|
||||||
|
animstate_.ApplyToSkeleton(sk_);
|
||||||
|
|
||||||
root_.UpdateMatrix();
|
root_.UpdateMatrix();
|
||||||
sk_.UpdateBoneMatrices();
|
sk_.UpdateBoneMatrices();
|
||||||
ubo_valid_ = false;
|
ubo_valid_ = false;
|
||||||
@ -76,14 +92,67 @@ void game::view::CharacterView::Draw(const DrawArgs& args)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool game::view::CharacterView::ProcessUpdateMsg(net::InMessage& msg)
|
bool game::view::CharacterView::ReadState(net::InMessage& msg)
|
||||||
{
|
{
|
||||||
net::PositionQ posq;
|
update_time_ = world_.GetTime();
|
||||||
if (!net::ReadPositionQ(msg, posq) || !msg.Read<net::PositiveAngleQ>(yaw_))
|
|
||||||
|
// init lerp start state
|
||||||
|
states_[0].trans = root_.local;
|
||||||
|
states_[0].loco_blend = animstate_.loco_blend;
|
||||||
|
states_[0].loco_phase = animstate_.loco_phase;
|
||||||
|
|
||||||
|
auto& new_state = states_[1];
|
||||||
|
|
||||||
|
// parse state delta
|
||||||
|
CharacterSyncFieldFlags fields;
|
||||||
|
if (!msg.Read(fields))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
net::DecodePosition(posq, root_.local.position);
|
// transform
|
||||||
root_.local.rotation = glm::rotate(glm::quat(1.0f, 0.0f, 0.0f, 0.0f), yaw_ + glm::pi<float>() * 0.5f, glm::vec3(0, 0, 1));
|
if (fields & CSF_TRANSFORM)
|
||||||
|
{
|
||||||
|
if (!net::ReadDelta(msg, sync_.pos.x) || !net::ReadDelta(msg, sync_.pos.y) ||
|
||||||
|
!net::ReadDelta(msg, sync_.pos.z) || !net::ReadDelta(msg, sync_.yaw))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
net::DecodePosition(sync_.pos, new_state.trans.position);
|
||||||
|
new_state.trans.rotation = glm::rotate(glm::quat(1.0f, 0.0f, 0.0f, 0.0f),
|
||||||
|
sync_.yaw.Decode() + glm::pi<float>() * 0.5f, glm::vec3(0, 0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields & CSF_IDLE_ANIM)
|
||||||
|
{
|
||||||
|
if (!msg.Read(sync_.idle_anim))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
animstate_.idle_anim_idx = sync_.idle_anim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields & CSF_LOCO_ANIMS)
|
||||||
|
{
|
||||||
|
if (!msg.Read(sync_.walk_anim) || !msg.Read(sync_.run_anim))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
animstate_.walk_anim_idx = sync_.walk_anim;
|
||||||
|
animstate_.run_anim_idx = sync_.run_anim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields & CSF_LOCO_VALS)
|
||||||
|
{
|
||||||
|
if (!net::ReadDelta(msg, sync_.loco_blend) || !net::ReadDelta(msg, sync_.loco_phase))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
new_state.loco_blend = sync_.loco_blend.Decode();
|
||||||
|
new_state.loco_phase = sync_.loco_phase.Decode();
|
||||||
|
|
||||||
|
if (new_state.loco_phase < states_[0].loco_phase)
|
||||||
|
states_[0].loco_phase -= 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool game::view::CharacterView::ProcessUpdateMsg(net::InMessage& msg)
|
||||||
|
{
|
||||||
|
return ReadState(msg);
|
||||||
|
}
|
||||||
|
|||||||
@ -4,10 +4,19 @@
|
|||||||
#include "assets/model.hpp"
|
#include "assets/model.hpp"
|
||||||
#include "game/skeletoninstance.hpp"
|
#include "game/skeletoninstance.hpp"
|
||||||
#include "skinning_ubo.hpp"
|
#include "skinning_ubo.hpp"
|
||||||
|
#include "game/character_anim_state.hpp"
|
||||||
|
#include "game/character_sync.hpp"
|
||||||
|
|
||||||
namespace game::view
|
namespace game::view
|
||||||
{
|
{
|
||||||
|
|
||||||
|
struct CharacterViewState
|
||||||
|
{
|
||||||
|
Transform trans;
|
||||||
|
float loco_blend = 0.0f;
|
||||||
|
float loco_phase = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
class CharacterView : public EntityView
|
class CharacterView : public EntityView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -21,6 +30,7 @@ public:
|
|||||||
virtual void Draw(const DrawArgs& args) override;
|
virtual void Draw(const DrawArgs& args) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool ReadState(net::InMessage& msg);
|
||||||
bool ProcessUpdateMsg(net::InMessage& msg);
|
bool ProcessUpdateMsg(net::InMessage& msg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -31,6 +41,14 @@ private:
|
|||||||
SkinningUBO ubo_;
|
SkinningUBO ubo_;
|
||||||
bool ubo_valid_ = false;
|
bool ubo_valid_ = false;
|
||||||
|
|
||||||
|
CharacterAnimState animstate_;
|
||||||
|
|
||||||
|
// sync
|
||||||
|
CharacterSyncState sync_;
|
||||||
|
CharacterViewState states_[2];
|
||||||
|
float update_time_ = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,4 +100,7 @@ using ColorQ = Quantized<uint8_t, 0, 1>;
|
|||||||
|
|
||||||
using NameTag = FixedStr<64>;
|
using NameTag = FixedStr<64>;
|
||||||
|
|
||||||
|
using AnimBlendQ = Quantized<uint8_t, 0, 1>;
|
||||||
|
using AnimTimeQ = Quantized<uint8_t, 0, 1>;
|
||||||
|
|
||||||
} // namespace net
|
} // namespace net
|
||||||
27
src/utils/math.hpp
Normal file
27
src/utils/math.hpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
inline void MoveToward(float& val, float target, float max_delta)
|
||||||
|
{
|
||||||
|
if (val == target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (val < target)
|
||||||
|
{
|
||||||
|
val += max_delta;
|
||||||
|
if (val > target)
|
||||||
|
val = target;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
val -= max_delta;
|
||||||
|
if (val < target)
|
||||||
|
val = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float UnMix(float a, float b, float x)
|
||||||
|
{
|
||||||
|
return (x - a) / (b - a);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user