fekalnigtacko/src/game/character.cpp

363 lines
10 KiB
C++

#include "character.hpp"
#include "assets/cache.hpp"
#include "net/utils.hpp"
#include "utils/math.hpp"
#include "world.hpp"
game::Character::Character(World& world, const CharacterTuning& tuning)
: Super(world, net::ET_CHARACTER), tuning_(tuning), bt_shape_(tuning_.shape.radius, tuning_.shape.height)
{
z_offset_ = tuning_.shape.height * 0.5f + tuning_.shape.radius - 0.05f;
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)
{
constexpr float PI = glm::pi<float>();
constexpr float TWO_PI = glm::two_pi<float>();
angle = glm::mod(angle, TWO_PI);
target = glm::mod(target, TWO_PI);
float diff = glm::mod(target - angle + PI, TWO_PI) - PI;
if (glm::abs(diff) <= step)
{
angle = target;
return true;
}
angle = glm::mod(angle + glm::sign(diff) * step, TWO_PI);
return false;
}
void game::Character::Update()
{
Super::Update();
SyncTransformFromController();
root_.UpdateMatrix();
UpdateMovement();
sync_current_ = 1 - sync_current_;
UpdateSyncState();
SendUpdateMsg();
}
void game::Character::SendInitData(Player& player, net::OutMessage& msg) const
{
Super::SendInitData(player, msg);
// write clothes
msg.Write<net::NumClothes>(tuning_.clothes.size());
for (const auto& clothes : tuning_.clothes)
{
msg.Write(net::ClothesName(clothes.name));
net::WriteRGB(msg, clothes.color);
}
// 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::Attach(net::EntNum parentnum)
{
Super::Attach(parentnum);
// remake these if already updated
if (IsUpToDate())
{
UpdateSyncState();
SendUpdateMsg();
}
}
void game::Character::EnablePhysics(bool enable)
{
if (enable && !controller_)
{
controller_ = std::make_unique<CharacterPhysicsController>(world_.GetBtWorld(), bt_shape_);
SyncControllerTransform();
}
else if (!enable && controller_)
{
controller_.reset();
}
}
void game::Character::SetInput(CharacterInputType type, bool enable)
{
if (enable)
in_ |= (1 << type);
else
in_ &= ~(1 << type);
}
// static bool SweepCapsule(btCollisionWorld& world, const btCapsuleShapeZ& shape, const glm::vec3& start,
// const glm::vec3& end, float& hit_fraction, glm::vec3& hit_normal)
// {
// btVector3 bt_start(start.x, start.y, start.z);
// btVector3 bt_end(end.x, end.y, end.z);
// static const btMatrix3x3 bt_basis(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
// btTransform start_transform(bt_basis, bt_start);
// btTransform end_transform(bt_basis, bt_end);
// btCollisionWorld::ClosestConvexResultCallback result_callback(bt_start, bt_end);
// world.convexSweepTest(&shape, start_transform, end_transform, result_callback);
// if (result_callback.hasHit())
// {
// hit_fraction = result_callback.m_closestHitFraction;
// hit_normal = glm::vec3(result_callback.m_hitNormalWorld.x(), result_callback.m_hitNormalWorld.y(),
// result_callback.m_hitNormalWorld.z());
// return true;
// }
// return false;
// }
void game::Character::SetPosition(const glm::vec3& position)
{
root_.local.position = position;
SyncControllerTransform();
}
void game::Character::SetMainAnim(const std::string& anim_name)
{
animstate_.idle_anim_idx = GetAnim(anim_name);
}
void game::Character::SyncControllerTransform()
{
if (!controller_)
return;
auto& position = root_.local.position;
auto& bt_ghost = controller_->GetBtGhost();
auto trans = bt_ghost.getWorldTransform();
trans.setOrigin(btVector3(position.x, position.y, position.z + z_offset_));
bt_ghost.setWorldTransform(trans);
}
void game::Character::SyncTransformFromController()
{
if (!controller_)
return;
auto bt_trans = controller_->GetBtGhost().getWorldTransform();
root_.local.SetBtTransform(bt_trans);
root_.local.position.z -= z_offset_; // foot pos
}
void game::Character::UpdateMovement()
{
constexpr float dt = 1.0f / 25.0f;
constexpr float running_mult = 3.0f;
bool walking = false;
bool running = false;
glm::vec2 movedir(0.0f);
if (in_ & (1 << CIN_FORWARD))
movedir.x += 1.0f;
if (in_ & (1 << CIN_BACKWARD))
movedir.x -= 1.0f;
if (in_ & (1 << CIN_RIGHT))
movedir.y -= 1.0f;
if (in_ & (1 << CIN_LEFT))
movedir.y += 1.0f;
glm::vec3 walkdir(0.0f);
if (movedir.x != 0.0f || movedir.y != 0.0f)
{
walking = true;
if (in_ & (1 << CIN_SPRINT))
running = true;
float target_yaw = forward_yaw_ + std::atan2(movedir.y, movedir.x);
Turn(yaw_, target_yaw, 8.0f * dt);
glm::vec3 forward_dir(glm::cos(yaw_), glm::sin(yaw_), 0.0f);
walkdir = forward_dir * walk_speed_ * dt;
if (running)
walkdir *= running_mult;
}
if (controller_)
{
auto& bt_character = controller_->GetBtController();
bt_character.setWalkDirection(btVector3(walkdir.x, walkdir.y, walkdir.z));
if (in_ & (1 << CIN_JUMP) && bt_character.canJump())
{
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));
if (running)
anim_speed *= running_mult;
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()
{
auto msg = BeginUpdateMsg();
auto fields_pos = msg.Reserve<CharacterSyncFieldFlags>();
auto fields = WriteState(msg, sync_[1 - sync_current_]);
if (fields == 0)
{
DiscardUpdateMsg();
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)
{
// glm::vec3 u = velocity * t; // Calculate the movement vector
// btCollisionWorld& bt_world = world_.GetBtWorld();
// btCapsuleShapeZ bt_shape(shape_.radius, shape_.height);
// const int MAX_ITERS = 16;
// for (size_t i = 0; i < MAX_ITERS && glm::dot(u, u) > 0.0f; ++i)
// {
// // printf("Entity::Move: Iteration %zu, u = (%f, %f, %f)\n", i, u.x, u.y, u.z);
// glm::vec3 to = position_ + u;
// float hit_fraction = 1.0f;
// glm::vec3 hit_normal;
// bool hit = SweepCapsule(bt_world, bt_shape, position_, to, hit_fraction, hit_normal);
// // Update the position based on the hit fraction
// position_ += hit_fraction * u;
// if (!hit)
// break;
// // hit_normal *= -1.0f; // Invert the normal to point outwards
// // printf("Entity::Move: Hit detected, hit_fraction = %f, hit_normal = (%f, %f, %f)\n", hit_fraction,
// // hit_normal.x, hit_normal.y, hit_normal.z);
// u -= hit_fraction * u; // Reduce the movement vector by the hit fraction
// u -= glm::dot(u, hit_normal) * hit_normal; // Reflect the velocity along the hit normal
// 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);
}
game::CharacterPhysicsController::CharacterPhysicsController(btDynamicsWorld& bt_world, btCapsuleShapeZ& bt_shape)
: bt_world_(bt_world), bt_character_(&bt_ghost_, &bt_shape, 0.3f, btVector3(0, 0, 1))
{
btTransform start_transform;
start_transform.setIdentity();
bt_ghost_.setWorldTransform(start_transform);
bt_ghost_.setCollisionShape(&bt_shape);
bt_ghost_.setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
bt_world_.addCollisionObject(&bt_ghost_, btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
// bt_world.addCollisionObject(&bt_ghost_);
bt_world_.addAction(&bt_character_);
}
game::CharacterPhysicsController::~CharacterPhysicsController()
{
bt_world_.removeAction(&bt_character_);
bt_world_.removeCollisionObject(&bt_ghost_);
}