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

+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;