simplify keyframes wip

This commit is contained in:
Andreas Engberg
2025-10-28 10:03:57 +01:00
parent 717de1bbdc
commit ab1b722ecb
3 changed files with 127 additions and 1 deletions

View File

@@ -53,6 +53,7 @@ public:
void play();
bool hasKeyframeRecording() const;
std::vector<ghoul::Dictionary> keyframes() const;
std::vector<ghoul::Dictionary> reduceKeyframes() const;
static openspace::scripting::LuaLibrary luaLibrary();

View File

@@ -27,6 +27,7 @@
#include <openspace/interaction/sessionrecordinghandler.h>
#include <openspace/network/messagestructureshelper.h>
#include <openspace/util/timemanager.h>
#include <glm/gtc/quaternion.hpp>
#include "keyframerecordinghandler_lua.inl"
@@ -196,6 +197,122 @@ std::vector<ghoul::Dictionary> KeyframeRecordingHandler::keyframes() const {
return sessionRecordingToDictionary(_timeline);
}
SessionRecording::Entry::Camera interpolate(const SessionRecording::Entry::Camera& a,
const SessionRecording::Entry::Camera& b,
double t)
{
// Linear interpolate for position and scale and slerp for quaternion rotation
glm::dvec3 pos = glm::mix(a.position, b.position, t);
double scale = glm::mix(a.scale, b.scale, t);
glm::quat quat = glm::slerp(a.rotation, b.rotation, static_cast<float>(t));
SessionRecording::Entry::Camera c;
c.position = std::move(pos);
c.rotation = std::move(quat);
c.scale = scale;
c.focusNode = a.focusNode;
c.followFocusNodeRotation = a.followFocusNodeRotation;
return c;
}
double isInterpolatedKeyframe(const SessionRecording::Entry::Camera& a,
const SessionRecording::Entry::Camera& b)
{
// a - b if all of the components are ideally = 0
// abs(a - b) / b < some %
double aLen = glm::length(a.position);
double bLen = glm::length(b.position);
double min = std::min(aLen, bLen);
double max = std::max(aLen, bLen);
double frac = min / max;
// If we're within 1% error margin the position is regarded as interpolated
bool isPositionInterpolated = (1.0 - frac) < 1;
bool isScaleInterpolated = std::abs(a.scale - b.scale) < 1e4;
//glm::angle()
return isPositionInterpolated && isScaleInterpolated;
double posDiff = glm::distance(a.position, b.position);
//auto rotDiff = glm::
return posDiff < 1e-5;
}
std::vector<ghoul::Dictionary> KeyframeRecordingHandler::reduceKeyframes() const {
SessionRecording timeline;
timeline.entries.reserve(_timeline.entries.size());
const auto& entries = _timeline.entries;
auto& r = timeline.entries;
r.push_back(entries[0]);
for (size_t i = 1; i < entries.size() - 1; i++) {
const SessionRecording::Entry& A = entries[i - 1];
const SessionRecording::Entry& B = entries[i];
const SessionRecording::Entry& C = entries[i + 1];
// Always add the scripts
if (std::holds_alternative<SessionRecording::Entry::Script>(B.value)) {
r.push_back(B);
continue;
}
// A, B, and C are not all camera keyframes we cant interpolate so we need to add B
if (std::holds_alternative<SessionRecording::Entry::Script>(A.value) ||
std::holds_alternative<SessionRecording::Entry::Script>(C.value))
{
r.push_back(B);
continue;
}
// If the timestamp is large between keyframes we don't want to lose potential
// camera acceleration. E.g., If the movement between A and B is fast and
// between B and C slow, we will lose temporal acceleration even if B is an
// interpolated keyframe. TODO: potentially look at the acceleration between
// keyframes? Also should the threshold be 1 second, less, more?
// TODO: remove timestamps where the t value is between 0.4 and 0.6
if (B.timestamp - A.timestamp > 1.0) {
r.push_back(B);
continue;
}
const auto& a = std::get<SessionRecording::Entry::Camera>(A.value);
const auto& b = std::get<SessionRecording::Entry::Camera>(B.value);
const auto& c = std::get<SessionRecording::Entry::Camera>(C.value);
// If the keyframes are not on the same focus node keep B
if (a.focusNode != b.focusNode || b.focusNode != c.focusNode) {
r.push_back(B);
continue;
}
// If the following changes we want to keep B
if (a.followFocusNodeRotation != b.followFocusNodeRotation ||
b.followFocusNodeRotation != c.followFocusNodeRotation)
{
r.push_back(B);
continue;
}
// Compute where B lies in time between A and C
const double t = (B.timestamp - A.timestamp) / (C.timestamp - A.timestamp);
// Compute an interpolated keyframe
SessionRecording::Entry::Camera interpolated = interpolate(a, c, t);
// Compare the interpolated keyframe with the existing keyframe, we only keep
// keyframes that are sufficiently different
if (!isInterpolatedKeyframe(b, interpolated)) {
r.push_back(B);
}
}
r.push_back(entries[entries.size() - 1]);
return sessionRecordingToDictionary(timeline);
}
scripting::LuaLibrary KeyframeRecordingHandler::luaLibrary() {
return {
"keyframeRecording",
@@ -213,7 +330,8 @@ scripting::LuaLibrary KeyframeRecordingHandler::luaLibrary() {
codegen::lua::LoadSequence,
codegen::lua::Play,
codegen::lua::Pause,
codegen::lua::Keyframes
codegen::lua::Keyframes,
codegen::lua::ReduceKeyframes
}
};
}

View File

@@ -94,6 +94,13 @@ namespace {
global::keyframeRecording->loadSequence(std::move(filename));
}
// Simplifies a timeline recording by removing redudant keyframes. Keyframes are removed
// if they are a linear combination of its neighboring keyframes.
[[codegen::luawrap]] std::vector<ghoul::Dictionary> reduceKeyframes() {
using namespace openspace;
return global::keyframeRecording->reduceKeyframes();
}
// Playback sequence optionally from the specified `sequenceTime` or if not specified
// starts playing from the current time set within the sequence
[[codegen::luawrap]] void play(std::optional<double> sequenceTime) {