Working on interpolation

This commit is contained in:
Vladislav Khorev committed 2026-11-06 15:14:48 +06:00
1 parent 23fef6f349
commit 28846b9f25
8 files changed
+231 -51

No files matched your search

+31 -14
View File
@@ -722,9 +722,10 @@ namespace FRG
return -1;
}
void BoneSystemNew::Interpolate(int frame)
void BoneSystemNew::Interpolate(float frame)
{
int startingKeyFrame = -1;
int nextKeyFrame = -1;
for (int i = 0; i < static_cast<int>(animations[0].keyFrames.size()) - 1; i++)
{
int oldFrame = animations[0].keyFrames[i].frame;
@@ -732,22 +733,29 @@ namespace FRG
if (frame >= oldFrame && frame < nextFrame)
{
startingKeyFrame = i;
nextKeyFrame = i + 1;
break;
}
}
if (startingKeyFrame == -1)
{
logger() << "Exception here: frame number is out of range of keyframes. Frame: " << frame << std::endl;
throw std::runtime_error("Exception here");
if (animations[0].keyFrames.size() < 2)
return;
startingKeyFrame = static_cast<int>(animations[0].keyFrames.size()) - 1;
nextKeyFrame = 0;
}
int modifiedFrameNumber = frame - animations[0].keyFrames[startingKeyFrame].frame;
int diffFrames = animations[0].keyFrames[startingKeyFrame + 1].frame - animations[0].keyFrames[startingKeyFrame].frame;
float t = (modifiedFrameNumber + 0.f) / diffFrames;
float modifiedFrameNumber = frame - animations[0].keyFrames[startingKeyFrame].frame;
int diffFrames = nextKeyFrame == 0
? animations[0].keyFrames.back().frame -
animations[0].keyFrames.front().frame + 1
: animations[0].keyFrames[nextKeyFrame].frame -
animations[0].keyFrames[startingKeyFrame].frame;
float t = diffFrames > 0 ? modifiedFrameNumber / diffFrames : 0.0f;
std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingKeyFrame].bones;
std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingKeyFrame + 1].bones;
std::vector<Bone>& nextFrameBones = animations[0].keyFrames[nextKeyFrame].bones;
std::vector<Matrix4f> skinningMatrixForEachBone(currentBones.size());
@@ -906,9 +914,10 @@ namespace FRG
}
}
void GpuSkinningShaderDataNew::ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame)
void GpuSkinningShaderDataNew::ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, float frame)
{
int startingKeyFrame = -1;
int nextKeyFrame = -1;
for (size_t i = 0; i < keyFrames.size() - 1; i++)
{
int oldFrame = keyFrames[i].frame;
@@ -916,6 +925,7 @@ namespace FRG
if (frame >= oldFrame && frame < nextFrame)
{
startingKeyFrame = static_cast<int>(i);
nextKeyFrame = static_cast<int>(i + 1);
break;
}
}
@@ -924,16 +934,23 @@ namespace FRG
if (startingKeyFrame == -1)
{
for (auto& m : skinningMatrices) m = Matrix4f::Identity();
return;
if (keyFrames.size() < 2)
{
for (auto& m : skinningMatrices) m = Matrix4f::Identity();
return;
}
startingKeyFrame = static_cast<int>(keyFrames.size()) - 1;
nextKeyFrame = 0;
}
int modifiedFrameNumber = frame - keyFrames[startingKeyFrame].frame;
int diffFrames = keyFrames[startingKeyFrame + 1].frame - keyFrames[startingKeyFrame].frame;
float t = (modifiedFrameNumber + 0.f) / diffFrames;
float modifiedFrameNumber = frame - keyFrames[startingKeyFrame].frame;
int diffFrames = nextKeyFrame == 0
? keyFrames.back().frame - keyFrames.front().frame + 1
: keyFrames[nextKeyFrame].frame - keyFrames[startingKeyFrame].frame;
float t = diffFrames > 0 ? modifiedFrameNumber / diffFrames : 0.0f;
const std::vector<Bone>& oneFrameBones = keyFrames[startingKeyFrame].bones;
const std::vector<Bone>& nextFrameBones = keyFrames[startingKeyFrame + 1].bones;
const std::vector<Bone>& nextFrameBones = keyFrames[nextKeyFrame].bones;
for (size_t i = 0; i < startBones.size(); i++)
{
+3 -3
View File
@@ -67,7 +67,7 @@ namespace FRG
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
void LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName = "");
void Interpolate(int frame);
void Interpolate(float frame);
int findBoneIndex(const std::string& name) const;
};
@@ -94,14 +94,14 @@ namespace FRG
void prepareGpuSkinningVBOs(BoneSystemNew& model);
void RenderVBO(Renderer& renderer, const std::vector<std::string>& meshNamesOrdered);
void ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame);
void ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, float frame);
};
struct BoneAnimationDataNew
{
BoneSystemNew model;
float currentFrame = 0.f;
int lastFrame = -1;
float lastFrame = -1.0f;
int totalFrames = 1;
GpuSkinningShaderDataNew gpuSkinningShaderData;
+98 -26
View File
@@ -52,6 +52,11 @@ Eigen::Vector3f Character::getCurrentNavigationTarget() const {
return state ? state->getCurrentNavigationTarget() : Eigen::Vector3f::Zero();
}
Eigen::Vector3f Character::renderPosition() const {
auto state = stateSnapshot();
return state ? interpolatedState(*state).position : Eigen::Vector3f::Zero();
}
float Character::getHp() const {
auto state = stateSnapshot();
return state ? state->getHp() : 0.f;
@@ -163,6 +168,62 @@ AnimationState Character::resolveActiveState(const CharacterState& state) const
return AnimationState::STAND;
}
CharacterState Character::interpolatedState(const CharacterState& state) const {
if (!hasPresentationSample_ || !currentPresentationSample_.enabled || !state.enabled) {
return state;
}
constexpr float kSimulationTickMs = 100.0f;
const auto now = std::chrono::steady_clock::now();
const float elapsedMs = static_cast<float>(
std::chrono::duration_cast<std::chrono::milliseconds>(
now - lastPresentationUpdate_).count());
const float alpha = (std::max)(0.0f, (std::min)(1.0f, elapsedMs / kSimulationTickMs));
CharacterState result = state;
result.position = previousPresentationSample_.position +
alpha * (currentPresentationSample_.position - previousPresentationSample_.position);
constexpr float kPi = 3.14159265358979323846f;
constexpr float kTwoPi = 2.0f * kPi;
float angleDelta = currentPresentationSample_.facingAngle -
previousPresentationSample_.facingAngle;
while (angleDelta > kPi) angleDelta -= kTwoPi;
while (angleDelta < -kPi) angleDelta += kTwoPi;
result.facingAngle = previousPresentationSample_.facingAngle + alpha * angleDelta;
if (previousPresentationSample_.currentState == state.currentState &&
currentPresentationSample_.currentState == state.currentState) {
float previousFrame = previousPresentationSample_.animationFrame;
float currentFrame = currentPresentationSample_.animationFrame;
const auto animationIndex = static_cast<std::size_t>(state.currentState);
const int frameCount = animationIndex < state.animationFrameCounts.size()
? state.animationFrameCounts[animationIndex] : 1;
if (frameCount > 1 && currentFrame < previousFrame)
currentFrame += static_cast<float>(frameCount);
result.animationFrame = previousFrame + alpha * (currentFrame - previousFrame);
}
return result;
}
void Character::updateAnimationForRender(const CharacterState& state) {
auto it = animations.find(state.currentState);
if (it == animations.end()) return;
auto& anim = it->second;
anim.currentFrame = (std::min)(
state.animationFrame, static_cast<float>((std::max)(0, anim.totalFrames)));
if (std::abs(anim.currentFrame - anim.lastFrame) <= 0.0001f) return;
if (!anim.model.animations.empty() && !anim.model.animations[0].keyFrames.empty()) {
anim.gpuSkinningShaderData.ComputeSkinningMatrices(
anim.model.startBones, anim.model.animations[0].keyFrames,
anim.currentFrame);
if (!state.useGpuSkinning)
anim.model.Interpolate(anim.currentFrame);
}
anim.lastFrame = anim.currentFrame;
}
void Character::update(int64_t deltaMs, const CharacterUpdateEvents* events) {
if (hitSparkEmitter.isConfigured()) {
hitSparkEmitter.update(static_cast<float>(deltaMs));
@@ -172,6 +233,26 @@ void Character::update(int64_t deltaMs, const CharacterUpdateEvents* events) {
if (!stateSnapshot) return;
const CharacterState& state = *stateSnapshot;
const auto now = std::chrono::steady_clock::now();
PresentationSample sample;
sample.position = state.position;
sample.facingAngle = state.facingAngle;
sample.animationFrame = state.animationFrame;
sample.currentState = state.currentState;
sample.enabled = state.enabled;
const bool largeGap = hasPresentationSample_ &&
std::chrono::duration_cast<std::chrono::milliseconds>(
now - lastPresentationUpdate_).count() > 250;
if (!hasPresentationSample_ || largeGap ||
currentPresentationSample_.enabled != sample.enabled) {
previousPresentationSample_ = sample;
} else {
previousPresentationSample_ = currentPresentationSample_;
}
currentPresentationSample_ = sample;
lastPresentationUpdate_ = now;
hasPresentationSample_ = true;
if (events) {
if (events->deathAnimationComplete && onDeathAnimComplete)
onDeathAnimComplete();
@@ -183,22 +264,7 @@ void Character::update(int64_t deltaMs, const CharacterUpdateEvents* events) {
}
if (!state.enabled) return;
auto it = animations.find(state.currentState);
if (it == animations.end()) return;
auto& anim = it->second;
anim.currentFrame = (std::min)(
state.animationFrame, static_cast<float>((std::max)(0, anim.totalFrames - 1)));
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
anim.gpuSkinningShaderData.ComputeSkinningMatrices(
anim.model.startBones, anim.model.animations[0].keyFrames,
static_cast<int>(anim.currentFrame));
if (!state.useGpuSkinning) {
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
}
anim.lastFrame = static_cast<int>(anim.currentFrame);
}
updateAnimationForRender(state);
}
void Character::draw(Renderer& renderer) {
@@ -211,8 +277,9 @@ void Character::draw(Renderer& renderer) {
auto stateSnapshot = this->stateSnapshot();
if (!stateSnapshot) return;
const CharacterState& state = *stateSnapshot;
CharacterState state = interpolatedState(*stateSnapshot);
if (!state.enabled) return;
updateAnimationForRender(state);
if (state.useGpuSkinning) {
drawGpuSkinning(renderer, state);
@@ -270,7 +337,7 @@ void Character::drawGpuSkinning(Renderer& renderer, const CharacterState& state)
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
anim.gpuSkinningShaderData.ComputeSkinningMatrices(
anim.model.startBones, anim.model.animations[0].keyFrames,
static_cast<int>(anim.currentFrame));
anim.currentFrame);
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) return;
}
@@ -328,7 +395,7 @@ bool Character::prepareGpuSkinning(const CharacterState& state) {
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return false;
anim.gpuSkinningShaderData.ComputeSkinningMatrices(
anim.model.startBones, anim.model.animations[0].keyFrames,
static_cast<int>(anim.currentFrame));
anim.currentFrame);
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) return false;
}
return true;
@@ -370,8 +437,9 @@ void Character::drawAttachedWeapon(Renderer& renderer, const CharacterState& sta
void Character::drawShadowDepth(Renderer& renderer) {
auto stateSnapshot = this->stateSnapshot();
if (!stateSnapshot) return;
const CharacterState& state = *stateSnapshot;
CharacterState state = interpolatedState(*stateSnapshot);
if (!state.enabled) return;
updateAnimationForRender(state);
if (state.useGpuSkinning) {
drawShadowDepthGpuSkinning(renderer, state);
} else {
@@ -453,8 +521,9 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer, const CharacterSt
void Character::drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
auto stateSnapshot = this->stateSnapshot();
if (!stateSnapshot) return;
const CharacterState& state = *stateSnapshot;
CharacterState state = interpolatedState(*stateSnapshot);
if (!state.enabled) return;
updateAnimationForRender(state);
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
}
@@ -603,8 +672,9 @@ void Character::drawNight(Renderer& renderer,
{
auto stateSnapshot = this->stateSnapshot();
if (!stateSnapshot) return;
const CharacterState& state = *stateSnapshot;
CharacterState state = interpolatedState(*stateSnapshot);
if (!state.enabled) return;
updateAnimationForRender(state);
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
}
@@ -766,8 +836,9 @@ void Character::drawNightWithShadow(Renderer& renderer,
{
auto stateSnapshot = this->stateSnapshot();
if (!stateSnapshot) return;
const CharacterState& state = *stateSnapshot;
CharacterState state = interpolatedState(*stateSnapshot);
if (!state.enabled) return;
updateAnimationForRender(state);
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
}
@@ -1014,7 +1085,7 @@ void Character::drawName(TextRenderer& textRenderer,
{
auto stateSnapshot = this->stateSnapshot();
if (!stateSnapshot) return;
const CharacterState& state = *stateSnapshot;
CharacterState state = interpolatedState(*stateSnapshot);
if (state.isPlayer) return;
if (state.npcName.empty()) return;
@@ -1037,10 +1108,11 @@ void Character::drawPlayerName(
{
auto snapshot = stateSnapshot();
if (!snapshot) return;
CharacterState state = interpolatedState(*snapshot);
static const std::array<float, 4> color = {0.f, 1.f, 0.f, 1.f};
drawCharacterNameLabel(
textRenderer, cameraViewMatrix, projectionMatrix,
*snapshot, displayName, color);
state, displayName, color);
}
void Character::drawHealthBar(Renderer& renderer,
@@ -1049,7 +1121,7 @@ void Character::drawHealthBar(Renderer& renderer,
{
auto stateSnapshot = this->stateSnapshot();
if (!stateSnapshot) return;
const CharacterState& state = *stateSnapshot;
CharacterState state = interpolatedState(*stateSnapshot);
if (state.isPlayer || !state.canAttack) return;
if (!state.enabled) return;
if (state.hp <= 0.f) return;
+17
View File
@@ -12,6 +12,7 @@
#include <vector>
#include <unordered_map>
#include <utility>
#include <chrono>
#include "dialogue/TranslationDatabase.h"
#include "AudioPlayerAsync.h"
@@ -94,6 +95,7 @@ public:
bool isMoving() const;
Eigen::Vector3f getRequestedWalkTarget() const;
Eigen::Vector3f getCurrentNavigationTarget() const;
Eigen::Vector3f renderPosition() const;
float getHp() const;
// ---- Weapon rendering data (initialized from state.creationInfo by createFromState) ----
@@ -124,7 +126,22 @@ private:
VertexRenderStruct healthBarMesh;
std::unordered_map<std::string, std::shared_ptr<Texture>> meshTextures;
struct PresentationSample {
Eigen::Vector3f position = Eigen::Vector3f::Zero();
float facingAngle = 0.0f;
float animationFrame = 0.0f;
AnimationState currentState = AnimationState::STAND;
bool enabled = true;
};
PresentationSample previousPresentationSample_;
PresentationSample currentPresentationSample_;
std::chrono::steady_clock::time_point lastPresentationUpdate_;
bool hasPresentationSample_ = false;
AnimationState resolveActiveState(const CharacterState& state) const;
CharacterState interpolatedState(const CharacterState& state) const;
void updateAnimationForRender(const CharacterState& state);
bool prepareGpuSkinning(const CharacterState& state);
void drawAttachedWeapon(Renderer& renderer, const CharacterState& state);
+2
View File
@@ -1194,6 +1194,8 @@ namespace FRG
simulationAccumulatorMs -= kSimulationTickMs;
}
}
if (Location* location = currentLocation())
location->updatePresentation(delta);
#if defined(NETWORK)
if (remoteCampaignActive && websocketGameClient &&
websocketGameClient->connectionFailed()) {
+12 -8
View File
@@ -1011,7 +1011,7 @@ namespace FRG
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(localState_->cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(localState_->cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f camTarget =
playerState ? playerState->position : Eigen::Vector3f::Zero();
player ? player->renderPosition() : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.f, 0 });
@@ -1088,7 +1088,7 @@ namespace FRG
auto playerState = player ? player->stateSnapshot() : nullptr;
const Eigen::Vector3f sceneCenter =
playerState ? playerState->position : Eigen::Vector3f::Zero();
player ? player->renderPosition() : Eigen::Vector3f::Zero();
shadowMap->updateLightSpaceMatrix(sceneCenter);
shadowMap->bind();
@@ -1206,7 +1206,7 @@ namespace FRG
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(localState_->cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
auto playerState = player ? player->stateSnapshot() : nullptr;
const Eigen::Vector3f camTarget =
playerState ? playerState->position : Eigen::Vector3f::Zero();
player ? player->renderPosition() : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.f, 0 });
@@ -1345,7 +1345,7 @@ namespace FRG
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(localState_->cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(localState_->cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f camTarget =
playerState ? playerState->position : Eigen::Vector3f::Zero();
player ? player->renderPosition() : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.f, 0 });
@@ -1591,7 +1591,7 @@ namespace FRG
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(localState_->cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(localState_->cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f camTarget =
playerState ? playerState->position : Eigen::Vector3f::Zero();
player ? player->renderPosition() : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.f, 0 });
@@ -1860,7 +1860,6 @@ namespace FRG
if (player) {
player->update(delta, eventFor(gameServer_.localPlayerId()));
dialogueSystem.update(static_cast<int>(delta));
auto playerState = player->stateSnapshot();
if (playerState) updateTriggerZones(playerState->position);
}
@@ -1896,8 +1895,6 @@ namespace FRG
}
}
for (auto& tz : teleportZones) tz.update(static_cast<float>(delta));
auto playerState = player ? player->stateSnapshot() : nullptr;
if (auto* tz = getTargetTeleportZone(*locationState); tz && playerState) {
float dist = (playerState->position - tz->position).norm();
@@ -2179,6 +2176,13 @@ namespace FRG
}
}
void Location::updatePresentation(int64_t delta)
{
dialogueSystem.update(static_cast<int>(delta));
for (auto& tz : teleportZones)
tz.update(static_cast<float>(delta));
}
bool Location::requestDialogueStart(const std::string& dialogueId)
{
return dialogueSystem.startDialogue(dialogueId);
+1
View File
@@ -210,6 +210,7 @@ namespace FRG
bool setNavigationAreaAvailable(const std::string& areaName, bool available);
void update(int64_t deltaMs, const LocationSimulationEvents& events);
void updatePresentation(int64_t deltaMs);
// Purely visual feedback for click-triggered player routes. Disabling it
// also cancels an animation that is currently playing.
void setPathVisualizationEnabled(bool enabled);