From ab1b722ecb6e6c49250cd746bbd255d515a67407 Mon Sep 17 00:00:00 2001 From: Andreas Engberg Date: Tue, 28 Oct 2025 10:03:57 +0100 Subject: [PATCH] simplify keyframes wip --- .../interaction/keyframerecordinghandler.h | 1 + src/interaction/keyframerecordinghandler.cpp | 120 +++++++++++++++++- .../keyframerecordinghandler_lua.inl | 7 + 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/include/openspace/interaction/keyframerecordinghandler.h b/include/openspace/interaction/keyframerecordinghandler.h index 58a21ae810..00e7ab236f 100644 --- a/include/openspace/interaction/keyframerecordinghandler.h +++ b/include/openspace/interaction/keyframerecordinghandler.h @@ -53,6 +53,7 @@ public: void play(); bool hasKeyframeRecording() const; std::vector keyframes() const; + std::vector reduceKeyframes() const; static openspace::scripting::LuaLibrary luaLibrary(); diff --git a/src/interaction/keyframerecordinghandler.cpp b/src/interaction/keyframerecordinghandler.cpp index dc175b619f..9859277516 100644 --- a/src/interaction/keyframerecordinghandler.cpp +++ b/src/interaction/keyframerecordinghandler.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "keyframerecordinghandler_lua.inl" @@ -196,6 +197,122 @@ std::vector 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(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 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(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(A.value) || + std::holds_alternative(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(A.value); + const auto& b = std::get(B.value); + const auto& c = std::get(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 } }; } diff --git a/src/interaction/keyframerecordinghandler_lua.inl b/src/interaction/keyframerecordinghandler_lua.inl index 746511fd49..a28cb6afce 100644 --- a/src/interaction/keyframerecordinghandler_lua.inl +++ b/src/interaction/keyframerecordinghandler_lua.inl @@ -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 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 sequenceTime) {