mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-05-03 17:30:04 -05:00
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:
@@ -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());
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user