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/trianglemesh.hpp"
|
||||
"src/collision/trianglemesh.cpp"
|
||||
"src/game/character_anim_state.hpp"
|
||||
"src/game/character_anim_state.cpp"
|
||||
"src/game/player_input.hpp"
|
||||
"src/game/skeletoninstance.hpp"
|
||||
"src/game/skeletoninstance.cpp"
|
||||
|
||||
@ -54,14 +54,27 @@ int assets::Skeleton::GetBoneIndex(const std::string& name) const
|
||||
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
|
||||
{
|
||||
auto it = anims_.find(name);
|
||||
if (it != anims_.end())
|
||||
{
|
||||
return it->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
return GetAnimation(GetAnimationIdx(name));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
using AnimIdx = uint8_t;
|
||||
constexpr AnimIdx NO_ANIM = 255;
|
||||
|
||||
class Skeleton
|
||||
{
|
||||
public:
|
||||
@ -30,17 +33,20 @@ public:
|
||||
size_t GetNumBones() const { return bones_.size(); }
|
||||
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;
|
||||
|
||||
private:
|
||||
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:
|
||||
std::vector<Bone> bones_;
|
||||
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
|
||||
@ -8,18 +8,21 @@ collision::DynamicsWorld::DynamicsWorld(std::shared_ptr<const assets::Map> map)
|
||||
{
|
||||
bt_world_.setGravity(btVector3(0, 0, -9.81f));
|
||||
|
||||
bt_broadphase_.getOverlappingPairCache()->setInternalGhostPairCallback(&bt_ghost_pair_cb_);
|
||||
|
||||
AddMapCollision();
|
||||
|
||||
btTransform t;
|
||||
t.setIdentity();
|
||||
t.setOrigin(btVector3(0,0,-12));
|
||||
// btTransform t;
|
||||
// t.setIdentity();
|
||||
// t.setOrigin(btVector3(0,0,-12));
|
||||
|
||||
|
||||
// TODO: remove
|
||||
static btDefaultMotionState motion(t);
|
||||
static btBoxShape box(btVector3(100, 100, 2));
|
||||
btRigidBody::btRigidBodyConstructionInfo rbInfo(0.0f, &motion, &box, btVector3(0,0,0));
|
||||
static btRigidBody body(rbInfo);
|
||||
bt_world_.addRigidBody(&body);
|
||||
// static btDefaultMotionState motion(t);
|
||||
// static btBoxShape box(btVector3(100, 100, 2));
|
||||
// btRigidBody::btRigidBodyConstructionInfo rbInfo(0.0f, &motion, &box, btVector3(0,0,0));
|
||||
// static btRigidBody body(rbInfo);
|
||||
// bt_world_.addRigidBody(&body);
|
||||
}
|
||||
|
||||
void collision::DynamicsWorld::AddMapCollision()
|
||||
@ -36,11 +39,13 @@ void collision::DynamicsWorld::AddMapCollision()
|
||||
}
|
||||
|
||||
// add static objects
|
||||
|
||||
// for (const auto& sobjs = map_->GetStaticObjects(); const auto& sobj : sobjs)
|
||||
// {
|
||||
// AddModelInstance(*sobj.model, sobj.node.local);
|
||||
// }
|
||||
for (const auto& chunks = map_->GetChunks(); const auto& chunk : chunks)
|
||||
{
|
||||
for (const auto& obj : chunk.objs)
|
||||
{
|
||||
AddModelInstance(*obj.model, obj.node.local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collision::DynamicsWorld::AddModelInstance(const assets::Model& model, const Transform& trans)
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <map>
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
|
||||
|
||||
#include "assets/map.hpp"
|
||||
|
||||
@ -44,6 +45,7 @@ private:
|
||||
|
||||
btDefaultCollisionConfiguration bt_cfg_;
|
||||
btCollisionDispatcher bt_dispatcher_;
|
||||
btGhostPairCallback bt_ghost_pair_cb_;
|
||||
btDbvtBroadphase bt_broadphase_;
|
||||
btSequentialImpulseConstraintSolver bt_solver_;
|
||||
btDiscreteDynamicsWorld bt_world_;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "character.hpp"
|
||||
#include "world.hpp"
|
||||
#include "net/utils.hpp"
|
||||
#include "assets/cache.hpp"
|
||||
#include "utils/math.hpp"
|
||||
|
||||
game::Character::Character(World& world, const CharacterInfo& info)
|
||||
: 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();
|
||||
static btGhostPairCallback ghostpaircb;
|
||||
bt_world.getBroadphase()->getOverlappingPairCache()->setInternalGhostPairCallback(&ghostpaircb);
|
||||
// bt_world.addCollisionObject(&bt_ghost_, btBroadphaseProxy::CharacterFilter,
|
||||
// btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
||||
bt_world.addCollisionObject(&bt_ghost_);
|
||||
bt_world.addCollisionObject(&bt_ghost_, btBroadphaseProxy::CharacterFilter,
|
||||
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
|
||||
// bt_world.addCollisionObject(&bt_ghost_);
|
||||
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)
|
||||
@ -51,12 +55,21 @@ void game::Character::Update()
|
||||
root_.local.position.z -= shape_.height * 0.5f + shape_.radius - 0.05f; // foot pos
|
||||
|
||||
UpdateMovement();
|
||||
|
||||
sync_current_ = 1 - sync_current_;
|
||||
UpdateSyncState();
|
||||
SendUpdateMsg();
|
||||
}
|
||||
|
||||
void game::Character::SendInitData(Player& player, net::OutMessage& msg) const
|
||||
{
|
||||
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)
|
||||
@ -110,7 +123,7 @@ game::Character::~Character()
|
||||
void game::Character::UpdateMovement()
|
||||
{
|
||||
constexpr float dt = 1.0f / 25.0f;
|
||||
|
||||
bool walking = false;
|
||||
glm::vec2 movedir(0.0f);
|
||||
|
||||
if (in_ & (1 << CIN_FORWARD))
|
||||
@ -129,6 +142,7 @@ void game::Character::UpdateMovement()
|
||||
|
||||
if (movedir.x != 0.0f || movedir.y != 0.0f)
|
||||
{
|
||||
walking = true;
|
||||
float target_yaw = forward_yaw_ + std::atan2(movedir.y, movedir.x);
|
||||
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));
|
||||
}
|
||||
|
||||
// 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()
|
||||
{
|
||||
net::PositionQ posq;
|
||||
net::EncodePosition(root_.local.position, posq);
|
||||
|
||||
auto msg = BeginEntMsg(net::EMSG_UPDATE);
|
||||
net::WritePositionQ(msg, posq);
|
||||
msg.Write<net::PositiveAngleQ>(yaw_);
|
||||
auto fields_pos = msg.Reserve<CharacterSyncFieldFlags>();
|
||||
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)
|
||||
@ -188,3 +282,8 @@ void game::Character::Move(glm::vec3& velocity, float t)
|
||||
// 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 "BulletCollision/CollisionDispatch/btGhostObject.h"
|
||||
#include "BulletDynamics/Character/btKinematicCharacterController.h"
|
||||
#include "character_anim_state.hpp"
|
||||
#include "character_sync.hpp"
|
||||
|
||||
namespace game
|
||||
{
|
||||
@ -52,10 +54,14 @@ public:
|
||||
|
||||
private:
|
||||
void UpdateMovement();
|
||||
void UpdateSyncState();
|
||||
void SendUpdateMsg();
|
||||
CharacterSyncFieldFlags WriteState(net::OutMessage& msg, const CharacterSyncState& base) const;
|
||||
|
||||
void Move(glm::vec3& velocity, float t);
|
||||
|
||||
assets::AnimIdx GetAnim(const std::string& name) const;
|
||||
|
||||
private:
|
||||
CapsuleShape shape_;
|
||||
|
||||
@ -74,6 +80,11 @@ private:
|
||||
|
||||
float walk_speed_ = 2.0f;
|
||||
|
||||
SkeletonInstance sk_;
|
||||
CharacterAnimState animstate_;
|
||||
|
||||
CharacterSyncState sync_[2];
|
||||
size_t sync_current_ = 0;
|
||||
};
|
||||
|
||||
} // 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/model.hpp"
|
||||
#include "net/utils.hpp"
|
||||
#include "worldview.hpp"
|
||||
|
||||
|
||||
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");
|
||||
sk_ = SkeletonInstance(basemodel_->GetSkeleton(), &root_);
|
||||
ubo_.Update();
|
||||
@ -25,8 +32,17 @@ bool game::view::CharacterView::ProcessMsg(net::EntMsgType type, net::InMessage&
|
||||
|
||||
void game::view::CharacterView::Update(const UpdateInfo& info)
|
||||
{
|
||||
auto anim = sk_.GetSkeleton()->GetAnimation("walk");
|
||||
sk_.ApplySkelAnim(*anim, info.time, 1.0f);
|
||||
// interpolate states
|
||||
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();
|
||||
sk_.UpdateBoneMatrices();
|
||||
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;
|
||||
if (!net::ReadPositionQ(msg, posq) || !msg.Read<net::PositiveAngleQ>(yaw_))
|
||||
update_time_ = world_.GetTime();
|
||||
|
||||
// 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;
|
||||
|
||||
net::DecodePosition(posq, root_.local.position);
|
||||
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));
|
||||
// transform
|
||||
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;
|
||||
}
|
||||
|
||||
bool game::view::CharacterView::ProcessUpdateMsg(net::InMessage& msg)
|
||||
{
|
||||
return ReadState(msg);
|
||||
}
|
||||
|
||||
@ -4,10 +4,19 @@
|
||||
#include "assets/model.hpp"
|
||||
#include "game/skeletoninstance.hpp"
|
||||
#include "skinning_ubo.hpp"
|
||||
#include "game/character_anim_state.hpp"
|
||||
#include "game/character_sync.hpp"
|
||||
|
||||
namespace game::view
|
||||
{
|
||||
|
||||
struct CharacterViewState
|
||||
{
|
||||
Transform trans;
|
||||
float loco_blend = 0.0f;
|
||||
float loco_phase = 0.0f;
|
||||
};
|
||||
|
||||
class CharacterView : public EntityView
|
||||
{
|
||||
public:
|
||||
@ -21,6 +30,7 @@ public:
|
||||
virtual void Draw(const DrawArgs& args) override;
|
||||
|
||||
private:
|
||||
bool ReadState(net::InMessage& msg);
|
||||
bool ProcessUpdateMsg(net::InMessage& msg);
|
||||
|
||||
private:
|
||||
@ -31,6 +41,14 @@ private:
|
||||
SkinningUBO ubo_;
|
||||
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 AnimBlendQ = Quantized<uint8_t, 0, 1>;
|
||||
using AnimTimeQ = Quantized<uint8_t, 0, 1>;
|
||||
|
||||
} // 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