Feature/time interpolation (#669)

* Initial implementation
* Better approximation of target time
* Correctly use double precision for time passing
* Cleanup
* Adding proportional adjustment of delta time at end of interpolation
* Keyframe based time interpolation
* Add property for time interpolation duration. Move time interpolation methods to TimeManager.
* Fix bugs with time gui
* Make several clicks on delta time buttons work as expected
* Clean up
* Improve time interpolation for parallel connection
* Improve time API. Fix time interpolation bugs.
* Fix mac compile issue
* Add hour button
* Add missing + sign
* Remove newer images from projection buffer when going back in time
* Add comment about clearing projection buffer
* Fix bug with jumping time in parallel connection
* Rename integrateFromTime to previousFrameTime
* Compile fix for iswa module
* Address code review comments
* Code cleanup
* Fix bug causig unsmooth behaviour when pausing while interpolating in time
This commit is contained in:
Emil Axelsson
2018-07-13 11:07:35 +02:00
committed by GitHub
parent 6007f1d70d
commit 902e3e6dac
42 changed files with 1465 additions and 586 deletions
+1
View File
@@ -167,6 +167,7 @@ OpenSpaceEngine::OpenSpaceEngine(std::string programName,
_navigationHandler->setPropertyOwner(_rootPropertyOwner.get());
// New property subowners also have to be added to the ImGuiModule callback!
_rootPropertyOwner->addPropertySubOwner(_navigationHandler.get());
_rootPropertyOwner->addPropertySubOwner(_timeManager.get());
_rootPropertyOwner->addPropertySubOwner(_renderEngine.get());
_rootPropertyOwner->addPropertySubOwner(_renderEngine->screenSpaceOwner());
+2 -2
View File
@@ -132,8 +132,8 @@ void KeyframeNavigator::addKeyframe(double timestamp, KeyframeNavigator::CameraP
timeline().addKeyframe(timestamp, std::move(pose));
}
void KeyframeNavigator::removeKeyframesAfter(double timestamp) {
timeline().removeKeyframesAfter(timestamp);
void KeyframeNavigator::removeKeyframesAfter(double timestamp, Inclusive inclusive) {
timeline().removeKeyframesAfter(timestamp, inclusive);
}
void KeyframeNavigator::clearKeyframes() {
+2 -2
View File
@@ -90,7 +90,7 @@ NSArray* focusIdentifiers;
if ([identifier isEqualToString:pauseResultId]) {
NSButton* button = [NSButton
buttonWithTitle:NSLocalizedString(
(OsEng.timeManager().time().paused() ? @"Resume" : @"Pause"),
(OsEng.timeManager().isPaused() ? @"Resume" : @"Pause"),
@""
)
target:self action:@selector(pauseResumeButtonAction:)
@@ -177,7 +177,7 @@ NSArray* focusIdentifiers;
NSButton* button = static_cast<NSButton*>(sender);
// This check is inverted since the togglePause script has not run yet
[button setTitle: OsEng.timeManager().time().paused() ? @"Pause" : @"Resume"];
[button setTitle: OsEng.timeManager().isPaused() ? @"Pause" : @"Resume"];
}
- (void)focusObjectAction:(id)sender {
+2 -1
View File
@@ -95,7 +95,8 @@ void NetworkEngine::publishStatusMessage() {
const double time = currentTime.j2000Seconds();
const std::string timeString = currentTime.UTC();
const double delta = currentTime.deltaTime();
double delta = OsEng.timeManager().deltaTime();
messageSize += sizeof(time);
messageSize += static_cast<uint16_t>(timeString.length());
+30 -9
View File
@@ -24,25 +24,31 @@
#include <openspace/network/parallelconnection.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/engine/wrapper/windowwrapper.h>
#include <ghoul/fmt.h>
#include <ghoul/io/socket/tcpsocket.h>
#include <ghoul/logging/logmanager.h>
namespace {
constexpr const uint32_t ProtocolVersion = 4;
constexpr const char* _loggerCat = "ParallelConnection";
} // namespace
namespace openspace {
const unsigned int ParallelConnection::ProtocolVersion = 5;
ParallelConnection::Message::Message(MessageType t, std::vector<char> c)
: type(t)
, content(std::move(c))
{}
ParallelConnection::DataMessage::DataMessage(datamessagestructures::Type t,
double time,
std::vector<char> c)
: type(t)
, timestamp(time)
, content(std::move(c))
{}
@@ -60,6 +66,7 @@ bool ParallelConnection::isConnectedOrConnecting() const {
void ParallelConnection::sendDataMessage(const DataMessage& dataMessage) {
const uint32_t dataMessageTypeOut = static_cast<uint32_t>(dataMessage.type);
const double dataMessageTimestamp = dataMessage.timestamp;
std::vector<char> messageContent;
messageContent.insert(
@@ -68,6 +75,12 @@ void ParallelConnection::sendDataMessage(const DataMessage& dataMessage) {
reinterpret_cast<const char*>(&dataMessageTypeOut) + sizeof(uint32_t)
);
messageContent.insert(
messageContent.end(),
reinterpret_cast<const char*>(&dataMessageTimestamp),
reinterpret_cast<const char*>(&dataMessageTimestamp) + sizeof(double)
);
messageContent.insert(messageContent.end(),
dataMessage.content.begin(),
dataMessage.content.end()
@@ -79,7 +92,6 @@ void ParallelConnection::sendDataMessage(const DataMessage& dataMessage) {
bool ParallelConnection::sendMessage(const Message& message) {
const uint32_t messageTypeOut = static_cast<uint32_t>(message.type);
const uint32_t messageSizeOut = static_cast<uint32_t>(message.content.size());
std::vector<char> header;
//insert header into buffer
@@ -121,8 +133,10 @@ ghoul::io::TcpSocket* ParallelConnection::socket() {
}
ParallelConnection::Message ParallelConnection::receiveMessage() {
// Header consists of 'OS' + majorVersion + minorVersion + messageSize
constexpr size_t HeaderSize = 2 * sizeof(char) + 3 * sizeof(uint32_t);
// Header consists of...
constexpr size_t HeaderSize =
2 * sizeof(char) + // OS
3 * sizeof(uint32_t); // Protocol version, message type and message size
// Create basic buffer for receiving first part of messages
std::vector<char> headerBuffer(HeaderSize);
@@ -140,11 +154,10 @@ ParallelConnection::Message ParallelConnection::receiveMessage() {
throw ConnectionLostError();
}
uint32_t* ptr = reinterpret_cast<uint32_t*>(&headerBuffer[2]);
const uint32_t protocolVersionIn = *(ptr++);
const uint32_t messageTypeIn = *(ptr++);
const uint32_t messageSizeIn = *(ptr++);
size_t offset = 2;
const uint32_t protocolVersionIn =
*reinterpret_cast<uint32_t*>(headerBuffer.data() + offset);
offset += sizeof(uint32_t);
if (protocolVersionIn != ProtocolVersion) {
LERROR(fmt::format(
@@ -155,6 +168,14 @@ ParallelConnection::Message ParallelConnection::receiveMessage() {
throw ConnectionLostError();
}
const uint32_t messageTypeIn =
*reinterpret_cast<uint32_t*>(headerBuffer.data() + offset);
offset += sizeof(uint32_t);
const uint32_t messageSizeIn =
*reinterpret_cast<uint32_t*>(headerBuffer.data() + offset);
offset += sizeof(uint32_t);
const size_t messageSize = messageSizeIn;
// Receive the payload
+141 -54
View File
@@ -95,12 +95,6 @@ namespace {
"Camera Keyframe interval",
"" // @TODO Missing documentation
};
constexpr openspace::properties::Property::PropertyInfo TimeToleranceInfo = {
"TimeTolerance",
"Time tolerance",
"" // @TODO Missing documentation
};
} // namespace
namespace openspace {
@@ -115,7 +109,6 @@ ParallelPeer::ParallelPeer()
, _bufferTime(BufferTimeInfo, 0.2f, 0.01f, 5.0f)
, _timeKeyframeInterval(TimeKeyFrameInfo, 0.1f, 0.f, 1.f)
, _cameraKeyframeInterval(CameraKeyFrameInfo, 0.1f, 0.f, 1.f)
, _timeTolerance(TimeToleranceInfo, 1.f, 0.5f, 5.f)
, _connectionEvent(std::make_shared<ghoul::Event<>>())
, _connection(nullptr)
{
@@ -129,11 +122,17 @@ ParallelPeer::ParallelPeer()
addProperty(_timeKeyframeInterval);
addProperty(_cameraKeyframeInterval);
addProperty(_timeTolerance);
}
ParallelPeer::~ParallelPeer() {
disconnect();
if (_timeJumpCallback != -1) {
OsEng.timeManager().removeTimeJumpCallback(_timeJumpCallback);
}
if (_timeJumpCallback != -1) {
OsEng.timeManager().removeTimeJumpCallback(_timeJumpCallback);
}
}
void ParallelPeer::connect() {
@@ -207,7 +206,7 @@ void ParallelPeer::queueInMessage(const ParallelConnection::Message& message) {
_receiveBuffer.push_back(message);
}
void ParallelPeer::handleMessage(const ParallelConnection::Message& message) {
void ParallelPeer::handleMessage(const ParallelConnection::Message& message) {
switch (message.type) {
case ParallelConnection::MessageType::Data:
dataMessageReceived(message.content);
@@ -224,10 +223,10 @@ void ParallelPeer::handleMessage(const ParallelConnection::Message& message) {
}
}
double ParallelPeer::calculateBufferedKeyframeTime(double originalTime) {
void ParallelPeer::analyzeTimeDifference(double messageTimestamp) {
std::lock_guard<std::mutex> latencyLock(_latencyMutex);
const double timeDiff = OsEng.windowWrapper().applicationTime() - originalTime;
const double timeDiff = OsEng.windowWrapper().applicationTime() - messageTimestamp;
if (_latencyDiffs.empty()) {
_initialTimeDiff = timeDiff;
}
@@ -236,8 +235,11 @@ double ParallelPeer::calculateBufferedKeyframeTime(double originalTime) {
_latencyDiffs.pop_front();
}
_latencyDiffs.push_back(latencyDiff);
}
return originalTime + timeDiff + latencyDiff + _bufferTime;
double ParallelPeer::convertTimestamp(double messageTimestamp) {
std::lock_guard<std::mutex> latencyLock(_latencyMutex);
return messageTimestamp + _initialTimeDiff + _bufferTime;
}
double ParallelPeer::latencyStandardDeviation() const {
@@ -259,46 +261,84 @@ double ParallelPeer::latencyStandardDeviation() const {
return std::sqrt(latencyVariance);
}
double ParallelPeer::timeTolerance() const {
return _timeTolerance;
}
void ParallelPeer::dataMessageReceived(const std::vector<char>& message)
{
size_t offset = 0;
void ParallelPeer::dataMessageReceived(const std::vector<char>& message) {
// The type of data message received
const uint32_t type = *(reinterpret_cast<const uint32_t*>(message.data()));
std::vector<char> buffer(message.begin() + sizeof(uint32_t), message.end());
const uint32_t type = *(reinterpret_cast<const uint32_t*>(message.data() + offset));
offset += sizeof(uint32_t);
const double timestamp = *(reinterpret_cast<const double*>(message.data() + offset));
offset += sizeof(double);
analyzeTimeDifference(timestamp);
std::vector<char> buffer(message.begin() + offset, message.end());
switch (static_cast<datamessagestructures::Type>(type)) {
case datamessagestructures::Type::CameraData: {
datamessagestructures::CameraKeyframe kf(buffer);
kf._timestamp = calculateBufferedKeyframeTime(kf._timestamp);
const double convertedTimestamp = convertTimestamp(kf._timestamp);
OsEng.navigationHandler().keyframeNavigator().removeKeyframesAfter(
kf._timestamp
convertedTimestamp
);
interaction::KeyframeNavigator::CameraPose pose;
pose.focusNode = kf._focusNode;
pose.position = kf._position;
pose.rotation = kf._rotation;
pose.scale = kf._scale;
pose.followFocusNodeRotation = kf._followNodeRotation;
OsEng.navigationHandler().keyframeNavigator().addKeyframe(
kf._timestamp,
pose
);
OsEng.navigationHandler().keyframeNavigator().addKeyframe(convertedTimestamp,
pose);
break;
}
case datamessagestructures::Type::TimeData: {
datamessagestructures::TimeKeyframe kf(buffer);
kf._timestamp = calculateBufferedKeyframeTime(kf._timestamp);
case datamessagestructures::Type::TimelineData: {
const double now = OsEng.windowWrapper().applicationTime();
datamessagestructures::TimeTimeline timelineMessage(buffer);
OsEng.timeManager().removeKeyframesAfter(kf._timestamp);
Time time(kf._time);
time.setDeltaTime(kf._dt);
time.setPause(kf._paused);
time.setTimeJumped(kf._requiresTimeJump);
if (timelineMessage._clear) {
OsEng.timeManager().removeKeyframesAfter(
convertTimestamp(timestamp),
true
);
}
OsEng.timeManager().addKeyframe(kf._timestamp, time);
const std::vector<datamessagestructures::TimeKeyframe>& keyframesMessage =
timelineMessage._keyframes;
// If there are new keyframes incoming, make sure to erase all keyframes
// that already exist after the first new keyframe.
if (keyframesMessage.size() > 0) {
const double convertedTimestamp =
convertTimestamp(keyframesMessage[0]._timestamp);
OsEng.timeManager().removeKeyframesAfter(convertedTimestamp, true);
}
for (const datamessagestructures::TimeKeyframe& kfMessage : keyframesMessage)
{
TimeKeyframeData timeKeyframeData;
timeKeyframeData.delta = kfMessage._dt;
timeKeyframeData.pause = kfMessage._paused;
timeKeyframeData.time = kfMessage._time;
timeKeyframeData.jump = kfMessage._requiresTimeJump;
const double kfTimestamp = convertTimestamp(kfMessage._timestamp);
// We only need at least one keyframe before the current timestamp,
// so we can remove any other previous ones
if (kfTimestamp < now) {
OsEng.timeManager().removeKeyframesBefore(kfTimestamp, true);
}
OsEng.timeManager().addKeyframe(
kfTimestamp,
timeKeyframeData
);
}
break;
}
case datamessagestructures::Type::ScriptData: {
@@ -321,7 +361,8 @@ void ParallelPeer::dataMessageReceived(const std::vector<char>& message) {
}
}
void ParallelPeer::connectionStatusMessageReceived(const std::vector<char>& message) {
void ParallelPeer::connectionStatusMessageReceived(const std::vector<char>& message)
{
if (message.size() < 2 * sizeof(uint32_t)) {
LERROR("Malformed connection status message.");
return;
@@ -368,7 +409,8 @@ void ParallelPeer::connectionStatusMessageReceived(const std::vector<char>& mess
OsEng.timeManager().clearKeyframes();
}
void ParallelPeer::nConnectionsMessageReceived(const std::vector<char>& message) {
void ParallelPeer::nConnectionsMessageReceived(const std::vector<char>& message)
{
if (message.size() < sizeof(uint32_t)) {
LERROR("Malformed host info message.");
return;
@@ -409,6 +451,8 @@ void ParallelPeer::requestHostship() {
reinterpret_cast<char*>(&passwordHash),
reinterpret_cast<char*>(&passwordHash) + sizeof(uint64_t)
);
const double now = OsEng.windowWrapper().applicationTime();
_connection.sendMessage(ParallelConnection::Message(
ParallelConnection::MessageType::HostshipRequest,
buffer
@@ -416,6 +460,8 @@ void ParallelPeer::requestHostship() {
}
void ParallelPeer::resignHostship() {
const double now = OsEng.windowWrapper().applicationTime();
std::vector<char> buffer;
_connection.sendMessage(ParallelConnection::Message(
ParallelConnection::MessageType::HostshipResignation,
@@ -442,8 +488,10 @@ void ParallelPeer::sendScript(std::string script) {
std::vector<char> buffer;
sm.serialize(buffer);
double timestamp = OsEng.windowWrapper().applicationTime();
ParallelConnection::DataMessage message(
datamessagestructures::Type::ScriptData,
timestamp,
buffer
);
_connection.sendDataMessage(message);
@@ -464,19 +512,20 @@ void ParallelPeer::preSynchronization() {
_receiveBuffer.pop_front();
}
if (status() == ParallelConnection::Status::Host) {
if (OsEng.timeManager().time().timeJumped()) {
_timeJumped = true;
}
if (isHost()) {
double now = OsEng.windowWrapper().applicationTime();
if (_lastCameraKeyframeTimestamp + _cameraKeyframeInterval < now) {
sendCameraKeyframe();
_lastCameraKeyframeTimestamp = now;
}
if (_lastTimeKeyframeTimestamp + _timeKeyframeInterval < now) {
sendTimeKeyframe();
if (_timeTimelineChanged ||
_lastTimeKeyframeTimestamp + _timeKeyframeInterval < now)
{
sendTimeTimeline();
_lastTimeKeyframeTimestamp = now;
_timeJumped = false;
_timeTimelineChanged = false;
}
}
if (_shouldDisconnect) {
@@ -490,6 +539,21 @@ void ParallelPeer::setStatus(ParallelConnection::Status status) {
_timeJumped = true;
_connectionEvent->publish("statusChanged");
}
if (isHost()) {
OsEng.timeManager().addTimeJumpCallback([this]() {
_timeJumped = true;
});
OsEng.timeManager().addTimelineChangeCallback([this]() {
_timeTimelineChanged = true;
});
} else {
if (_timeJumpCallback != -1) {
OsEng.timeManager().removeTimeJumpCallback(_timeJumpCallback);
}
if (_timeTimelineChangeCallback != -1) {
OsEng.timeManager().removeTimelineChangeCallback(_timeTimelineChangeCallback);
}
}
}
ParallelConnection::Status ParallelPeer::status() {
@@ -554,39 +618,62 @@ void ParallelPeer::sendCameraKeyframe() {
// Fill the keyframe buffer
kf.serialize(buffer);
const double timestamp = OsEng.windowWrapper().applicationTime();
// Send message
_connection.sendDataMessage(ParallelConnection::DataMessage(
datamessagestructures::Type::CameraData,
timestamp,
buffer
));
}
void ParallelPeer::sendTimeKeyframe() {
void ParallelPeer::sendTimeTimeline() {
// Create a keyframe with current position and orientation of camera
datamessagestructures::TimeKeyframe kf;
const Timeline<TimeKeyframeData> timeline = OsEng.timeManager().timeline();
std::deque<Keyframe<TimeKeyframeData>> keyframes = timeline.keyframes();
const Time& time = OsEng.timeManager().time();
datamessagestructures::TimeTimeline timelineMessage;
timelineMessage._clear = true;
timelineMessage._keyframes.reserve(timeline.nKeyframes());
kf._dt = time.deltaTime();
kf._paused = time.paused();
kf._requiresTimeJump = _timeJumped;
kf._time = time.j2000Seconds();
// Case 1: Copy all keyframes from the native timeline
for (size_t i = 0; i < timeline.nKeyframes(); ++i) {
const Keyframe<TimeKeyframeData>& kf = keyframes.at(i);
// Timestamp as current runtime of OpenSpace instance
kf._timestamp = OsEng.windowWrapper().applicationTime();
datamessagestructures::TimeKeyframe kfMessage;
kfMessage._time = kf.data.time.j2000Seconds();
kfMessage._dt = kf.data.delta;
kfMessage._paused = kf.data.pause;
kfMessage._requiresTimeJump = kf.data.jump;
kfMessage._timestamp = kf.timestamp;
timelineMessage._keyframes.push_back(kfMessage);
}
// Case 2: Send one keyframe to represent the curernt time.
// If time jumped this frame, this is represented in the keyframe.
if (timeline.nKeyframes() == 0) {
datamessagestructures::TimeKeyframe kfMessage;
kfMessage._time = OsEng.timeManager().time().j2000Seconds();
kfMessage._dt = OsEng.timeManager().targetDeltaTime();
kfMessage._paused = OsEng.timeManager().isPaused();
kfMessage._timestamp = OsEng.windowWrapper().applicationTime();
kfMessage._requiresTimeJump = _timeJumped;
timelineMessage._keyframes.push_back(kfMessage);
}
// Create a buffer for the keyframe
std::vector<char> buffer;
// Fill the keyframe buffer
kf.serialize(buffer);
// Fill the timeline buffer
timelineMessage.serialize(buffer);
double timestamp = OsEng.windowWrapper().applicationTime();
// Send message
_connection.sendDataMessage(ParallelConnection::DataMessage(
datamessagestructures::Type::TimeData,
datamessagestructures::Type::TimelineData,
timestamp,
buffer
));
_timeJumped = false;
}
ghoul::Event<>& ParallelPeer::connectionEvent() {
+4 -4
View File
@@ -426,9 +426,12 @@ void RenderEngine::updateScene() {
_scene->updateInterpolations();
const Time& currentTime = OsEng.timeManager().time();
const Time& integrateFromTime = OsEng.timeManager().integrateFromTime();
_scene->update({
{ glm::dvec3(0.0), glm::dmat3(11.), 1.0 },
currentTime,
integrateFromTime,
_performanceManager != nullptr
});
@@ -684,10 +687,7 @@ void RenderEngine::renderDashboard() {
}
void RenderEngine::postDraw() {
Time& currentTime = OsEng.timeManager().time();
if (currentTime.timeJumped()) {
currentTime.setTimeJumped(false);
}
const Time& currentTime = OsEng.timeManager().time();
if (_shouldTakeScreenshot) {
// We only create the directory here, as we don't want to spam the users
+43 -35
View File
@@ -28,12 +28,15 @@
#include <openspace/engine/wrapper/windowwrapper.h>
#include <openspace/query/query.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scripting/lualibrary.h>
#include <ghoul/logging/logmanager.h>
#include <openspace/util/camera.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scene/scenelicensewriter.h>
#include <openspace/scene/sceneinitializer.h>
#include <openspace/scripting/lualibrary.h>
#include <openspace/util/camera.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/logging/logmanager.h>
#include <string>
#include <stack>
@@ -107,7 +110,7 @@ void Scene::unregisterNode(SceneGraphNode* node) {
// Just try to remove all properties; if the property doesn't exist, the
// removeInterpolation will not do anything
for (properties::Property* p : node->properties()) {
removeInterpolation(p);
removePropertyInterpolation(p);
}
removePropertySubOwner(node);
_dirtyNodeRegistry = true;
@@ -428,29 +431,29 @@ SceneGraphNode* Scene::loadNode(const ghoul::Dictionary& nodeDictionary) {
return rawNodePointer;
}
void Scene::addInterpolation(properties::Property* prop, float durationSeconds,
void Scene::addPropertyInterpolation(properties::Property* prop, float durationSeconds,
ghoul::EasingFunction easingFunction)
{
ghoul_precondition(prop != nullptr, "prop must not be nullptr");
ghoul_precondition(durationSeconds > 0.0, "durationSeconds must be positive");
ghoul_precondition(durationSeconds > 0.f, "durationSeconds must be positive");
ghoul_postcondition(
std::find_if(
_interpolationInfos.begin(),
_interpolationInfos.end(),
[prop](const InterpolationInfo& info) {
_propertyInterpolationInfos.begin(),
_propertyInterpolationInfos.end(),
[prop](const PropertyInterpolationInfo& info) {
return info.prop == prop && !info.isExpired;
}
) != _interpolationInfos.end(),
) != _propertyInterpolationInfos.end(),
"A new interpolation record exists for p that is not expired"
);
ghoul::EasingFunc<float> func =
easingFunction == ghoul::EasingFunction::Linear ?
(easingFunction == ghoul::EasingFunction::Linear) ?
nullptr :
ghoul::easingFunction<float>(easingFunction);
// First check if the current property already has an interpolation information
for (InterpolationInfo& info : _interpolationInfos) {
for (PropertyInterpolationInfo& info : _propertyInterpolationInfos) {
if (info.prop == prop) {
info.beginTime = std::chrono::steady_clock::now();
info.durationSeconds = durationSeconds;
@@ -461,36 +464,36 @@ void Scene::addInterpolation(properties::Property* prop, float durationSeconds,
}
}
InterpolationInfo i = {
PropertyInterpolationInfo i = {
prop,
std::chrono::steady_clock::now(),
durationSeconds,
func
};
_interpolationInfos.push_back(std::move(i));
_propertyInterpolationInfos.push_back(std::move(i));
}
void Scene::removeInterpolation(properties::Property* prop) {
void Scene::removePropertyInterpolation(properties::Property* prop) {
ghoul_precondition(prop != nullptr, "prop must not be nullptr");
ghoul_postcondition(
std::find_if(
_interpolationInfos.begin(),
_interpolationInfos.end(),
[prop](const InterpolationInfo& info) {
_propertyInterpolationInfos.begin(),
_propertyInterpolationInfos.end(),
[prop](const PropertyInterpolationInfo& info) {
return info.prop == prop;
}
) == _interpolationInfos.end(),
) == _propertyInterpolationInfos.end(),
"No interpolation record exists for prop"
);
_interpolationInfos.erase(
_propertyInterpolationInfos.erase(
std::remove_if(
_interpolationInfos.begin(),
_interpolationInfos.end(),
[prop](const InterpolationInfo& info) { return info.prop == prop; }
_propertyInterpolationInfos.begin(),
_propertyInterpolationInfos.end(),
[prop](const PropertyInterpolationInfo& info) { return info.prop == prop; }
),
_interpolationInfos.end()
_propertyInterpolationInfos.end()
);
}
@@ -499,14 +502,19 @@ void Scene::updateInterpolations() {
auto now = steady_clock::now();
for (InterpolationInfo& i : _interpolationInfos) {
// First, let's update the properties
for (PropertyInterpolationInfo& i : _propertyInterpolationInfos) {
long long usPassed = duration_cast<std::chrono::microseconds>(
now - i.beginTime
).count();
float t = static_cast<float>(
static_cast<double>(usPassed) /
static_cast<double>(i.durationSeconds * 1000000)
const float t = glm::clamp(
static_cast<float>(
static_cast<double>(usPassed) /
static_cast<double>(i.durationSeconds * 1000000)
),
0.f,
1.f
);
// @FRAGILE(abock): This method might crash if someone deleted the property
@@ -515,20 +523,20 @@ void Scene::updateInterpolations() {
// SceneGraphNodes. This is true in general, but if Propertys are
// created and destroyed often by the SceneGraphNode, this might
// become a problem.
i.prop->interpolateValue(glm::clamp(t, 0.f, 1.f), i.easingFunction);
i.prop->interpolateValue(t, i.easingFunction);
i.isExpired = (t >= 1.f);
i.isExpired = (t == 1.f);
}
_interpolationInfos.erase(
_propertyInterpolationInfos.erase(
std::remove_if(
_interpolationInfos.begin(),
_interpolationInfos.end(),
[](const InterpolationInfo& i) {
_propertyInterpolationInfos.begin(),
_propertyInterpolationInfos.end(),
[](const PropertyInterpolationInfo& i) {
return i.isExpired;
}
),
_interpolationInfos.end()
_propertyInterpolationInfos.end()
);
}
+4 -4
View File
@@ -107,12 +107,12 @@ void applyRegularExpression(lua_State* L, const std::string& regex,
foundMatching = true;
if (interpolationDuration == 0.0) {
OsEng.renderEngine().scene()->removeInterpolation(prop);
OsEng.renderEngine().scene()->removePropertyInterpolation(prop);
prop->setLuaValue(L);
}
else {
prop->setLuaInterpolationTarget(L);
OsEng.renderEngine().scene()->addInterpolation(
OsEng.renderEngine().scene()->addPropertyInterpolation(
prop,
static_cast<float>(interpolationDuration),
easingFunction
@@ -184,12 +184,12 @@ int setPropertyCall_single(properties::Property& prop, const std::string& uri,
}
else {
if (duration == 0.0) {
OsEng.renderEngine().scene()->removeInterpolation(&prop);
OsEng.renderEngine().scene()->removePropertyInterpolation(&prop);
prop.setLuaValue(L);
}
else {
prop.setLuaInterpolationTarget(L);
OsEng.renderEngine().scene()->addInterpolation(
OsEng.renderEngine().scene()->addPropertyInterpolation(
&prop,
static_cast<float>(duration),
eastingFunction
+43 -45
View File
@@ -44,7 +44,6 @@ double Time::convertTime(const std::string& time) {
Time::Time(double secondsJ2000) : _time(secondsJ2000) {}
Time Time::now() {
Time now;
time_t secondsSince1970;
@@ -58,45 +57,21 @@ Time Time::now() {
return now;
}
void Time::setTime(double j2000Seconds, bool requireJump) {
_time = j2000Seconds;
_timeJumped = requireJump;
void Time::setTime(double value) {
_time = value;
}
double Time::j2000Seconds() const {
return _time;
}
double Time::advanceTime(double tickTime) {
if (_timePaused) {
return _time;
}
else {
_time += _dt * tickTime;
}
double Time::advanceTime(double delta) {
_time += delta;
return _time;
}
void Time::setDeltaTime(double deltaT) {
_dt = deltaT;
}
double Time::deltaTime() const {
return _dt;
}
void Time::setPause(bool pause) {
_timePaused = pause;
}
bool Time::togglePause() {
_timePaused = !_timePaused;
return _timePaused;
}
void Time::setTime(std::string time, bool requireJump) {
void Time::setTime(std::string time) {
_time = SpiceManager::ref().ephemerisTimeFromDate(std::move(time));
_timeJumped = requireJump;
}
std::string Time::UTC() const {
@@ -152,22 +127,22 @@ std::string Time::ISO8601() const {
return datetime;
}
bool Time::timeJumped() const {
return _timeJumped;
}
void Time::setTimeJumped(bool jumped) {
_timeJumped = jumped;
}
bool Time::paused() const {
return _timePaused;
}
scripting::LuaLibrary Time::luaLibrary() {
return {
"time",
{
{
"setTime",
&luascriptfunctions::time_setTime,
{},
"{number, string}",
"Sets the current simulation time to the "
"specified value. If the parameter is a number, the value is the number "
"of seconds past the J2000 epoch. If it is a string, it has to be a "
"valid ISO 8601-like date string of the format YYYY-MM-DDTHH:MN:SS. "
"Note: providing time zone using the Z format is not supported. UTC is "
"assumed."
},
{
"setDeltaTime",
&luascriptfunctions::time_setDeltaTime,
@@ -200,10 +175,10 @@ scripting::LuaLibrary Time::luaLibrary() {
" and restoring it afterwards"
},
{
"setTime",
&luascriptfunctions::time_setTime,
"interpolateTime",
&luascriptfunctions::time_interpolateTime,
{},
"{number, string}",
"{number, string} [, number]",
"Sets the current simulation time to the "
"specified value. If the parameter is a number, the value is the number "
"of seconds past the J2000 epoch. If it is a string, it has to be a "
@@ -211,6 +186,29 @@ scripting::LuaLibrary Time::luaLibrary() {
"Note: providing time zone using the Z format is not supported. UTC is "
"assumed."
},
{
"interpolateDeltaTime",
&luascriptfunctions::time_interpolateDeltaTime,
{},
"number",
"Sets the amount of simulation time that happens "
"in one second of real time"
},
{
"interpolatePause",
&luascriptfunctions::time_interpolatePause,
{},
"bool",
"Pauses the simulation time or restores the delta time"
},
{
"interpolateTogglePause",
&luascriptfunctions::time_interpolateTogglePause,
{},
"",
"Toggles the pause function, i.e. temporarily setting the delta time to 0"
" and restoring it afterwards"
},
{
"currentTime",
&luascriptfunctions::time_currentTime,
+325 -40
View File
@@ -22,7 +22,12 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/util/timeconversion.h>
#include <ghoul/fmt.h>
#include <ghoul/misc/assert.h>
#include <ghoul/misc/misc.h>
@@ -37,40 +42,108 @@ namespace openspace::luascriptfunctions {
* Sets the delta time by calling the Time::setDeltaTime method
*/
int time_setDeltaTime(lua_State* L) {
const bool isFunction = (lua_isfunction(L, -1) != 0);
if (isFunction) {
// If the top of the stack is a function, it is ourself
const char* msg = lua_pushfstring(L, "method called without argument");
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
const bool isNumber = (lua_isnumber(L, 1) != 0);
if (!isNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const double newDeltaTime = lua_tonumber(L, 1);
OsEng.timeManager().setDeltaTime(newDeltaTime);
} else {
lua_settop(L, 0);
const char* msg = lua_pushfstring(L,
"Bad number of arguments. Expected 1 or 2.");
return ghoul::lua::luaError(L, fmt::format("bad argument ({})", msg));
}
const bool isNumber = (lua_isnumber(L, -1) != 0);
if (isNumber) {
double value = lua_tonumber(L, -1);
lua_pop(L, 1);
OsEng.timeManager().time().setDeltaTime(value);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* time_interpolateDeltaTime(number [, number]):
* Interpolates the delta time by calling the Time::interpolateDeltaTime method
* Same behaviour as setDeltaTime, but interpolates the delta time.
* If interpolationDuration is not provided, the interpolation time will be based on the
* `defaultDeltaTimeInterpolationDuration` property of the TimeManager.
*/
int time_interpolateDeltaTime(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 2) {
const bool deltaIsNumber = (lua_isnumber(L, 1) != 0);
if (!deltaIsNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const bool durationIsNumber = (lua_isnumber(L, 2) != 0);
if (!durationIsNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const double interpolationDuration = lua_tonumber(L, 2);
const double newDeltaTime = lua_tonumber(L, 1);
OsEng.timeManager().interpolateDeltaTime(newDeltaTime, interpolationDuration);
}
else if (nArguments == 1) {
const bool isNumber = (lua_isnumber(L, 1) != 0);
if (!isNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const double newDeltaTime = lua_tonumber(L, 1);
OsEng.timeManager().interpolateDeltaTime(newDeltaTime,
OsEng.timeManager().defaultDeltaTimeInterpolationDuration());
}
else {
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
lua_settop(L, 0);
const char* msg = lua_pushfstring(L,
"Bad number of arguments. Expected 1 or 2.");
return ghoul::lua::luaError(L, fmt::format("bad argument ({})", msg));
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* deltaTime():
* Returns the delta time by calling the Time::deltaTime method
*/
int time_deltaTime(lua_State* L) {
lua_pushnumber(L, OsEng.timeManager().time().deltaTime());
lua_pushnumber(L, OsEng.timeManager().deltaTime());
ghoul_assert(lua_gettop(L) == 1, "Incorrect number of items left on stack");
return 1;
}
@@ -78,28 +151,148 @@ int time_deltaTime(lua_State* L) {
/**
* \ingroup LuaScripts
* togglePause():
* Toggles a pause functionm i.e. setting the delta time to 0 and restoring it afterwards
* Toggles pause, i.e. setting the delta time to 0 and restoring it afterwards
*/
int time_togglePause(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::time_togglePause");
const int nArguments = lua_gettop(L);
OsEng.timeManager().time().togglePause();
if (nArguments == 0) {
OsEng.timeManager().setPause(!OsEng.timeManager().isPaused());
} else {
lua_settop(L, 0);
return luaL_error(
L,
"bad number of arguments, expected 0, got %i",
nArguments
);
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* interpolateTogglePause([interpolationDuration]):
* Same behaviour as togglePause, but with interpolation.
* If no interpolation duration is provided, the interpolation time will be based on the
* `defaultPauseInterpolationDuration` and `defaultUnpauseInterpolationDuration` properties
* of the TimeManager.
*/
int time_interpolateTogglePause(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
const bool isNumber = (lua_isnumber(L, 1) != 0);
if (!isNumber) {
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 1, msg);
}
const double interpolationDuration = lua_tonumber(L, 1);
OsEng.timeManager().interpolatePause(
!OsEng.timeManager().isPaused(),
interpolationDuration
);
}
else if (nArguments == 0) {
const bool pause = !OsEng.timeManager().isPaused();
OsEng.timeManager().interpolatePause(pause,
pause ?
OsEng.timeManager().defaultPauseInterpolationDuration() :
OsEng.timeManager().defaultUnpauseInterpolationDuration()
);
} else {
lua_settop(L, 0);
return luaL_error(
L,
"bad number of arguments, expected 0 or 1, got %i",
nArguments
);
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* togglePause():
* Toggles a pause functionm i.e. setting the delta time to 0 and restoring it afterwards
* Toggles a pause function i.e. setting the delta time to 0 and restoring it afterwards
*/
int time_setPause(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::time_setPause");
const int nArguments = lua_gettop(L);
bool pause = lua_toboolean(L, -1) == 1;
OsEng.timeManager().time().setPause(pause);
if (nArguments == 1) {
const bool pause = lua_toboolean(L, 1) == 1;
OsEng.timeManager().setPause(pause);
} else {
lua_settop(L, 0);
return luaL_error(
L,
"bad number of arguments, expected 1, got %i",
nArguments
);
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* interpolateTogglePause(bool [, interpolationDuration]):
* Same behaviour as setPause, but with interpolation.
* If no interpolation duration is provided, the interpolation time will be based on the
* `defaultPauseInterpolationDuration` and `defaultUnpauseInterpolationDuration` properties
* of the TimeManager.
*/
int time_interpolatePause(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 2) {
const bool isNumber = (lua_isnumber(L, 2) != 0);
if (!isNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const double interpolationDuration = lua_tonumber(L, 2);
const bool pause = lua_toboolean(L, 1) == 1;
OsEng.timeManager().interpolatePause(pause, interpolationDuration);
}
else if (nArguments == 1) {
const bool pause = lua_toboolean(L, 1) == 1;
OsEng.timeManager().interpolatePause(pause,
pause ?
OsEng.timeManager().defaultPauseInterpolationDuration() :
OsEng.timeManager().defaultUnpauseInterpolationDuration()
);
} else {
lua_settop(L, 0);
return luaL_error(
L,
"bad number of arguments, expected 1 or 2, got %i",
nArguments
);
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
@@ -122,25 +315,117 @@ int time_setTime(lua_State* L) {
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
const bool isNumber = (lua_isnumber(L, -1) != 0);
const bool isString = (lua_isstring(L, -1) != 0);
const bool isNumber = (lua_isnumber(L, 1) != 0);
const bool isString = (lua_isstring(L, 1) != 0);
if (!isNumber && !isString) {
const char* msg = lua_pushfstring(L, "%s or %s expected, got %s",
lua_typename(L, LUA_TNUMBER),
lua_typename(L, LUA_TSTRING), luaL_typename(L, -1));
const char* msg = lua_pushfstring(
L,
"%s or %s expected, got %s",
lua_typename(L, LUA_TNUMBER),
lua_typename(L, LUA_TSTRING),
luaL_typename(L, -1)
);
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
if (isNumber) {
double value = lua_tonumber(L, -1);
OsEng.timeManager().time().setTime(value);
return 0;
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
if (isNumber) {
double value = lua_tonumber(L, 1);
OsEng.timeManager().setTimeNextFrame(value);
return 0;
}
if (isString) {
const char* time = lua_tostring(L, 1);
OsEng.timeManager().setTimeNextFrame(Time::convertTime(time));
return 0;
}
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
} else {
return luaL_error(
L,
"bad number of arguments, expected 1 or 2, got %i",
nArguments
);
}
if (isString) {
const char* time = lua_tostring(L, -1);
OsEng.timeManager().time().setTime(time);
return 0;
return 0;
}
/**
* \ingroup LuaScripts
* interpolateTime({number, string} [, interpolationDuration]):
* Interpolates the simulation time to the passed value.
* Same behaviour as setTime, but interpolates time.
* If interpolationDuration is not provided, the interpolation time will be based on the
* `defaultTimeInterpolationDuration` property of the TimeManager.
*/
int time_interpolateTime(lua_State* L) {
const bool isFunction = (lua_isfunction(L, -1) != 0);
if (isFunction) {
// If the top of the stack is a function, it is ourself
const char* msg = lua_pushfstring(L, "method called without argument");
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
const bool isNumber = (lua_isnumber(L, 1) != 0);
const bool isString = (lua_isstring(L, 1) != 0);
if (!isNumber && !isString) {
const char* msg = lua_pushfstring(
L,
"%s or %s expected, got %s",
lua_typename(L, LUA_TNUMBER),
lua_typename(L, LUA_TSTRING),
luaL_typename(L, -1)
);
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
if (lua_gettop(L) == 1) {
if (isNumber) {
double value = lua_tonumber(L, 1);
OsEng.timeManager().interpolateTime(
value,
OsEng.timeManager().defaultTimeInterpolationDuration()
);
return 0;
}
if (isString) {
const char* time = lua_tostring(L, 1);
OsEng.timeManager().interpolateTime(
Time::convertTime(time),
OsEng.timeManager().defaultTimeInterpolationDuration()
);
return 0;
}
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
}
else {
int nArguments = lua_gettop(L);
if (nArguments != 2) {
return luaL_error(
L,
"bad number of arguments, expected 1 or 2, got %i",
nArguments
);
}
double targetTime;
if (lua_isnumber(L, 1)) {
targetTime = lua_tonumber(L, 1);
}
else {
targetTime = Time::convertTime(lua_tostring(L, 1));
}
const double duration = lua_tonumber(L, 2);
if (duration > 0) {
OsEng.timeManager().interpolateTime(targetTime, duration);
}
else {
OsEng.timeManager().setTimeNextFrame(targetTime);
}
}
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
+393 -96
View File
@@ -29,22 +29,84 @@
#include <openspace/network/parallelpeer.h>
#include <openspace/util/timeline.h>
#include <ghoul/logging/logmanager.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"
};
constexpr const char* _loggerCat = "TimeManager";
}
namespace openspace {
using datamessagestructures::TimeKeyframe;
TimeManager::TimeManager()
: properties::PropertyOwner({ "TimeManager" })
, _defaultTimeInterpolationDuration(DefaultTimeInterpolationDurationInfo, 2.0f, 0.f, 5.f)
, _defaultDeltaTimeInterpolationDuration(DefaultDeltaTimeInterpolationDurationInfo, 1.0f, 0.f, 5.f)
, _defaultPauseInterpolationDuration(DefaultPauseInterpolationDurationInfo, 0.5f, 0.f, 5.f)
, _defaultUnpauseInterpolationDuration(DefaultUnpauseInterpolationDurationInfo, 1.0f, 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 = OsEng.windowWrapper().applicationTime();
const bool pause = OsEng.timeManager().isPaused();
const TimeKeyframeData current = { time(), deltaTime(), false, false };
const TimeKeyframeData next = { targetTime, targetDeltaTime(), pause, false };
clearKeyframes();
addKeyframe(now, current);
addKeyframe(now + durationSeconds, next);
}
void TimeManager::preSynchronization(double dt) {
removeKeyframesBefore(_latestConsumedTimestamp);
if (_shouldSetTime) {
time().setTime(_timeNextFrame.j2000Seconds());
_shouldSetTime = false;
} else if (_timeline.nKeyframes() == 0) {
time().advanceTime(dt);
} else {
consumeKeyframes(dt);
}
progressTime(dt);
// Notify observers about time changes if any.
const double newTime = time().j2000Seconds();
const double newDeltaTime = time().deltaTime();
const double newDeltaTime = _deltaTime;
if (newTime != _lastTime) {
using K = const CallbackHandle;
using V = TimeChangeCallback;
@@ -59,133 +121,242 @@ void TimeManager::preSynchronization(double dt) {
it.second();
}
}
if (_timelineChanged) {
using K = const CallbackHandle;
using V = TimeChangeCallback;
for (const std::pair<K, V>& it : _timelineChangeCallbacks) {
it.second();
}
}
_lastTime = newTime;
_lastDeltaTime = newDeltaTime;
_timelineChanged = false;
}
void TimeManager::consumeKeyframes(double dt) {
const double now = OsEng.windowWrapper().applicationTime();
TimeKeyframeData TimeManager::interpolate(double applicationTime) {
const std::deque<Keyframe<TimeKeyframeData>>& keyframes = _timeline.keyframes();
const std::deque<Keyframe<Time>>& keyframes = _timeline.keyframes();
const auto firstFutureKeyframe = std::lower_bound(
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) {
_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 = OsEng.windowWrapper().applicationTime();
const std::deque<Keyframe<TimeKeyframeData>>& keyframes = _timeline.keyframes();
auto firstFutureKeyframe = std::lower_bound(
keyframes.begin(),
keyframes.end(),
now,
&compareKeyframeTimeWithTime
);
const bool consumingTimeJump = std::find_if(
keyframes.begin(),
firstFutureKeyframe,
[](const Keyframe<Time>& f) { return f.data.timeJumped(); }
) != firstFutureKeyframe;
const bool hasFutureKeyframes = firstFutureKeyframe != keyframes.end();
const bool hasPastKeyframes = firstFutureKeyframe != keyframes.begin();
if (firstFutureKeyframe == keyframes.end()) {
// All keyframes are in the past.
// Consume the latest one.
const Keyframe<Time>& current = keyframes.back();
const Time& currentTime = current.data;
time().setTime(currentTime.j2000Seconds(), consumingTimeJump);
time().setDeltaTime(currentTime.deltaTime());
time().setPause(currentTime.paused());
_latestConsumedTimestamp = current.timestamp;
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);
}
else {
const Keyframe<Time>& next = *firstFutureKeyframe;
const Time& nextTime = next.data;
if (firstFutureKeyframe != keyframes.begin()) {
const Keyframe<Time>& latest = *(firstFutureKeyframe - 1);
const Time& latestTime = latest.data;
// In case of unconsumed passed keyframes, let the last one
// determine whether the time should be paused or not.
// If there was a time jump or time is paused, apply it directly.
// Then consume the last keyframe.
time().setPause(latestTime.paused());
time().setTimeJumped(consumingTimeJump);
time().setDeltaTime(latestTime.deltaTime());
if (consumingTimeJump || latestTime.paused()) {
time().setTime(latestTime.j2000Seconds(), consumingTimeJump);
}
_latestConsumedTimestamp = latest.timestamp;
}
// Do not interpolate with time jumping keyframes.
// Instead, wait until their timestamp and apply them directly.
if (nextTime.timeJumped()) {
time().advanceTime(dt);
return;
}
if (time().paused()) {
return;
}
const double secondsOffTolerance = OsEng.parallelPeer().timeTolerance();
const double predictedTime = time().j2000Seconds() + time().deltaTime() *
(next.timestamp - now);
const bool withinTolerance = std::abs(predictedTime - nextTime.j2000Seconds()) <
std::abs(nextTime.deltaTime() * secondsOffTolerance);
if (nextTime.deltaTime() == time().deltaTime() && withinTolerance) {
time().advanceTime(dt);
return;
}
const double t0 = now - dt;
const double t1 = now;
const double t2 = next.timestamp;
const double parameter = (t1 - t0) / (t2 - t0);
const double y0 = time().j2000Seconds();
// double yPrime0 = time().deltaTime();
const double y2 = nextTime.j2000Seconds();
// double yPrime2 = nextTime.deltaTime();
const double y1 = (1 - parameter) * y0 + parameter * y2;
const double y1Prime = (y1 - y0) / dt;
time().setDeltaTime(y1Prime);
time().setTime(y1, false);
if (hasPastKeyframes) {
_latestConsumedTimestamp = lastPastKeyframe->timestamp;
}
}
void TimeManager::addKeyframe(double timestamp, Time keyframeTime) {
_timeline.addKeyframe(timestamp, std::move(keyframeTime));
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 {
interpolatedTime,
interpolatedDeltaTime,
past.data.pause,
past.data.jump
};
return data;
}
void TimeManager::removeKeyframesAfter(double timestamp) {
_timeline.removeKeyframesAfter(timestamp);
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::removeKeyframesBefore(double timestamp) {
_timeline.removeKeyframesBefore(timestamp);
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);
}
size_t TimeManager::nKeyframes() const {
return _timeline.nKeyframes();
}
Time& TimeManager::time() {
const Time& TimeManager::time() const {
return _currentTime;
}
const Time& TimeManager::integrateFromTime() const {
return _integrateFromTime;
}
const Timeline<TimeKeyframeData>& TimeManager::timeline() {
return _timeline;
}
std::vector<Syncable*> TimeManager::getSyncables() {
return { &_currentTime };
return { &_currentTime, &_integrateFromTime };
}
TimeManager::CallbackHandle TimeManager::addTimeChangeCallback(TimeChangeCallback cb) {
@@ -201,6 +372,19 @@ TimeManager::CallbackHandle TimeManager::addDeltaTimeChangeCallback(TimeChangeCa
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(),
@@ -235,4 +419,117 @@ void TimeManager::removeDeltaTimeChangeCallback(CallbackHandle handle) {
_deltaTimeChangeCallbacks.erase(it);
}
void TimeManager::removeTimeJumpCallback(CallbackHandle handle) {
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) {
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;
}
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 = OsEng.windowWrapper().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);
}
void TimeManager::setPause(bool pause) {
interpolatePause(pause, 0);
}
void TimeManager::interpolatePause(bool pause, double interpolationDuration) {
if (pause == _timePaused) {
return;
}
const double now = OsEng.windowWrapper().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