Also interpolate taxi and interactive objects
This commit is contained in:
1 parent
28846b9f25
commit
1ab9f300b6
5 files changed
+213
-28
No files matched your search
@@ -44,6 +44,15 @@ Dialogue typewriter text and cutscene presentation timers use the same
|
|||||||
per-render-frame path. This keeps character reveals, subtitle timing, fades, and
|
per-render-frame path. This keeps character reveals, subtitle timing, fades, and
|
||||||
cutscene image poses smooth instead of stepping in 100 ms increments.
|
cutscene image poses smooth instead of stepping in 100 ms increments.
|
||||||
|
|
||||||
|
Interactive objects retain authoritative move, rotate, scale, and fade tasks on
|
||||||
|
the simulation tick. Their previous and current visual transforms are sampled
|
||||||
|
at tick boundaries and interpolated only while drawing, so doors and similar
|
||||||
|
objects remain logically deterministic while their presentation is smooth.
|
||||||
|
|
||||||
|
The same render-only sampling is used for `NpcCar` taxi visuals. Its position,
|
||||||
|
body rotation, wheel rotation, and steering angle are interpolated for drawing;
|
||||||
|
the authoritative taxi movement and waypoint logic remain fixed-step.
|
||||||
|
|
||||||
## Animation skinning
|
## Animation skinning
|
||||||
|
|
||||||
GPU and CPU bone interpolation receive the fractional render frame instead of an
|
GPU and CPU bone interpolation receive the fractional render frame instead of an
|
||||||
|
|||||||
+72
-16
@@ -1134,12 +1134,8 @@ namespace FRG
|
|||||||
drawInstancedStatics("shadow_depth_inst", InstancedTexture::None);
|
drawInstancedStatics("shadow_depth_inst", InstancedTexture::None);
|
||||||
|
|
||||||
for (auto& intObj : interactiveObjects) {
|
for (auto& intObj : interactiveObjects) {
|
||||||
if (intObj.state.castShadow && intObj.loadedObject.texture) {
|
if (intObj.state.castShadow)
|
||||||
renderer.PushMatrix();
|
intObj.drawShadowDepth(renderer);
|
||||||
renderer.TranslateMatrix(intObj.state.position);
|
|
||||||
renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh);
|
|
||||||
renderer.PopMatrix();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw characters (they handle their own skinning shader switch internally)
|
// Draw characters (they handle their own skinning shader switch internally)
|
||||||
@@ -1431,12 +1427,8 @@ namespace FRG
|
|||||||
}
|
}
|
||||||
drawInstancedStatics("shadow_depth_inst", InstancedTexture::None);
|
drawInstancedStatics("shadow_depth_inst", InstancedTexture::None);
|
||||||
for (auto& intObj : interactiveObjects) {
|
for (auto& intObj : interactiveObjects) {
|
||||||
if (intObj.state.castShadowNight && intObj.loadedObject.texture) {
|
if (intObj.state.castShadowNight)
|
||||||
renderer.PushMatrix();
|
intObj.drawShadowDepth(renderer);
|
||||||
renderer.TranslateMatrix(intObj.state.position);
|
|
||||||
renderer.DrawVertexRenderStruct(intObj.loadedObject.mesh);
|
|
||||||
renderer.PopMatrix();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (player) player->drawShadowDepth(renderer);
|
if (player) player->drawShadowDepth(renderer);
|
||||||
for (auto& [id, remote] : remotePlayers_)
|
for (auto& [id, remote] : remotePlayers_)
|
||||||
@@ -1453,14 +1445,75 @@ namespace FRG
|
|||||||
shadowMap->unbind();
|
shadowMap->unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Location::captureNpcCarPresentation(const NpcCar& car)
|
||||||
|
{
|
||||||
|
NpcCarPresentationSample sample;
|
||||||
|
sample.position = car.position;
|
||||||
|
sample.rotation = car.rotation;
|
||||||
|
sample.wheelAngle = car.wheelAngle;
|
||||||
|
sample.steeringAngle = car.steeringAngle;
|
||||||
|
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const bool largeGap = hasNpcCarPresentationSample_ &&
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
now - lastNpcCarPresentationUpdate_).count() > 250;
|
||||||
|
if (!hasNpcCarPresentationSample_ || largeGap) {
|
||||||
|
previousNpcCarPresentation_ = sample;
|
||||||
|
} else {
|
||||||
|
previousNpcCarPresentation_ = currentNpcCarPresentation_;
|
||||||
|
}
|
||||||
|
currentNpcCarPresentation_ = sample;
|
||||||
|
lastNpcCarPresentationUpdate_ = now;
|
||||||
|
hasNpcCarPresentationSample_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location::NpcCarPresentationSample
|
||||||
|
Location::interpolatedNpcCarPresentation(const NpcCar& car) const
|
||||||
|
{
|
||||||
|
NpcCarPresentationSample result;
|
||||||
|
result.position = car.position;
|
||||||
|
result.rotation = car.rotation;
|
||||||
|
result.wheelAngle = car.wheelAngle;
|
||||||
|
result.steeringAngle = car.steeringAngle;
|
||||||
|
if (!hasNpcCarPresentationSample_) return result;
|
||||||
|
|
||||||
|
constexpr float kSimulationTickMs = 100.0f;
|
||||||
|
const float elapsedMs = static_cast<float>(
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - lastNpcCarPresentationUpdate_).count());
|
||||||
|
const float alpha = (std::max)(0.0f,
|
||||||
|
(std::min)(1.0f, elapsedMs / kSimulationTickMs));
|
||||||
|
|
||||||
|
result.position = previousNpcCarPresentation_.position +
|
||||||
|
alpha * (currentNpcCarPresentation_.position -
|
||||||
|
previousNpcCarPresentation_.position);
|
||||||
|
result.wheelAngle = previousNpcCarPresentation_.wheelAngle +
|
||||||
|
alpha * (currentNpcCarPresentation_.wheelAngle -
|
||||||
|
previousNpcCarPresentation_.wheelAngle);
|
||||||
|
result.steeringAngle = previousNpcCarPresentation_.steeringAngle +
|
||||||
|
alpha * (currentNpcCarPresentation_.steeringAngle -
|
||||||
|
previousNpcCarPresentation_.steeringAngle);
|
||||||
|
|
||||||
|
constexpr float kPi = 3.14159265358979323846f;
|
||||||
|
constexpr float kTwoPi = 2.0f * kPi;
|
||||||
|
float rotationDelta = currentNpcCarPresentation_.rotation -
|
||||||
|
previousNpcCarPresentation_.rotation;
|
||||||
|
while (rotationDelta > kPi) rotationDelta -= kTwoPi;
|
||||||
|
while (rotationDelta < -kPi) rotationDelta += kTwoPi;
|
||||||
|
result.rotation = previousNpcCarPresentation_.rotation + alpha * rotationDelta;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void Location::drawNpcCar()
|
void Location::drawNpcCar()
|
||||||
{
|
{
|
||||||
auto locationState = stateSnapshot();
|
auto locationState = stateSnapshot();
|
||||||
if (!locationState) return;
|
if (!locationState) return;
|
||||||
|
const NpcCarPresentationSample car =
|
||||||
|
interpolatedNpcCarPresentation(locationState->taxiCar);
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.TranslateMatrix(locationState->taxiCar.position);
|
renderer.TranslateMatrix(car.position);
|
||||||
renderer.TranslateMatrix({ 0, 0.7f - 0.25f +x, 0 });
|
renderer.TranslateMatrix({ 0, 0.7f - 0.25f +x, 0 });
|
||||||
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(locationState->taxiCar.rotation, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(car.rotation, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
glBindTexture(GL_TEXTURE_2D, carTexture->getTexID());
|
glBindTexture(GL_TEXTURE_2D, carTexture->getTexID());
|
||||||
renderer.DrawVertexRenderStruct(carMesh);
|
renderer.DrawVertexRenderStruct(carMesh);
|
||||||
|
|
||||||
@@ -1480,10 +1533,10 @@ namespace FRG
|
|||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.TranslateMatrix(wheelPositions[i]);
|
renderer.TranslateMatrix(wheelPositions[i]);
|
||||||
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(locationState->taxiCar.wheelAngle, -Eigen::Vector3f::UnitX())).toRotationMatrix());
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(car.wheelAngle, -Eigen::Vector3f::UnitX())).toRotationMatrix());
|
||||||
|
|
||||||
if (isFront[i]) {
|
if (isFront[i]) {
|
||||||
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(locationState->taxiCar.steeringAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(car.steeringAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
}
|
}
|
||||||
renderer.DrawVertexRenderStruct(carWheelMesh);
|
renderer.DrawVertexRenderStruct(carWheelMesh);
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
@@ -1800,6 +1853,7 @@ namespace FRG
|
|||||||
if (!locationState) return;
|
if (!locationState) return;
|
||||||
auto gameSnapshot = gameServer_.snapshot();
|
auto gameSnapshot = gameServer_.snapshot();
|
||||||
if (!gameSnapshot) return;
|
if (!gameSnapshot) return;
|
||||||
|
captureNpcCarPresentation(locationState->taxiCar);
|
||||||
if (events.taxiArrived)
|
if (events.taxiArrived)
|
||||||
scriptEngine.callCallTaxiCallback();
|
scriptEngine.callCallTaxiCallback();
|
||||||
|
|
||||||
@@ -1936,6 +1990,8 @@ namespace FRG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& intObj : interactiveObjects)
|
||||||
|
intObj.capturePresentationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Location::handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
void Location::handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "LocationGameState.h"
|
#include "LocationGameState.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <chrono>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
|
|
||||||
@@ -265,6 +266,21 @@ namespace FRG
|
|||||||
|
|
||||||
PathVisualization pathVisualization;
|
PathVisualization pathVisualization;
|
||||||
|
|
||||||
|
struct NpcCarPresentationSample {
|
||||||
|
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
||||||
|
float rotation = 0.f;
|
||||||
|
float wheelAngle = 0.f;
|
||||||
|
float steeringAngle = 0.f;
|
||||||
|
};
|
||||||
|
|
||||||
|
NpcCarPresentationSample previousNpcCarPresentation_;
|
||||||
|
NpcCarPresentationSample currentNpcCarPresentation_;
|
||||||
|
std::chrono::steady_clock::time_point lastNpcCarPresentationUpdate_;
|
||||||
|
bool hasNpcCarPresentationSample_ = false;
|
||||||
|
|
||||||
|
void captureNpcCarPresentation(const NpcCar& car);
|
||||||
|
NpcCarPresentationSample interpolatedNpcCarPresentation(const NpcCar& car) const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace FRG
|
} // namespace FRG
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
#include <Eigen/Geometry>
|
#include <Eigen/Geometry>
|
||||||
|
|
||||||
namespace FRG {
|
namespace FRG {
|
||||||
@@ -68,8 +70,72 @@ namespace FRG {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void InteractiveObject::capturePresentationState()
|
||||||
|
{
|
||||||
|
PresentationSample sample;
|
||||||
|
sample.position = state.position;
|
||||||
|
sample.rotationY = state.rotationY;
|
||||||
|
sample.scale = state.scale;
|
||||||
|
sample.alpha = state.alpha;
|
||||||
|
sample.isActive = state.isActive;
|
||||||
|
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const bool largeGap = hasPresentationSample_ &&
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
now - lastPresentationUpdate_).count() > 250;
|
||||||
|
if (!hasPresentationSample_ || largeGap ||
|
||||||
|
currentPresentationSample_.isActive != sample.isActive) {
|
||||||
|
previousPresentationSample_ = sample;
|
||||||
|
} else {
|
||||||
|
previousPresentationSample_ = currentPresentationSample_;
|
||||||
|
}
|
||||||
|
currentPresentationSample_ = sample;
|
||||||
|
lastPresentationUpdate_ = now;
|
||||||
|
hasPresentationSample_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractiveObject::PresentationSample
|
||||||
|
InteractiveObject::interpolatedPresentationState() const
|
||||||
|
{
|
||||||
|
PresentationSample result;
|
||||||
|
result.position = state.position;
|
||||||
|
result.rotationY = state.rotationY;
|
||||||
|
result.scale = state.scale;
|
||||||
|
result.alpha = state.alpha;
|
||||||
|
result.isActive = state.isActive;
|
||||||
|
if (!hasPresentationSample_) return result;
|
||||||
|
|
||||||
|
constexpr float kSimulationTickMs = 100.0f;
|
||||||
|
const float elapsedMs = static_cast<float>(
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - lastPresentationUpdate_).count());
|
||||||
|
const float alpha = (std::max)(0.0f,
|
||||||
|
(std::min)(1.0f, elapsedMs / kSimulationTickMs));
|
||||||
|
|
||||||
|
result.position = previousPresentationSample_.position +
|
||||||
|
alpha * (currentPresentationSample_.position -
|
||||||
|
previousPresentationSample_.position);
|
||||||
|
result.scale = previousPresentationSample_.scale +
|
||||||
|
alpha * (currentPresentationSample_.scale -
|
||||||
|
previousPresentationSample_.scale);
|
||||||
|
result.alpha = previousPresentationSample_.alpha +
|
||||||
|
alpha * (currentPresentationSample_.alpha -
|
||||||
|
previousPresentationSample_.alpha);
|
||||||
|
|
||||||
|
constexpr float kPi = 3.14159265358979323846f;
|
||||||
|
constexpr float kTwoPi = 2.0f * kPi;
|
||||||
|
float rotationDelta = currentPresentationSample_.rotationY -
|
||||||
|
previousPresentationSample_.rotationY;
|
||||||
|
while (rotationDelta > kPi) rotationDelta -= kTwoPi;
|
||||||
|
while (rotationDelta < -kPi) rotationDelta += kTwoPi;
|
||||||
|
result.rotationY = previousPresentationSample_.rotationY +
|
||||||
|
alpha * rotationDelta;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void InteractiveObject::drawDarklands(Renderer& renderer) const {
|
void InteractiveObject::drawDarklands(Renderer& renderer) const {
|
||||||
if (!state.isActive || !loadedObject.textureDarklands) return;
|
if (!state.isActive || !loadedObject.textureDarklands) return;
|
||||||
|
const PresentationSample presentation = interpolatedPresentationState();
|
||||||
|
|
||||||
if (state.alpha < 0.999f) {
|
if (state.alpha < 0.999f) {
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
@@ -77,16 +143,16 @@ namespace FRG {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.TranslateMatrix(state.position);
|
renderer.TranslateMatrix(presentation.position);
|
||||||
if (state.rotationY != 0.f) {
|
if (presentation.rotationY != 0.f) {
|
||||||
renderer.TranslateMatrix(state.pivot);
|
renderer.TranslateMatrix(state.pivot);
|
||||||
renderer.RotateMatrix(Eigen::AngleAxisf(state.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
renderer.RotateMatrix(Eigen::AngleAxisf(presentation.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
||||||
renderer.TranslateMatrix(-state.pivot);
|
renderer.TranslateMatrix(-state.pivot);
|
||||||
}
|
}
|
||||||
if (state.scale != 1.f)
|
if (presentation.scale != 1.f)
|
||||||
renderer.ScaleMatrix(state.scale);
|
renderer.ScaleMatrix(presentation.scale);
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
renderer.RenderUniform1f("uAlpha", state.alpha);
|
renderer.RenderUniform1f("uAlpha", presentation.alpha);
|
||||||
glBindTexture(GL_TEXTURE_2D, loadedObject.textureDarklands->getTexID());
|
glBindTexture(GL_TEXTURE_2D, loadedObject.textureDarklands->getTexID());
|
||||||
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
@@ -96,6 +162,7 @@ namespace FRG {
|
|||||||
|
|
||||||
void InteractiveObject::draw(Renderer& renderer) const {
|
void InteractiveObject::draw(Renderer& renderer) const {
|
||||||
if (!state.isActive || !loadedObject.texture) return;
|
if (!state.isActive || !loadedObject.texture) return;
|
||||||
|
const PresentationSample presentation = interpolatedPresentationState();
|
||||||
|
|
||||||
if (state.alpha < 0.999f) {
|
if (state.alpha < 0.999f) {
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
@@ -103,16 +170,16 @@ namespace FRG {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.TranslateMatrix(state.position);
|
renderer.TranslateMatrix(presentation.position);
|
||||||
if (state.rotationY != 0.f) {
|
if (presentation.rotationY != 0.f) {
|
||||||
renderer.TranslateMatrix(state.pivot);
|
renderer.TranslateMatrix(state.pivot);
|
||||||
renderer.RotateMatrix(Eigen::AngleAxisf(state.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
renderer.RotateMatrix(Eigen::AngleAxisf(presentation.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
||||||
renderer.TranslateMatrix(-state.pivot);
|
renderer.TranslateMatrix(-state.pivot);
|
||||||
}
|
}
|
||||||
if (state.scale != 1.f)
|
if (presentation.scale != 1.f)
|
||||||
renderer.ScaleMatrix(state.scale);
|
renderer.ScaleMatrix(presentation.scale);
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
renderer.RenderUniform1f("uAlpha", state.alpha);
|
renderer.RenderUniform1f("uAlpha", presentation.alpha);
|
||||||
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
|
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
|
||||||
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
@@ -120,4 +187,23 @@ namespace FRG {
|
|||||||
renderer.RenderUniform1f("uAlpha", 1.0f);
|
renderer.RenderUniform1f("uAlpha", 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InteractiveObject::drawShadowDepth(Renderer& renderer) const
|
||||||
|
{
|
||||||
|
if (!state.isActive) return;
|
||||||
|
const PresentationSample presentation = interpolatedPresentationState();
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix(presentation.position);
|
||||||
|
if (presentation.rotationY != 0.f) {
|
||||||
|
renderer.TranslateMatrix(state.pivot);
|
||||||
|
renderer.RotateMatrix(Eigen::AngleAxisf(
|
||||||
|
presentation.rotationY, Eigen::Vector3f::UnitY()).toRotationMatrix());
|
||||||
|
renderer.TranslateMatrix(-state.pivot);
|
||||||
|
}
|
||||||
|
if (presentation.scale != 1.f)
|
||||||
|
renderer.ScaleMatrix(presentation.scale);
|
||||||
|
renderer.DrawVertexRenderStruct(loadedObject.mesh);
|
||||||
|
renderer.PopMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace FRG
|
} // namespace FRG
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <Eigen/Core>
|
#include <Eigen/Core>
|
||||||
#include "render/Renderer.h"
|
#include "render/Renderer.h"
|
||||||
#include "InteractiveObjectState.h"
|
#include "InteractiveObjectState.h"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
namespace FRG {
|
namespace FRG {
|
||||||
|
|
||||||
@@ -30,6 +31,8 @@ namespace FRG {
|
|||||||
|
|
||||||
void draw(Renderer& renderer) const;
|
void draw(Renderer& renderer) const;
|
||||||
void drawDarklands(Renderer& renderer) const;
|
void drawDarklands(Renderer& renderer) const;
|
||||||
|
void drawShadowDepth(Renderer& renderer) const;
|
||||||
|
void capturePresentationState();
|
||||||
const std::string& entityId() const { return id; }
|
const std::string& entityId() const { return id; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -38,6 +41,21 @@ namespace FRG {
|
|||||||
friend class LocationEditor;
|
friend class LocationEditor;
|
||||||
friend class ScriptEngine;
|
friend class ScriptEngine;
|
||||||
InteractiveObjectState& state; // non-owning binding to LocationGameState
|
InteractiveObjectState& state; // non-owning binding to LocationGameState
|
||||||
|
|
||||||
|
struct PresentationSample {
|
||||||
|
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
||||||
|
float rotationY = 0.f;
|
||||||
|
float scale = 1.f;
|
||||||
|
float alpha = 1.f;
|
||||||
|
bool isActive = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
PresentationSample previousPresentationSample_;
|
||||||
|
PresentationSample currentPresentationSample_;
|
||||||
|
std::chrono::steady_clock::time_point lastPresentationUpdate_;
|
||||||
|
bool hasPresentationSample_ = false;
|
||||||
|
|
||||||
|
PresentationSample interpolatedPresentationState() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace FRG
|
} // namespace FRG
|
||||||
Reference in new issue
Block a user