Files
OpenSpace/src/util/timemanager.cpp
Alexander Bock de97a5520a Merge branch 'master' into feature/session-recording-improvements
# Conflicts:
#	include/openspace/network/messagestructures.h
#	src/interaction/sessionrecording.cpp
2020-10-09 22:10:40 +02:00

722 lines
23 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2020 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <openspace/util/timemanager.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/network/parallelpeer.h>
#include <openspace/util/timeline.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/profiling.h>
namespace {
// Properties for time interpolation
// These are used when setting the time from lua time interpolation functions,
// when called without arguments.
constexpr openspace::properties::Property::PropertyInfo
DefaultTimeInterpolationDurationInfo = {
"DefaultTimeInterpolationDuration",
"Default Time Interpolation Duration",
"The default duration taken to interpolate between times"
};
constexpr openspace::properties::Property::PropertyInfo
DefaultDeltaTimeInterpolationDurationInfo = {
"DefaultDeltaTimeInterpolationDuration",
"Default Delta Time Interpolation Duration",
"The default duration taken to interpolate between delta times"
};
constexpr openspace::properties::Property::PropertyInfo
DefaultPauseInterpolationDurationInfo = {
"DefaultPauseInterpolationDuration",
"Default Pause Interpolation Duration",
"The default duration taken to transition to the paused state, "
"when interpolating"
};
constexpr openspace::properties::Property::PropertyInfo
DefaultUnpauseInterpolationDurationInfo = {
"DefaultUnpauseInterpolationDuration",
"Default Unpause Interpolation Duration",
"The default duration taken to transition to the unpaused state, "
"when interpolating"
};
}
namespace openspace {
using datamessagestructures::TimeKeyframe;
TimeManager::TimeManager()
: properties::PropertyOwner({ "TimeManager" })
, _defaultTimeInterpolationDuration(DefaultTimeInterpolationDurationInfo,
2.f,
0.f,
5.f
)
, _defaultDeltaTimeInterpolationDuration(
DefaultDeltaTimeInterpolationDurationInfo,
1.f,
0.f,
5.f
)
, _defaultPauseInterpolationDuration(
DefaultPauseInterpolationDurationInfo,
0.5f,
0.f,
5.f
)
, _defaultUnpauseInterpolationDuration(
DefaultUnpauseInterpolationDurationInfo,
1.f,
0.f,
5.f
)
{
addProperty(_defaultTimeInterpolationDuration);
addProperty(_defaultDeltaTimeInterpolationDuration);
addProperty(_defaultPauseInterpolationDuration);
addProperty(_defaultUnpauseInterpolationDuration);
}
void TimeManager::interpolateTime(double targetTime, double durationSeconds) {
ghoul_precondition(durationSeconds > 0.f, "durationSeconds must be positive");
const double now = global::windowDelegate.applicationTime();
const bool pause = isPaused();
const TimeKeyframeData current = { time(), deltaTime(), false, false };
const TimeKeyframeData next = { Time(targetTime), targetDeltaTime(), pause, false };
clearKeyframes();
addKeyframe(now, current);
addKeyframe(now + durationSeconds, next);
}
void TimeManager::interpolateTimeRelative(double delta, double durationSeconds) {
ghoul_precondition(durationSeconds > 0.f, "durationSeconds must be positive");
const float duration = global::timeManager.defaultTimeInterpolationDuration();
const TimeKeyframeData predictedTime = interpolate(
global::windowDelegate.applicationTime() + duration
);
const double targetTime = predictedTime.time.j2000Seconds() + delta;
interpolateTime(targetTime, durationSeconds);
}
void TimeManager::preSynchronization(double dt) {
ZoneScoped
removeKeyframesBefore(_latestConsumedTimestamp);
progressTime(dt);
// Notify observers about time changes if any.
const double newTime = time().j2000Seconds();
if (newTime != _lastTime) {
ZoneScopedN("newTime != _lastTime")
using K = const CallbackHandle;
using V = TimeChangeCallback;
for (const std::pair<K, V>& it : _timeChangeCallbacks) {
ZoneScopedN("tcc")
it.second();
}
}
if (_deltaTime != _lastDeltaTime ||
_timePaused != _lastTimePaused ||
_targetDeltaTime != _lastTargetDeltaTime)
{
ZoneScopedN("delta time changed")
using K = const CallbackHandle;
using V = TimeChangeCallback;
for (const std::pair<K, V>& it : _deltaTimeChangeCallbacks) {
ZoneScopedN("dtcc")
it.second();
}
}
if (_deltaTimeStepsChanged) {
using K = const CallbackHandle;
using V = TimeChangeCallback;
for (const std::pair<K, V>& it : _deltaTimeStepsChangeCallbacks) {
it.second();
}
}
if (_timelineChanged) {
ZoneScopedN("timeline changed")
using K = const CallbackHandle;
using V = TimeChangeCallback;
for (const std::pair<K, V>& it : _timelineChangeCallbacks) {
ZoneScopedN("tlcc")
it.second();
}
}
_lastTime = newTime;
_lastDeltaTime = _deltaTime;
_lastTargetDeltaTime = _targetDeltaTime;
_lastTimePaused = _timePaused;
_deltaTimeStepsChanged = false;
_timelineChanged = false;
}
TimeKeyframeData TimeManager::interpolate(double applicationTime) {
const std::deque<Keyframe<TimeKeyframeData>>& keyframes = _timeline.keyframes();
auto firstFutureKeyframe = std::lower_bound(
keyframes.begin(),
keyframes.end(),
applicationTime,
&compareKeyframeTimeWithTime
);
const bool hasFutureKeyframes = firstFutureKeyframe != keyframes.end();
const bool hasPastKeyframes = firstFutureKeyframe != keyframes.begin();
const auto lastPastKeyframe = hasPastKeyframes ?
(firstFutureKeyframe - 1) :
keyframes.end();
if (hasPastKeyframes && hasFutureKeyframes && !firstFutureKeyframe->data.jump) {
return interpolate(
*lastPastKeyframe,
*firstFutureKeyframe,
applicationTime
);
}
else if (hasPastKeyframes) {
// Extrapolate based on last past keyframe
const double deltaApplicationTime = applicationTime - lastPastKeyframe->timestamp;
Time predictedTime(
lastPastKeyframe->data.time.j2000Seconds() +
deltaApplicationTime *
(lastPastKeyframe->data.pause ? 0.0 : lastPastKeyframe->data.delta)
);
return TimeKeyframeData {
predictedTime,
lastPastKeyframe->data.delta,
false,
false
};
}
// As the last option, fall back on the current time.
return TimeKeyframeData { _currentTime, _targetDeltaTime, _timePaused, false };
}
void TimeManager::progressTime(double dt) {
ZoneScoped
_integrateFromTime = static_cast<Time&>(_currentTime);
// Frames | 1 2 |
// |------------------------------------|
// Keyframes | a b c d e |
// 1: Previous frame
// 2: Current frame (now)
// lastPastKeyframe: c
// firstFutureKeyframe: d
// _latestConsumedTimestamp: a.timestamp
if (_shouldSetTime) {
// Setting the time using `setTimeNextFrame`
// will override any timeline operations.
_currentTime.data().setTime(_timeNextFrame.j2000Seconds());
_integrateFromTime.data().setTime(_timeNextFrame.j2000Seconds());
_shouldSetTime = false;
using K = const CallbackHandle;
using V = TimeChangeCallback;
for (const std::pair<K, V>& it : _timeJumpCallbacks) {
it.second();
}
return;
}
const double now = global::windowDelegate.applicationTime();
const std::deque<Keyframe<TimeKeyframeData>>& keyframes = _timeline.keyframes();
auto firstFutureKeyframe = std::lower_bound(
keyframes.begin(),
keyframes.end(),
now,
&compareKeyframeTimeWithTime
);
const bool hasFutureKeyframes = firstFutureKeyframe != keyframes.end();
const bool hasPastKeyframes = firstFutureKeyframe != keyframes.begin();
const auto lastPastKeyframe = hasPastKeyframes ?
(firstFutureKeyframe - 1) :
keyframes.end();
const bool hasConsumedLastPastKeyframe = hasPastKeyframes ?
(lastPastKeyframe->timestamp <= _latestConsumedTimestamp) :
true;
if (hasFutureKeyframes && hasPastKeyframes && !firstFutureKeyframe->data.jump) {
// If keyframes exist before and after this frame,
// interpolate between those.
TimeKeyframeData interpolated = interpolate(
*lastPastKeyframe,
*firstFutureKeyframe,
now
);
_currentTime.data().setTime(interpolated.time.j2000Seconds());
_deltaTime = interpolated.delta;
}
else if (!hasConsumedLastPastKeyframe) {
applyKeyframeData(lastPastKeyframe->data);
}
else if (!isPaused()) {
// If there are no keyframes to consider
// and time is not paused, just advance time.
_deltaTime = _targetDeltaTime;
_currentTime.data().advanceTime(dt * _deltaTime);
}
if (hasPastKeyframes) {
_latestConsumedTimestamp = lastPastKeyframe->timestamp;
}
}
TimeKeyframeData TimeManager::interpolate(const Keyframe<TimeKeyframeData>& past,
const Keyframe<TimeKeyframeData>& future,
double appTime)
{
// https://en.wikipedia.org/wiki/Spline_interpolation
// interpolatedTime = (1 - t)y1 + t*y2 + t(1 - t)(a(1 - t) + bt), where
// a = k1 * deltaAppTime - deltaSimTime,
// b = -k2 * deltaAppTime + deltaSimTime,
// with y1 = pastTime, y2 = futureTime, k1 = past dt, k2 = future dt.
const double pastSimTime = past.data.time.j2000Seconds();
const double futureSimTime = future.data.time.j2000Seconds();
const double pastDerivative = past.data.pause ? 0.0 : past.data.delta;
const double futureDerivative = future.data.pause ? 0.0 : future.data.delta;
const double deltaAppTime = future.timestamp - past.timestamp;
const double deltaSimTime = futureSimTime - pastSimTime;
const double t = (appTime - past.timestamp) / deltaAppTime;
const double a = pastDerivative * deltaAppTime - deltaSimTime;
const double b = -futureDerivative * deltaAppTime + deltaSimTime;
const double interpolatedTime =
(1 - t)*pastSimTime + t * futureSimTime + t * (1 - t)*(a*(1 - t) + b * t);
// Derivative of interpolated time.
// Division by deltaAppTime to get appTime derivative
// as opposed to t (in [0, 1]) derivative.
const double interpolatedDeltaTime =
(3 * a*t*t - 4 * a*t + a - 3 * b*t*t + 2 * b*t - pastSimTime + futureSimTime) /
deltaAppTime;
TimeKeyframeData data {
Time(interpolatedTime),
interpolatedDeltaTime,
past.data.pause,
past.data.jump
};
return data;
}
void TimeManager::applyKeyframeData(const TimeKeyframeData& keyframeData) {
const Time& currentTime = keyframeData.time;
_currentTime.data().setTime(currentTime.j2000Seconds());
_timePaused = keyframeData.pause;
_targetDeltaTime = keyframeData.delta;
_deltaTime = _timePaused ? 0.0 : _targetDeltaTime;
}
void TimeManager::addKeyframe(double timestamp, TimeKeyframeData time) {
_timeline.addKeyframe(timestamp, std::move(time));
_timelineChanged = true;
}
void TimeManager::removeKeyframesAfter(double timestamp, bool inclusive) {
size_t nKeyframes = _timeline.nKeyframes();
_timeline.removeKeyframesAfter(timestamp, inclusive);
if (nKeyframes != _timeline.nKeyframes()) {
_timelineChanged = true;
}
}
void TimeManager::removeKeyframesBefore(double timestamp, bool inclusive) {
size_t nKeyframes = _timeline.nKeyframes();
_timeline.removeKeyframesBefore(timestamp, inclusive);
if (nKeyframes != _timeline.nKeyframes()) {
_timelineChanged = true;
}
}
void TimeManager::clearKeyframes() {
size_t nKeyframes = _timeline.nKeyframes();
_timeline.clearKeyframes();
if (nKeyframes != _timeline.nKeyframes()) {
_timelineChanged = true;
}
}
void TimeManager::setTimeNextFrame(Time t) {
_shouldSetTime = true;
_timeNextFrame = std::move(t);
clearKeyframes();
}
void TimeManager::setDeltaTime(double deltaTime) {
interpolateDeltaTime(deltaTime, 0.0);
}
void TimeManager::setDeltaTimeSteps(std::vector<double> deltaTimes) {
std::vector<double> negatives;
negatives.reserve(deltaTimes.size());
std::transform(
deltaTimes.begin(),
deltaTimes.end(),
std::back_inserter(negatives),
[](double d) { return -d; }
);
deltaTimes.reserve(2 * deltaTimes.size());
deltaTimes.insert(deltaTimes.end(), negatives.begin(), negatives.end());
std::sort(deltaTimes.begin(), deltaTimes.end());
deltaTimes.erase(std::unique(deltaTimes.begin(), deltaTimes.end()), deltaTimes.end());
_deltaTimeSteps = std::move(deltaTimes);
_deltaTimeStepsChanged = true;
}
size_t TimeManager::nKeyframes() const {
return _timeline.nKeyframes();
}
const Time& TimeManager::time() const {
return _currentTime;
}
const Time& TimeManager::integrateFromTime() const {
return _integrateFromTime;
}
const Timeline<TimeKeyframeData>& TimeManager::timeline() const {
return _timeline;
}
std::vector<Syncable*> TimeManager::getSyncables() {
return { &_currentTime, &_integrateFromTime };
}
TimeManager::CallbackHandle TimeManager::addTimeChangeCallback(TimeChangeCallback cb) {
CallbackHandle handle = _nextCallbackHandle++;
_timeChangeCallbacks.emplace_back(handle, std::move(cb));
return handle;
}
TimeManager::CallbackHandle TimeManager::addDeltaTimeChangeCallback(TimeChangeCallback cb)
{
CallbackHandle handle = _nextCallbackHandle++;
_deltaTimeChangeCallbacks.emplace_back(handle, std::move(cb));
return handle;
}
TimeManager::CallbackHandle
TimeManager::addDeltaTimeStepsChangeCallback(TimeChangeCallback cb)
{
CallbackHandle handle = _nextCallbackHandle++;
_deltaTimeStepsChangeCallbacks.emplace_back(handle, std::move(cb));
return handle;
}
TimeManager::CallbackHandle TimeManager::addTimeJumpCallback(TimeChangeCallback cb) {
CallbackHandle handle = _nextCallbackHandle++;
_timeJumpCallbacks.emplace_back(handle, std::move(cb));
return handle;
}
TimeManager::CallbackHandle TimeManager::addTimelineChangeCallback(TimeChangeCallback cb)
{
CallbackHandle handle = _nextCallbackHandle++;
_timelineChangeCallbacks.push_back({ handle, std::move(cb) });
return handle;
}
void TimeManager::removeTimeChangeCallback(CallbackHandle handle) {
const auto it = std::find_if(
_timeChangeCallbacks.begin(),
_timeChangeCallbacks.end(),
[handle](const std::pair<CallbackHandle, TimeChangeCallback>& cb) {
return cb.first == handle;
}
);
ghoul_assert(
it != _timeChangeCallbacks.end(),
"handle must be a valid callback handle"
);
_timeChangeCallbacks.erase(it);
}
void TimeManager::removeDeltaTimeChangeCallback(CallbackHandle handle) {
const auto it = std::find_if(
_deltaTimeChangeCallbacks.begin(),
_deltaTimeChangeCallbacks.end(),
[handle](const std::pair<CallbackHandle, std::function<void()>>& cb) {
return cb.first == handle;
}
);
ghoul_assert(
it != _deltaTimeChangeCallbacks.end(),
"handle must be a valid callback handle"
);
_deltaTimeChangeCallbacks.erase(it);
}
void TimeManager::removeDeltaTimeStepsChangeCallback(CallbackHandle handle) {
const auto it = std::find_if(
_deltaTimeStepsChangeCallbacks.begin(),
_deltaTimeStepsChangeCallbacks.end(),
[handle](const std::pair<CallbackHandle, std::function<void()>>& cb) {
return cb.first == handle;
}
);
ghoul_assert(
it != _deltaTimeStepsChangeCallbacks.end(),
"handle must be a valid callback handle"
);
_deltaTimeStepsChangeCallbacks.erase(it);
}
void TimeManager::triggerPlaybackStart() {
_playbackModeEnabled = true;
}
void TimeManager::stopPlayback() {
_playbackModeEnabled = false;
}
void TimeManager::removeTimeJumpCallback(CallbackHandle handle) {
const auto it = std::find_if(
_timeJumpCallbacks.begin(),
_timeJumpCallbacks.end(),
[handle](const std::pair<CallbackHandle, std::function<void()>>& cb) {
return cb.first == handle;
}
);
ghoul_assert(
it != _timeJumpCallbacks.end(),
"handle must be a valid callback handle"
);
_timeJumpCallbacks.erase(it);
}
void TimeManager::removeTimelineChangeCallback(CallbackHandle handle) {
const auto it = std::find_if(
_timelineChangeCallbacks.begin(),
_timelineChangeCallbacks.end(),
[handle](const std::pair<CallbackHandle, std::function<void()>>& cb) {
return cb.first == handle;
}
);
ghoul_assert(
it != _timelineChangeCallbacks.end(),
"handle must be a valid callback handle"
);
_timelineChangeCallbacks.erase(it);
}
bool TimeManager::isPaused() const {
return _timePaused;
}
float TimeManager::defaultTimeInterpolationDuration() const {
return _defaultTimeInterpolationDuration;
}
float TimeManager::defaultDeltaTimeInterpolationDuration() const {
return _defaultDeltaTimeInterpolationDuration;
}
float TimeManager::defaultPauseInterpolationDuration() const {
return _defaultPauseInterpolationDuration;
}
float TimeManager::defaultUnpauseInterpolationDuration() const {
return _defaultUnpauseInterpolationDuration;
}
double TimeManager::deltaTime() const {
return _deltaTime;
}
double TimeManager::targetDeltaTime() const {
return _targetDeltaTime;
}
std::vector<double> TimeManager::deltaTimeSteps() const {
return _deltaTimeSteps;
}
void TimeManager::interpolateDeltaTime(double newDeltaTime, double interpolationDuration)
{
if (newDeltaTime == _targetDeltaTime) {
return;
}
clearKeyframes();
if (_timePaused || interpolationDuration <= 0.0) {
_targetDeltaTime = newDeltaTime;
_deltaTime = _timePaused ? 0.0 : _targetDeltaTime;
return;
}
const double now = global::windowDelegate.applicationTime();
Time newTime(
time().j2000Seconds() + (_deltaTime + newDeltaTime) * 0.5 * interpolationDuration
);
TimeKeyframeData currentKeyframe = { time(), _deltaTime, false, false };
TimeKeyframeData futureKeyframe = { newTime, newDeltaTime, false, false };
_targetDeltaTime = newDeltaTime;
addKeyframe(now, currentKeyframe);
addKeyframe(now + interpolationDuration, futureKeyframe);
}
std::optional<double> TimeManager::nextDeltaTimeStep() {
if (!hasNextDeltaTimeStep()) {
return std::nullopt;
}
std::vector<double>::iterator nextStepIterator = std::upper_bound(
_deltaTimeSteps.begin(),
_deltaTimeSteps.end(),
_targetDeltaTime
);
if (nextStepIterator == _deltaTimeSteps.end()) {
return std::nullopt; // should not get here
}
return *nextStepIterator;
}
std::optional<double> TimeManager::previousDeltaTimeStep() {
if (!hasPreviousDeltaTimeStep()) {
return std::nullopt;
}
std::vector<double>::iterator lowerBoundIterator = std::lower_bound(
_deltaTimeSteps.begin(),
_deltaTimeSteps.end(),
_targetDeltaTime
);
if (lowerBoundIterator == _deltaTimeSteps.begin()) {
return std::nullopt; // should not get here
}
std::vector<double>::iterator prevStepIterator = lowerBoundIterator - 1;
return *prevStepIterator;
}
bool TimeManager::hasNextDeltaTimeStep() const {
if (_deltaTimeSteps.empty())
return false;
return _targetDeltaTime < _deltaTimeSteps.back();
}
bool TimeManager::hasPreviousDeltaTimeStep() const {
if (_deltaTimeSteps.empty())
return false;
return _targetDeltaTime > _deltaTimeSteps.front();
}
void TimeManager::setNextDeltaTimeStep() {
interpolateNextDeltaTimeStep(0);
}
void TimeManager::setPreviousDeltaTimeStep() {
interpolatePreviousDeltaTimeStep(0);
}
void TimeManager::interpolateNextDeltaTimeStep(double durationSeconds) {
if (!hasNextDeltaTimeStep())
return;
double nextDeltaTime = nextDeltaTimeStep().value();
interpolateDeltaTime(nextDeltaTime, durationSeconds);
}
void TimeManager::interpolatePreviousDeltaTimeStep(double durationSeconds) {
if (!hasPreviousDeltaTimeStep())
return;
double previousDeltaTime = previousDeltaTimeStep().value();
interpolateDeltaTime(previousDeltaTime, durationSeconds);
}
void TimeManager::setPause(bool pause) {
interpolatePause(pause, 0);
}
void TimeManager::interpolatePause(bool pause, double interpolationDuration) {
if (pause == _timePaused) {
return;
}
const double now = global::windowDelegate.applicationTime();
double targetDelta = pause ? 0.0 : _targetDeltaTime;
Time newTime(
time().j2000Seconds() + (_deltaTime + targetDelta) * 0.5 * interpolationDuration
);
TimeKeyframeData currentKeyframe = { time(), _deltaTime, false, false };
TimeKeyframeData futureKeyframe = { newTime, _targetDeltaTime, pause, false };
_timePaused = pause;
clearKeyframes();
if (interpolationDuration > 0) {
addKeyframe(now, currentKeyframe);
}
addKeyframe(now + interpolationDuration, futureKeyframe);
}
} // namespace openspace