feature/time-refactor (#294)

- Change Time class to become a non-singleton
- Move ownership of the current time to TimeManager(instead of singleton access).
- Store the Time as a Syncable in TimeManager instead of representing all member variables of Time as Syncables.
- Pass a Time object around in the update/render methods, so that renderables don't have to query the OpenSpaceEngine to know if time is paused or if it jumped.
- Introduce Timeline and Keyframe classes
- Make use of Timelineand Keyframeclasses in KeyframeInteractionMode and TimeManager
- Added basic unit tests for Timelineand Keyframe

Future work: Add interpolation schemes for keyframes. Possibly use keyframes+interpolation feature to tween/morph properties, or figure out if this should be a separate mechanism.
This commit is contained in:
Emil Axelsson
2017-05-22 14:01:08 +02:00
committed by GitHub
parent 3a5635d2a8
commit 752081d31b
51 changed files with 745 additions and 453 deletions

View File

@@ -65,7 +65,6 @@ public:
// Interaction mode setters
void setCameraStateFromDictionary(const ghoul::Dictionary& cameraDict);
void setInteractionMode(const std::string& interactionModeKey);
InteractionMode* interactionMode();
void goToChunk(int x, int y, int level);
@@ -73,9 +72,10 @@ public:
void resetKeyBindings();
void addKeyframe(const datamessagestructures::CameraKeyframe &kf);
void addKeyframe(double timestamp, KeyframeInteractionMode::CameraPose pose);
void removeKeyframesAfter(double timestamp);
void clearKeyframes();
size_t nKeyframes() const;
const std::vector<datamessagestructures::CameraKeyframe>& keyframes() const;
void bindKeyLocal(
@@ -134,7 +134,7 @@ private:
std::string generateJson() const override;
void setInteractionMode(std::shared_ptr<InteractionMode> interactionMode);
void setInteractionMode(InteractionMode* interactionMode);
bool _cameraUpdatedFromScript = false;
@@ -143,14 +143,18 @@ private:
std::unique_ptr<InputState> _inputState;
Camera* _camera;
std::shared_ptr<InteractionMode> _currentInteractionMode;
InteractionMode* _currentInteractionMode;
std::map<std::string, std::shared_ptr<InteractionMode>> _interactionModes;
std::shared_ptr<OrbitalInteractionMode::MouseStates> _mouseStates;
std::unique_ptr<OrbitalInteractionMode> _orbitalInteractionMode;
std::unique_ptr<GlobeBrowsingInteractionMode> _globeBrowsingInteractionMode;
std::unique_ptr<KeyframeInteractionMode> _keyframeInteractionMode;
// Properties
properties::StringProperty _origin;
properties::OptionProperty _interactionModeOption;
properties::BoolProperty _rotationalFriction;
properties::BoolProperty _horizontalFriction;
properties::BoolProperty _verticalFriction;

View File

@@ -28,6 +28,7 @@
#include <openspace/network/parallelconnection.h>
#include <openspace/util/mouse.h>
#include <openspace/util/keys.h>
#include <openspace/util/timeline.h>
#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED
#include <modules/globebrowsing/tile/tileindex.h>
@@ -80,14 +81,6 @@ namespace interaction {
void mousePositionCallback(double mouseX, double mouseY);
void mouseScrollWheelCallback(double mouseScrollDelta);
// Mutators
void addKeyframe(const datamessagestructures::CameraKeyframe &kf);
void removeKeyframesAfter(double timestamp);
void clearKeyframes();
void clearOldKeyframes();
static bool compareKeyframeTimes(const datamessagestructures::CameraKeyframe& a, const datamessagestructures::CameraKeyframe& b);
// Accessors
const std::list<std::pair<Key, KeyModifier> >& getPressedKeys() const;
const std::list<MouseButton>& getPressedMouseButtons() const;
@@ -104,9 +97,6 @@ namespace interaction {
std::list<MouseButton> _mouseButtonsDown;
glm::dvec2 _mousePosition;
double _mouseScrollDelta;
// Remote input via keyframes
std::vector<datamessagestructures::CameraKeyframe> _keyframes;
};
@@ -194,16 +184,23 @@ protected:
class KeyframeInteractionMode : public InteractionMode
{
public:
struct CameraPose {
glm::dvec3 position;
glm::quat rotation;
std::string focusNode;
bool followFocusNodeRotation;
};
KeyframeInteractionMode();
~KeyframeInteractionMode();
virtual void updateMouseStatesFromInput(const InputState& inputState, double deltaTime);
virtual void updateCameraStateFromMouseStates(Camera& camera, double deltaTime);
bool followingNodeRotation() const override;
Timeline<CameraPose>& timeline();
private:
std::vector<datamessagestructures::CameraKeyframe> _keyframes;
double _currentKeyframeTime;
Timeline<CameraPose> _cameraPoseTimeline;
};
class GlobeBrowsingInteractionMode;

View File

@@ -55,8 +55,6 @@ namespace openspace {
* The synchronization of the simulation time requires
*/
class SyncBuffer;
class Time {
public:
/**
@@ -90,20 +88,6 @@ public:
static Time now();
/**
* Returns the reference to the Time singleton object.
* \return The reference to the Time singleton object
* \pre The Time singleton must have been initialized
*/
static Time& ref();
/**
* Returns <code>true</code> if the singleton has been successfully initialized,
* <code>false</code> otherwise
* \return <code>true</code> if the singleton has been successfully initialized,
* <code>false</code> otherwise
*/
static bool isInitialized();
/**
* Sets the current time to the specified value in seconds past the J2000 epoch. This
@@ -210,15 +194,10 @@ public:
*/
static scripting::LuaLibrary luaLibrary();
std::vector<Syncable*> getSyncables();
private:
static Time* _instance; ///< The singleton instance
SyncData<double> _time;
SyncData<double> _dt;
SyncData<bool> _timeJumped;
double _time;
double _dt;
bool _timeJumped;
bool _timePaused = false;
};

View File

@@ -0,0 +1,96 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2017 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_CORE___TIMELINE___H__
#define __OPENSPACE_CORE___TIMELINE___H__
#include <algorithm>
#include <deque>
#include <cstddef>
namespace openspace {
/**
* Base class for keyframes
*/
struct KeyframeBase {
size_t id;
double timestamp;
};
/**
* Templated class for keyframes containing data
*/
template <typename T>
struct Keyframe : public KeyframeBase {
Keyframe(size_t i, double t, T p)
: KeyframeBase{i, t}
, data(p)
{}
T data;
};
/**
* Templated class for timelines
*/
template <typename T>
class Timeline {
public:
Timeline();
virtual ~Timeline();
void addKeyframe(double time, T data);
void clearKeyframes();
void removeKeyframe(size_t id);
void removeKeyframesBefore(double timestamp, bool inclusive = false);
void removeKeyframesAfter(double timestamp, bool inclusive = false);
void removeKeyframesBetween(double begin, double end, bool inclusiveBegin = false, bool inclusiveEnd = false);
size_t nKeyframes() const;
const Keyframe<T>* firstKeyframeAfter(double timestamp, bool inclusive = false) const;
const Keyframe<T>* lastKeyframeBefore(double timestamp, bool inclusive = false) const;
const std::deque<Keyframe<T>>& keyframes() const;
private:
size_t _nextKeyframeId;
std::deque<Keyframe<T>> _keyframes;
};
/**
* Return true if the timestamp of a is smaller the timestamp of b.
*/
bool compareKeyframeTimes(const KeyframeBase& a, const KeyframeBase& b);
/**
* Return true if a is smaller than the timestamp of b.
*/
bool compareTimeWithKeyframeTime(double a, const KeyframeBase& b);
/**
* Return true if the timestamp of a is smaller than b.
*/
bool compareKeyframeTimeWithTime(const KeyframeBase& a, double b);
} // namespace openspace
#include <openspace/util/timeline.inl>;
#endif // __OPENSPACE_CORE___TIMELINE___H__

View File

@@ -0,0 +1,121 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2017 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
namespace openspace {
template <typename T>
Timeline<T>::Timeline()
: _nextKeyframeId(1)
{}
template <typename T>
Timeline<T>::~Timeline() {}
template <typename T>
void Timeline<T>::addKeyframe(double timestamp, T data) {
Keyframe<T> keyframe(++_nextKeyframeId, timestamp, data);
auto iter = std::upper_bound(_keyframes.begin(), _keyframes.end(), keyframe, &compareKeyframeTimes);
_keyframes.insert(iter, keyframe);
}
template <typename T>
void Timeline<T>::removeKeyframesAfter(double timestamp, bool inclusive) {
auto iter = inclusive
? std::lower_bound(_keyframes.begin(), _keyframes.end(), timestamp, &compareKeyframeTimeWithTime)
: std::upper_bound(_keyframes.begin(), _keyframes.end(), timestamp, &compareTimeWithKeyframeTime);
_keyframes.erase(iter, _keyframes.end());
}
template <typename T>
void Timeline<T>::removeKeyframesBefore(double timestamp, bool inclusive) {
auto iter = inclusive
? std::upper_bound(_keyframes.begin(), _keyframes.end(), timestamp, &compareTimeWithKeyframeTime)
: std::lower_bound(_keyframes.begin(), _keyframes.end(), timestamp, &compareKeyframeTimeWithTime);
_keyframes.erase(_keyframes.begin(), iter);
}
template <typename T>
void Timeline<T>::removeKeyframesBetween(double begin, double end, bool inclusiveBegin, bool inclusiveEnd) {
auto beginIter = inclusiveBegin
? std::lower_bound(_keyframes.begin(), _keyframes.end(), begin, &compareKeyframeTimeWithTime)
: std::upper_bound(_keyframes.begin(), _keyframes.end(), begin, &compareTimeWithKeyframeTime);
auto endIter = inclusiveEnd
? std::upper_bound(beginIter, _keyframes.end(), end, &compareTimeWithKeyframeTime)
: std::lower_bound(beginIter, _keyframes.end(), end, &compareKeyframeTimeWithTime);
_keyframes.erase(beginIter, endIter);
}
template <typename T>
void Timeline<T>::clearKeyframes() {
_keyframes.clear();
}
template <typename T>
void Timeline<T>::removeKeyframe(size_t id) {
_keyframes.erase(std::remove_if(_keyframes.begin(), _keyframes.end(), [id] (Keyframe<T> keyframe) {
return keyframe.id == id;
}), _keyframes.end());
}
template <typename T>
size_t Timeline<T>::nKeyframes() const {
return _keyframes.size();
}
template <typename T>
const Keyframe<T>* Timeline<T>::firstKeyframeAfter(double timestamp, bool inclusive) const {
auto it = inclusive
? std::lower_bound(_keyframes.begin(), _keyframes.end(), timestamp, &compareKeyframeTimeWithTime)
: std::upper_bound(_keyframes.begin(), _keyframes.end(), timestamp, &compareTimeWithKeyframeTime);
if (it == _keyframes.end()) {
return nullptr;
}
return &(*it);
}
template <typename T>
const Keyframe<T>* Timeline<T>::lastKeyframeBefore(double timestamp, bool inclusive) const {
auto it = inclusive
? std::upper_bound(_keyframes.begin(), _keyframes.end(), timestamp, &compareTimeWithKeyframeTime)
: std::lower_bound(_keyframes.begin(), _keyframes.end(), timestamp, &compareKeyframeTimeWithTime);
if (it == _keyframes.begin()) {
return nullptr;
}
it--;
return &(*it);
}
template<typename T>
const std::deque<Keyframe<T>>& Timeline<T>::keyframes() const {
return _keyframes;
}
}

View File

@@ -27,24 +27,26 @@
#include <vector>
#include <deque>
#include <openspace/network/messagestructures.h>
#include <openspace/util/timeline.h>
#include <openspace/util/time.h>
#include <openspace/util/syncdata.h>
namespace openspace {
class TimeManager {
public:
Time& time();
std::vector<Syncable*> getSyncables();
void preSynchronization(double dt);
void addKeyframe(const datamessagestructures::TimeKeyframe& kf);
void addKeyframe(double timestamp, Time kf);
void removeKeyframesBefore(double timestamp);
void removeKeyframesAfter(double timestamp);
void clearKeyframes();
const std::deque<datamessagestructures::TimeKeyframe>& keyframes() const;
size_t nKeyframes() const;
private:
Timeline<Time> _timeline;
SyncData<Time> _currentTime;
void consumeKeyframes(double dt);
std::deque<datamessagestructures::TimeKeyframe> _keyframes;
static bool compareKeyframeTimes(
const datamessagestructures::TimeKeyframe& a,
const datamessagestructures::TimeKeyframe& b);
double _latestConsumedTimestamp;
};

View File

@@ -27,6 +27,7 @@
#include <openspace/util/camera.h>
#include <openspace/util/powerscaledcoordinate.h>
#include <openspace/util/time.h>
namespace openspace {
@@ -44,11 +45,8 @@ struct TransformData {
struct UpdateData {
TransformData modelTransform;
double time;
double delta;
bool timePaused;
bool isTimeJump;
bool doPerformanceMeasurement;
const Time time;
const bool doPerformanceMeasurement;
};
@@ -57,6 +55,7 @@ struct RenderData {
// psc position to be removed in favor of the double precision position defined in
// the translation in transform.
psc position;
const Time time;
bool doPerformanceMeasurement;
int renderBinMask;
TransformData modelTransform;

View File

@@ -230,7 +230,7 @@ void RenderableSphericalGrid::render(const RenderData& data){
}
void RenderableSphericalGrid::update(const UpdateData& data) {
_parentMatrix = SpiceManager::ref().positionTransformMatrix("IAU_JUPITER", "GALACTIC", data.time);
_parentMatrix = SpiceManager::ref().positionTransformMatrix("IAU_JUPITER", "GALACTIC", data.time.j2000Seconds());
}
}

View File

@@ -195,7 +195,7 @@ void RenderableTrailOrbit::update(const UpdateData& data) {
// 2
// Write the current location into the floating position
glm::vec3 p = _translation->position(data.time);
glm::vec3 p = _translation->position(data.time.j2000Seconds());
_vertexArray[_primaryRenderInformation.first] = { p.x, p.y, p.z };
glBindVertexArray(_primaryRenderInformation._vaoID);
@@ -316,22 +316,22 @@ RenderableTrailOrbit::UpdateReport RenderableTrailOrbit::updateTrails(
{
// If we are doing a time jump, it is in general faster to recalculate everything
// than to only update parts of the array
if (data.isTimeJump) {
if (data.time.timeJumped()) {
_needsFullSweep = true;
}
if (_needsFullSweep) {
fullSweep(data.time);
fullSweep(data.time.j2000Seconds());
return { true, UpdateReport::All } ;
}
// When time stands still (at the iron hill), we don't need to perform any work
if (data.delta == 0.0) {
if (data.time.deltaTime() == 0.0) {
return { false, 0 };
}
double secondsPerPoint = _period / (_resolution - 1);
// How much time has passed since the last permanent point
double delta = data.time - _lastPointTime;
double delta = data.time.j2000Seconds() - _lastPointTime;
// We'd like to test for equality with 0 here, but due to rounding issues, we won't
// get there. If this check is not here, we will trigger the positive or negative
@@ -357,7 +357,7 @@ RenderableTrailOrbit::UpdateReport RenderableTrailOrbit::updateTrails(
// If we would need to generate more new points than there are total points in the
// array, it is faster to regenerate the entire array
if (nNewPoints >= _resolution) {
fullSweep(data.time);
fullSweep(data.time.j2000Seconds());
return { true, UpdateReport::All };
}
@@ -392,7 +392,7 @@ RenderableTrailOrbit::UpdateReport RenderableTrailOrbit::updateTrails(
// If we would need to generate more new points than there are total points in the
// array, it is faster to regenerate the entire array
if (nNewPoints >= _resolution) {
fullSweep(data.time);
fullSweep(data.time.j2000Seconds());
return { true, UpdateReport::All };
}

View File

@@ -255,7 +255,7 @@ void RenderableTrailTrajectory::update(const UpdateData& data) {
// If only trail so far should be rendered, we need to find the corresponding time
// in the array and only render it until then
_primaryRenderInformation.first = 0;
double t = (data.time - _start) / (_end - _start);
double t = (data.time.j2000Seconds() - _start) / (_end - _start);
_primaryRenderInformation.count = std::min(
static_cast<GLsizei>(ceil(_vertexArray.size() * t)),
static_cast<GLsizei>(_vertexArray.size() - 1)
@@ -264,7 +264,7 @@ void RenderableTrailTrajectory::update(const UpdateData& data) {
// If we are inside the valid time, we additionally want to draw a line from the last
// correct point to the current location of the object
if (data.time >= _start && data.time <= _end && !_renderFullTrail) {
if (data.time.j2000Seconds() >= _start && data.time.j2000Seconds() <= _end && !_renderFullTrail) {
// Copy the last valid location
glm::dvec3 v0(
_vertexArray[_primaryRenderInformation.count - 1].x,
@@ -273,7 +273,7 @@ void RenderableTrailTrajectory::update(const UpdateData& data) {
);
// And get the current location of the object
glm::dvec3 p = _translation->position(data.time);
glm::dvec3 p = _translation->position(data.time.j2000Seconds());
glm::dvec3 v1 = { p.x, p.y, p.z };
// Comptue the difference between the points in double precision

View File

@@ -180,7 +180,7 @@ void RenderableGlobe::render(const RenderData& data) {
}
void RenderableGlobe::update(const UpdateData& data) {
_time = data.time;
_time = data.time.j2000Seconds();
_distanceSwitch.update(data);
glm::dmat4 translation =

View File

@@ -28,8 +28,10 @@
#ifdef GLOBEBROWSING_USE_GDAL
#include <modules/globebrowsing/tile/tileprovider/tileprovider.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/util/time.h>
#include <openspace/util/timemanager.h>
#include <openspace/util/timerange.h>
#include <ghoul/misc/dictionary.h>
@@ -197,7 +199,7 @@ public:
typedef std::string TimeKey;
std::shared_ptr<TileProvider> getTileProvider(Time t = Time::ref());
std::shared_ptr<TileProvider> getTileProvider(Time t = OsEng.timeManager().time());
std::shared_ptr<TileProvider> getTileProvider(TimeKey timekey);
private:

View File

@@ -34,6 +34,9 @@
#include <openspace/properties/triggerproperty.h>
#include <openspace/rendering/renderable.h>
#include <openspace/rendering/transferfunction.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/util/timemanager.h>
#include <openspace/util/time.h>
#include <modules/iswa/rendering/iswabasegroup.h>
@@ -115,7 +118,7 @@ protected:
* this should be the data file.
* @return true if update was successfull
*/
virtual bool downloadTextureResource(double timestamp = Time::ref().j2000Seconds()) = 0;
virtual bool downloadTextureResource(double timestamp = OsEng.timeManager().time().j2000Seconds()) = 0;
virtual bool readyToRender() const = 0;
/**
* should set all uniforms needed to render

View File

@@ -35,6 +35,8 @@
#ifdef OPENSPACE_MODULE_KAMELEON_ENABLED
#include <ccmc/Kameleon.h>
#endif
#include <openspace/engine/openspaceengine.h>
#include <openspace/engine/downloadmanager.h>
#include <modules/kameleon/include/kameleonwrapper.h>
#include <openspace/rendering/renderable.h>
@@ -43,6 +45,7 @@
#include <openspace/util/spicemanager.h>
#include <openspace/properties/selectionproperty.h>
#include <modules/iswa/ext/json/json.hpp>
#include <openspace/util/timemanager.h>
#include <openspace/util/time.h>
@@ -92,7 +95,7 @@ public:
std::future<DownloadManager::MemoryFile> fetchImageCygnet(int id, double timestamp);
std::future<DownloadManager::MemoryFile> fetchDataCygnet(int id, double timestamp);
std::string iswaUrl(int id, double timestamp = Time::ref().j2000Seconds(), std::string type = "image");
std::string iswaUrl(int id, double timestamp = OsEng.timeManager().time().j2000Seconds(), std::string type = "image");
std::shared_ptr<IswaBaseGroup> iswaGroup(std::string name);

View File

@@ -528,8 +528,8 @@ std::vector<unsigned int> RenderableMultiresVolume::getBuffers() {
}*/
void RenderableMultiresVolume::update(const UpdateData& data) {
_timestep++;
_time = data.time;
_timestep++;
_time = data.time.j2000Seconds();
if (_gatheringStats) {
std::chrono::system_clock::time_point frameEnd = std::chrono::system_clock::now();

View File

@@ -240,10 +240,10 @@ void RenderableCrawlingLine::update(const UpdateData& data) {
_source,
//"ECLIPJ2000",
"GALACTIC",
data.time
data.time.j2000Seconds()
);
glm::dmat3 tm = SpiceManager::ref().frameTransformationMatrix(_instrumentName, "ECLIPJ2000", data.time);
glm::dmat3 tm = SpiceManager::ref().frameTransformationMatrix(_instrumentName, "ECLIPJ2000", data.time.j2000Seconds());
//_positions[SourcePosition] = { 0.f, 0.f, 0.f, 0.f };

View File

@@ -409,7 +409,7 @@ void RenderableFov::computeIntercepts(const UpdateData& data, const std::string&
if (!isInFov) {
// If the target is not in the field of view, we don't need to perform any
// surface intercepts
glm::vec3 o = orthogonalProjection(bound, data.time, target);
glm::vec3 o = orthogonalProjection(bound, data.time.j2000Seconds(), target);
second = {
{ o.x, o.y, o.z },
@@ -426,7 +426,7 @@ void RenderableFov::computeIntercepts(const UpdateData& data, const std::string&
_instrument.name,
ref.first,
_instrument.aberrationCorrection,
data.time,
data.time.j2000Seconds(),
bound
);
@@ -443,7 +443,7 @@ void RenderableFov::computeIntercepts(const UpdateData& data, const std::string&
r.surfaceVector = SpiceManager::ref().frameTransformationMatrix(
ref.first,
_instrument.referenceFrame,
data.time
data.time.j2000Seconds()
) * r.surfaceVector;
}
@@ -460,7 +460,7 @@ void RenderableFov::computeIntercepts(const UpdateData& data, const std::string&
}
else {
// This point did not intersect the target though others did
glm::vec3 o = orthogonalProjection(bound, data.time, target);
glm::vec3 o = orthogonalProjection(bound, data.time.j2000Seconds(), target);
second = {
{ o.x, o.y, o.z },
RenderInformation::VertexColorTypeInFieldOfView
@@ -516,7 +516,7 @@ void RenderableFov::computeIntercepts(const UpdateData& data, const std::string&
_instrument.name,
makeBodyFixedReferenceFrame(_instrument.referenceFrame).first,
_instrument.aberrationCorrection,
data.time,
data.time.j2000Seconds(),
probe
).interceptFound;
};
@@ -531,7 +531,7 @@ void RenderableFov::computeIntercepts(const UpdateData& data, const std::string&
_instrument.name,
ref.first,
_instrument.aberrationCorrection,
data.time,
data.time.j2000Seconds(),
probe
);
@@ -539,7 +539,7 @@ void RenderableFov::computeIntercepts(const UpdateData& data, const std::string&
r.surfaceVector = SpiceManager::ref().frameTransformationMatrix(
ref.first,
_instrument.referenceFrame,
data.time
data.time.j2000Seconds()
) * r.surfaceVector;
}
@@ -561,7 +561,7 @@ void RenderableFov::computeIntercepts(const UpdateData& data, const std::string&
};
}
else {
const glm::vec3 o = orthogonalProjection(tBound, data.time, target);
const glm::vec3 o = orthogonalProjection(tBound, data.time.j2000Seconds(), target);
_orthogonalPlane.data[indexForBounds(i) + m] = {
{ o.x, o.y, o.z },
@@ -1003,8 +1003,8 @@ void RenderableFov::update(const UpdateData& data) {
_drawFOV = ImageSequencer::ref().instrumentActive(_instrument.name);
}
if (_drawFOV && !data.timePaused) {
auto t = determineTarget(data.time);
if (_drawFOV && !data.time.paused()) {
auto t = determineTarget(data.time.j2000Seconds());
std::string target = t.first;
bool inFOV = t.second;
@@ -1012,7 +1012,7 @@ void RenderableFov::update(const UpdateData& data) {
updateGPU();
double t2 = (ImageSequencer::ref().getNextCaptureTime());
double diff = (t2 - data.time);
double diff = (t2 - data.time.j2000Seconds());
_interpolationTime = 0.0;
float interpolationStart = 7.0; //seconds before
if (diff <= interpolationStart) {

View File

@@ -277,7 +277,7 @@ void RenderableModelProjection::update(const UpdateData& data) {
if (_depthFboProgramObject->isDirty())
_depthFboProgramObject->rebuildFromFile();
_time = data.time;
_time = data.time.j2000Seconds();
if (openspace::ImageSequencer::ref().isReady()) {
openspace::ImageSequencer::ref().updateSequencer(_time);

View File

@@ -159,7 +159,7 @@ void RenderablePlaneProjection::render(const RenderData& data) {
}
void RenderablePlaneProjection::update(const UpdateData& data) {
double time = data.time;
double time = data.time.j2000Seconds();
const Image img = openspace::ImageSequencer::ref().getLatestImageForInstrument(_instrument);
if (img.path == "")

View File

@@ -32,7 +32,6 @@
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/util/factorymanager.h>
#include <openspace/util/time.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/io/texture/texturereader.h>
@@ -465,7 +464,7 @@ void RenderablePlanetProjection::update(const UpdateData& data) {
_projectionComponent.update();
_time = Time::ref().j2000Seconds();
_time = data.time.j2000Seconds();
_capture = false;
if (openspace::ImageSequencer::ref().isReady()){

View File

@@ -137,11 +137,11 @@ void RenderableShadowCylinder::render(const RenderData& data){
}
void RenderableShadowCylinder::update(const UpdateData& data) {
_stateMatrix = SpiceManager::ref().positionTransformMatrix(_bodyFrame, _mainFrame, data.time);
_stateMatrix = SpiceManager::ref().positionTransformMatrix(_bodyFrame, _mainFrame, data.time.j2000Seconds());
if (_shader->isDirty()) {
_shader->rebuildFromFile();
}
createCylinder(data.time);
createCylinder(data.time.j2000Seconds());
}
glm::vec4 psc_addition(glm::vec4 v1, glm::vec4 v2) {

View File

@@ -28,6 +28,8 @@
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/filesystem/directory.h>
#include <openspace/util/time.h>
#include <openspace/util/timemanager.h>
#include <openspace/engine/openspaceengine.h>
#include <ghoul/filesystem/cachemanager.h>
#include <modules/newhorizons/util/decoder.h>
@@ -83,14 +85,10 @@ bool ImageSequencer::isReady() {
return _hasData;
}
void ImageSequencer::updateSequencer(double time) {
if (Time::ref().timeJumped() && Time::ref().deltaTime() == 0) {
Time::ref().setDeltaTime(0.1);
} // Time is not properly updated when time jump with dt = 0
if (_currentTime != time) {
void ImageSequencer::updateSequencer(const Time& time) {
if (_currentTime != time.j2000Seconds()) {
_previousTime = _currentTime;
_currentTime = time;
_currentTime = time.j2000Seconds();
}
}
@@ -279,7 +277,7 @@ bool ImageSequencer::getImagePaths(std::vector<Image>& captures,
// check if this instance is either in range or
// a valid candidate to recieve data
if (!instrumentActive(instrumentRequest) && !Time::ref().timeJumped()) return false;
if (!instrumentActive(instrumentRequest) && !OsEng.timeManager().time().timeJumped()) return false;
//if (!Time::ref().timeJumped() && projectee == getCurrentTarget().second)

View File

@@ -34,6 +34,7 @@
namespace openspace {
class Time;
class SequenceParser;
/**
@@ -79,7 +80,7 @@ public:
* Updates sequencer with current <code>time</code>. This is used internally for keeping
* track of both current simulation time and the time of the previously rendered frame.
*/
void updateSequencer(double time);
void updateSequencer(const Time& time);
/**
* Runs parser and recieves the datastructures filled by it.
* \see SequenceParser

View File

@@ -82,11 +82,11 @@ void GuiParallelComponent::renderClientWithHost() {
ImGui::Text("%s", connectionInfo.c_str());
renderClientCommon();
const std::deque<datamessagestructures::TimeKeyframe> timeKeyframes = OsEng.timeManager().keyframes();
const std::vector<datamessagestructures::CameraKeyframe> cameraKeyframes = OsEng.interactionHandler().keyframes();
const size_t nTimeKeyframes = OsEng.timeManager().nKeyframes();
const size_t nCameraKeyframes = OsEng.interactionHandler().nKeyframes();
std::string timeKeyframeInfo = "TimeKeyframes : " + std::to_string(timeKeyframes.size());
std::string cameraKeyframeInfo = "CameraKeyframes : " + std::to_string(cameraKeyframes.size());
std::string timeKeyframeInfo = "TimeKeyframes : " + std::to_string(nTimeKeyframes);
std::string cameraKeyframeInfo = "CameraKeyframes : " + std::to_string(nCameraKeyframes);
std::string latencyStandardDeviation = "Latency standard deviation: " + std::to_string(parallel.latencyStandardDeviation()) + " s";
const bool resetTimeOffset = ImGui::Button("Reset time offset");

View File

@@ -25,6 +25,7 @@
#include <modules/onscreengui/include/guitimecomponent.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/util/timemanager.h>
#include <openspace/util/time.h>
#include "imgui.h"
@@ -37,7 +38,7 @@ GuiTimeComponent::GuiTimeComponent()
{}
void GuiTimeComponent::render() {
float deltaTime = static_cast<float>(Time::ref().deltaTime());
float deltaTime = static_cast<float>(OsEng.timeManager().time().deltaTime());
bool changed = ImGui::SliderFloat("Delta Time", &deltaTime, -50000.f, 50000.f);
if (changed) {

View File

@@ -211,7 +211,7 @@ void RenderableConstellationBounds::update(const UpdateData& data) {
_stateMatrix = SpiceManager::ref().positionTransformMatrix(
_originReferenceFrame,
"GALACTIC",
data.time
data.time.j2000Seconds()
);
}

View File

@@ -531,7 +531,7 @@ void RenderablePlanet::update(const UpdateData& data) {
// set spice-orientation in accordance to timestamp
_stateMatrix = data.modelTransform.rotation;
//_stateMatrix = SpiceManager::ref().positionTransformMatrix(_frame, "GALACTIC", data.time);
_time = data.time;
_time = data.time.j2000Seconds();
}
void RenderablePlanet::loadTexture() {

View File

@@ -119,7 +119,7 @@ void SpiceRotation::update(const UpdateData& data) {
_matrix = SpiceManager::ref().positionTransformMatrix(
_sourceFrame,
_destinationFrame,
data.time
data.time.j2000Seconds()
);
}
catch (const SpiceManager::SpiceException&) {

View File

@@ -267,7 +267,7 @@ void KeplerTranslation::update(const UpdateData& data) {
_orbitPlaneDirty = false;
}
double t = data.time - _epoch;
double t = data.time.j2000Seconds() - _epoch;
double meanMotion = 2.0 * glm::pi<double>() / _period;
double meanAnomaly = glm::radians(_meanAnomalyAtEpoch.value()) + t * meanMotion;
double e = eccentricAnomaly(meanAnomaly);

View File

@@ -166,7 +166,7 @@ glm::dvec3 SpiceTranslation::position() const {
void SpiceTranslation::update(const UpdateData& data) {
double lightTime = 0.0;
_position = SpiceManager::ref().targetPosition(
_target, _origin, _frame, {}, data.time, lightTime
_target, _origin, _frame, {}, data.time.j2000Seconds(), lightTime
) * glm::pow(10.0, 3.0);
}

View File

@@ -121,7 +121,7 @@ void RenderableToyVolume::update(const UpdateData& data) {
_raycaster->setColor(_color);
_raycaster->setStepSize(_stepSize);
_raycaster->setModelTransform(transform);
_raycaster->setTime(data.time);
_raycaster->setTime(data.time.j2000Seconds());
}
}

View File

@@ -155,6 +155,7 @@ set(OPENSPACE_SOURCE
${OPENSPACE_BASE_DIR}/src/util/task.cpp
${OPENSPACE_BASE_DIR}/src/util/taskloader.cpp
${OPENSPACE_BASE_DIR}/src/util/time.cpp
${OPENSPACE_BASE_DIR}/src/util/timeline.cpp
${OPENSPACE_BASE_DIR}/src/util/timemanager.cpp
${OPENSPACE_BASE_DIR}/src/util/time_lua.inl
${OPENSPACE_BASE_DIR}/src/util/timerange.cpp
@@ -299,6 +300,8 @@ set(OPENSPACE_HEADER
${OPENSPACE_BASE_DIR}/include/openspace/util/task.h
${OPENSPACE_BASE_DIR}/include/openspace/util/taskloader.h
${OPENSPACE_BASE_DIR}/include/openspace/util/time.h
${OPENSPACE_BASE_DIR}/include/openspace/util/timeline.h
${OPENSPACE_BASE_DIR}/include/openspace/util/timeline.inl
${OPENSPACE_BASE_DIR}/include/openspace/util/timemanager.h
${OPENSPACE_BASE_DIR}/include/openspace/util/timerange.h
${OPENSPACE_BASE_DIR}/include/openspace/util/updatestructures.h

View File

@@ -177,7 +177,6 @@ OpenSpaceEngine::OpenSpaceEngine(std::string programName,
);
SpiceManager::initialize();
Time::initialize();
TransformationManager::initialize();
}
@@ -384,7 +383,7 @@ void OpenSpaceEngine::destroy() {
func();
}
_engine->_syncEngine->removeSyncables(Time::ref().getSyncables());
_engine->_syncEngine->removeSyncables(_engine->timeManager().getSyncables());
_engine->_syncEngine->removeSyncables(_engine->_renderEngine->getSyncables());
_engine->_syncEngine->removeSyncable(_engine->_scriptEngine.get());
@@ -396,7 +395,6 @@ void OpenSpaceEngine::destroy() {
delete _engine;
FactoryManager::deinitialize();
Time::deinitialize();
SpiceManager::deinitialize();
@@ -563,7 +561,7 @@ void OpenSpaceEngine::loadScene(const std::string& scenePath) {
Scene* previousScene = _renderEngine->scene();
if (previousScene) {
_syncEngine->removeSyncables(Time::ref().getSyncables());
_syncEngine->removeSyncables(_timeManager->getSyncables());
_syncEngine->removeSyncables(_renderEngine->getSyncables());
_syncEngine->removeSyncable(_scriptEngine.get());
@@ -606,7 +604,7 @@ void OpenSpaceEngine::loadScene(const std::string& scenePath) {
);
}
_syncEngine->addSyncables(Time::ref().getSyncables());
_syncEngine->addSyncables(_timeManager->getSyncables());
_syncEngine->addSyncables(_renderEngine->getSyncables());
_syncEngine->addSyncable(_scriptEngine.get());
@@ -996,7 +994,7 @@ void OpenSpaceEngine::preSynchronization() {
double dt = _windowWrapper->averageDeltaTime();
_timeManager->preSynchronization(dt);
auto scheduledScripts = _scriptScheduler->progressTo(Time::ref().j2000Seconds());
auto scheduledScripts = _scriptScheduler->progressTo(timeManager().time().j2000Seconds());
for (auto it = scheduledScripts.first; it != scheduledScripts.second; ++it) {
_scriptEngine->queueScript(
*it, ScriptEngine::RemoteScripting::Yes

View File

@@ -60,6 +60,15 @@ namespace {
const char* MainTemplateFilename = "${OPENSPACE_DATA}/web/keybindings/main.hbs";
const char* KeybindingTemplateFilename = "${OPENSPACE_DATA}/web/keybindings/keybinding.hbs";
const char* JsFilename = "${OPENSPACE_DATA}/web/keybindings/script.js";
const int IdOrbitalInteractionMode = 0;
const char* KeyOrbitalInteractionMode = "Orbital";
const int IdGlobeBrowsingInteractionMode = 1;
const char* KeyGlobeBrowsingInteractionMode = "GlobeBrowsing";
const int IdKeyframeInteractionMode = 2;
const char* KeyKeyframeInteractionMode = "Keyframe";
} // namespace
#include "interactionhandler_lua.inl"
@@ -85,6 +94,11 @@ InteractionHandler::InteractionHandler()
, _verticalFriction("verticalFriction", "Vertical Friction", true)
, _sensitivity("sensitivity", "Sensitivity", 0.5f, 0.001f, 1.f)
, _rapidness("rapidness", "Rapidness", 1.f, 0.1f, 60.f)
, _interactionModeOption(
"interactionMode",
"Interaction Mode",
properties::OptionProperty::DisplayType::Dropdown
)
{
_origin.onChange([this]() {
SceneGraphNode* node = sceneGraphNode(_origin.value());
@@ -100,24 +114,17 @@ InteractionHandler::InteractionHandler()
_inputState = std::make_unique<InputState>();
// Inject the same mouse states to both orbital and global interaction mode
_mouseStates = std::make_unique<OrbitalInteractionMode::MouseStates>(_sensitivity * pow(10.0,-4), 1);
_interactionModes.insert(
std::pair<std::string, std::shared_ptr<InteractionMode>>(
"Orbital",
std::make_shared<OrbitalInteractionMode>(_mouseStates)
));
_interactionModes.insert(
std::pair<std::string, std::shared_ptr<InteractionMode>>(
"GlobeBrowsing",
std::make_shared<GlobeBrowsingInteractionMode>(_mouseStates)
));
_interactionModes.insert(
std::pair<std::string, std::shared_ptr<InteractionMode>>(
"Keyframe",
std::make_shared<KeyframeInteractionMode>()
));
_orbitalInteractionMode = std::make_unique<OrbitalInteractionMode>(_mouseStates);
_globeBrowsingInteractionMode = std::make_unique<GlobeBrowsingInteractionMode>(_mouseStates);
_keyframeInteractionMode = std::make_unique<KeyframeInteractionMode>();
_interactionModeOption.addOption(IdOrbitalInteractionMode, KeyOrbitalInteractionMode);
_interactionModeOption.addOption(IdGlobeBrowsingInteractionMode, KeyGlobeBrowsingInteractionMode);
_interactionModeOption.addOption(IdKeyframeInteractionMode, KeyKeyframeInteractionMode);
// Set the interactionMode
_currentInteractionMode = _interactionModes["Orbital"];
_currentInteractionMode = _orbitalInteractionMode.get();
// Define lambda functions for changed properties
_rotationalFriction.onChange([&]() {
@@ -136,8 +143,25 @@ InteractionHandler::InteractionHandler()
_mouseStates->setVelocityScaleFactor(_rapidness);
});
_interactionModeOption.onChange([this]() {
switch(_interactionModeOption.value()) {
case IdGlobeBrowsingInteractionMode:
setInteractionMode(_globeBrowsingInteractionMode.get());
break;
case IdKeyframeInteractionMode:
setInteractionMode(_keyframeInteractionMode.get());
break;
case IdOrbitalInteractionMode:
default:
setInteractionMode(_orbitalInteractionMode.get());
break;
}
});
// Add the properties
addProperty(_origin);
addProperty(_interactionModeOption);
addProperty(_rotationalFriction);
addProperty(_horizontalFriction);
@@ -153,14 +177,9 @@ InteractionHandler::~InteractionHandler() {
void InteractionHandler::initialize() {
OsEng.parallelConnection().connectionEvent()->subscribe("interactionHandler", "statusChanged", [this]() {
if (OsEng.parallelConnection().status() == ParallelConnection::Status::ClientWithHost) {
setInteractionMode("Keyframe");
} else {
auto keyframeModeIter = _interactionModes.find("Keyframe");
if (keyframeModeIter != _interactionModes.end()) {
if (_currentInteractionMode == keyframeModeIter->second) {
setInteractionMode("Orbital");
}
}
setInteractionMode(_keyframeInteractionMode.get());
} else if (_currentInteractionMode == _keyframeInteractionMode.get()) {
setInteractionMode(_orbitalInteractionMode.get());
}
});
}
@@ -183,7 +202,7 @@ void InteractionHandler::resetCameraDirection() {
_currentInteractionMode->rotateToFocusNodeInterpolator().start();
}
void InteractionHandler::setInteractionMode(std::shared_ptr<InteractionMode> interactionMode) {
void InteractionHandler::setInteractionMode(InteractionMode* interactionMode) {
// Focus node is passed over from the previous interaction mode
SceneGraphNode* focusNode = _currentInteractionMode->focusNode();
@@ -195,31 +214,13 @@ void InteractionHandler::setInteractionMode(std::shared_ptr<InteractionMode> int
}
InteractionMode * InteractionHandler::interactionMode() {
return _currentInteractionMode.get();
return _currentInteractionMode;
}
void InteractionHandler::setInteractionMode(const std::string& interactionModeKey) {
if (_interactionModes.find(interactionModeKey) != _interactionModes.end()) {
setInteractionMode(_interactionModes[interactionModeKey]);
LINFO("Interaction mode set to '" << interactionModeKey << "'");
}
else {
std::string listInteractionModes("");
for (auto pair : _interactionModes) {
listInteractionModes += "'" + pair.first + "', ";
}
LWARNING("'" << interactionModeKey <<
"' is not a valid interaction mode. Candidates are " << listInteractionModes);
}
}
void InteractionHandler::goToChunk(int x, int y, int level) {
std::shared_ptr<GlobeBrowsingInteractionMode> gbim =
std::dynamic_pointer_cast<GlobeBrowsingInteractionMode> (_currentInteractionMode);
if (gbim) {
if (_currentInteractionMode == _globeBrowsingInteractionMode.get()) {
#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED
gbim->goToChunk(*_camera, globebrowsing::TileIndex(x,y,level), glm::vec2(0.5,0.5), true);
_globeBrowsingInteractionMode->goToChunk(*_camera, globebrowsing::TileIndex(x,y,level), glm::vec2(0.5,0.5), true);
#endif
} else {
LWARNING("Interaction mode must be set to 'GlobeBrowsing'");
@@ -227,12 +228,9 @@ void InteractionHandler::goToChunk(int x, int y, int level) {
}
void InteractionHandler::goToGeo(double latitude, double longitude) {
std::shared_ptr<GlobeBrowsingInteractionMode> gbim =
std::dynamic_pointer_cast<GlobeBrowsingInteractionMode> (_currentInteractionMode);
if (gbim) {
if (_currentInteractionMode == _globeBrowsingInteractionMode.get()) {
#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED
gbim->goToGeodetic2(
_globeBrowsingInteractionMode->goToGeodetic2(
*_camera,
globebrowsing::Geodetic2(latitude, longitude) / 180 * glm::pi<double>(), true
);
@@ -499,12 +497,6 @@ scripting::LuaLibrary InteractionHandler::luaLibrary() {
"that is to be executed, and the optional third argument is a human "
"readable description of the command for documentation purposes."
},
{
"setInteractionMode",
&luascriptfunctions::setInteractionMode,
"string",
"Set the interaction mode for the camera"
},
{
"saveCameraStateToFile",
&luascriptfunctions::saveCameraStateToFile,
@@ -539,20 +531,32 @@ scripting::LuaLibrary InteractionHandler::luaLibrary() {
};
}
void InteractionHandler::addKeyframe(const datamessagestructures::CameraKeyframe &kf) {
_inputState->addKeyframe(kf);
void InteractionHandler::addKeyframe(double timestamp, KeyframeInteractionMode::CameraPose pose) {
if (!_keyframeInteractionMode) {
return;
}
_keyframeInteractionMode->timeline().addKeyframe(timestamp, pose);
}
void InteractionHandler::removeKeyframesAfter(double timestamp) {
_inputState->removeKeyframesAfter(timestamp);
if (!_keyframeInteractionMode) {
return;
}
_keyframeInteractionMode->timeline().removeKeyframesAfter(timestamp);
}
void InteractionHandler::clearKeyframes() {
_inputState->clearKeyframes();
if (!_keyframeInteractionMode) {
return;
}
_keyframeInteractionMode->timeline().clearKeyframes();
}
const std::vector<datamessagestructures::CameraKeyframe>& InteractionHandler::keyframes() const {
return _inputState->keyframes();
size_t InteractionHandler::nKeyframes() const {
if (!_keyframeInteractionMode) {
return 0;
}
return _keyframeInteractionMode->timeline().keyframes().size();
}
} // namespace interaction

View File

@@ -169,28 +169,6 @@ int clearKeys(lua_State* L) {
return 0;
}
/**
* \ingroup LuaScripts
* setInteractionMode():
* Set the interaction mode
*/
int setInteractionMode(lua_State* L) {
using ghoul::lua::luaTypeToString;
int nArguments = lua_gettop(L);
if (nArguments != 1)
return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments);
std::string interactionModeKey = luaL_checkstring(L, -1);
if (interactionModeKey.empty())
return luaL_error(L, "interactionmode name string is empty");
OsEng.interactionHandler().setInteractionMode(interactionModeKey);
return 0;
}
int goToChunk(lua_State* L) {
using ghoul::lua::luaTypeToString;

View File

@@ -60,46 +60,6 @@ namespace interaction {
}
const std::vector<datamessagestructures::CameraKeyframe>& InputState::keyframes() const {
return _keyframes;
}
void InputState::addKeyframe(const datamessagestructures::CameraKeyframe &kf) {
clearOldKeyframes();
if (kf._timestamp < OsEng.runTime()) {
return;
}
_keyframes.insert(std::upper_bound(_keyframes.begin(), _keyframes.end(), kf, &InputState::compareKeyframeTimes), kf);
}
void InputState::removeKeyframesAfter(double timestamp) {
datamessagestructures::CameraKeyframe kf;
kf._timestamp = timestamp;
// Remove keyframes after the inserted keyframe.
_keyframes.erase(std::upper_bound(_keyframes.begin(), _keyframes.end(), kf, &InputState::compareKeyframeTimes), _keyframes.end());
}
bool InputState::compareKeyframeTimes(const datamessagestructures::CameraKeyframe& a, const datamessagestructures::CameraKeyframe& b) {
return a._timestamp < b._timestamp;
}
void InputState::clearOldKeyframes() {
double now = OsEng.runTime();
auto isLater = [now](const datamessagestructures::CameraKeyframe kf) {
return kf._timestamp > now;
};
// Remote keyframes with earlier timestamps than the current time.
auto nextKeyframe = std::find_if(_keyframes.begin(), _keyframes.end(), isLater);
if (nextKeyframe != _keyframes.begin()) {
_keyframes.erase(_keyframes.begin(), nextKeyframe - 1);
}
}
void InputState::clearKeyframes() {
_keyframes.clear();
}
void InputState::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) {
if (action == KeyAction::Press) {
_keysDown.push_back(std::pair<Key, KeyModifier>(key, modifier));
@@ -205,66 +165,73 @@ Interpolator<double>& InteractionMode::rotateToFocusNodeInterpolator() {
// KeyframeInteractionMode
KeyframeInteractionMode::KeyframeInteractionMode(){
KeyframeInteractionMode::KeyframeInteractionMode()
{
}
KeyframeInteractionMode::~KeyframeInteractionMode() {
}
void KeyframeInteractionMode::updateMouseStatesFromInput(const InputState& inputState, double) {
_keyframes = inputState.keyframes();
void KeyframeInteractionMode::updateMouseStatesFromInput(const InputState&, double) {
// Do nothing.
}
void KeyframeInteractionMode::updateCameraStateFromMouseStates(Camera& camera, double) {
if (_keyframes.size() == 0) {
return;
}
double now = OsEng.runTime();
auto isLater = [now](const datamessagestructures::CameraKeyframe kf) {
return kf._timestamp > now;
};
auto nextKeyframe = std::find_if(_keyframes.begin(), _keyframes.end(), isLater);
if (nextKeyframe == _keyframes.end()) {
if (_cameraPoseTimeline.nKeyframes() == 0) {
return;
}
if (nextKeyframe == _keyframes.begin()) {
camera.setPositionVec3(_keyframes[0]._position);
camera.setRotation(_keyframes[0]._rotation);
const Keyframe<CameraPose>* nextKeyframe = _cameraPoseTimeline.firstKeyframeAfter(now);
const Keyframe<CameraPose>* prevKeyframe = _cameraPoseTimeline.lastKeyframeBefore(now);
double nextTime = 0;
double prevTime = 0;
double t = 0;
if (nextKeyframe) {
nextTime = nextKeyframe->timestamp;
} else {
return;
}
auto prevKeyframe = nextKeyframe - 1;
double prevTime = prevKeyframe->_timestamp;
double nextTime = nextKeyframe->_timestamp;
if (prevKeyframe) {
prevTime = prevKeyframe->timestamp;
t = (now - prevTime) / (nextTime - prevTime);
} else {
// If there is no keyframe before: Only use the next keyframe.
prevTime = nextTime;
prevKeyframe = nextKeyframe;
t = 1;
}
double t = (now - prevTime) / (nextTime - prevTime);
_cameraPoseTimeline.removeKeyframesBefore(prevTime);
const CameraPose& prevPose = prevKeyframe->data;
const CameraPose& nextPose = nextKeyframe->data;
Scene* scene = camera.parent()->scene();
SceneGraphNode* prevFocusNode = scene->sceneGraphNode(prevKeyframe->_focusNode);
SceneGraphNode* nextFocusNode = scene->sceneGraphNode(nextKeyframe->_focusNode);
SceneGraphNode* prevFocusNode = scene->sceneGraphNode(prevPose.focusNode);
SceneGraphNode* nextFocusNode = scene->sceneGraphNode(nextPose.focusNode);
if (!prevFocusNode || !nextFocusNode) {
return;
}
glm::dvec3 prevKeyframeCameraPosition = prevKeyframe->_position;
glm::dvec3 nextKeyframeCameraPosition = nextKeyframe->_position;
glm::dquat prevKeyframeCameraRotation = prevKeyframe->_rotation;
glm::dquat nextKeyframeCameraRotation = nextKeyframe->_rotation;
glm::dvec3 prevKeyframeCameraPosition = prevPose.position;
glm::dvec3 nextKeyframeCameraPosition = nextPose.position;
glm::dquat prevKeyframeCameraRotation = prevPose.rotation;
glm::dquat nextKeyframeCameraRotation = nextPose.rotation;
// Transform position and rotation based on focus node rotation (if following rotation)
if (prevKeyframe->_followNodeRotation) {
prevKeyframeCameraRotation = prevFocusNode->worldRotationMatrix() * glm::dmat3(prevKeyframe->_rotation);
prevKeyframeCameraPosition = prevFocusNode->worldRotationMatrix() * prevKeyframeCameraPosition;
if (prevPose.followFocusNodeRotation) {
prevKeyframeCameraRotation = prevFocusNode->worldRotationMatrix() * glm::dmat3(glm::dquat(prevPose.rotation));
prevKeyframeCameraPosition = prevFocusNode->worldRotationMatrix() * prevPose.position;
}
if (nextKeyframe->_followNodeRotation) {
nextKeyframeCameraRotation = nextFocusNode->worldRotationMatrix() * glm::dmat3(nextKeyframe->_rotation);
nextKeyframeCameraPosition = nextFocusNode->worldRotationMatrix() * nextKeyframeCameraPosition;
if (nextPose.followFocusNodeRotation) {
nextKeyframeCameraRotation = nextFocusNode->worldRotationMatrix() * glm::dmat3(glm::dquat(nextPose.rotation));
nextKeyframeCameraPosition = nextFocusNode->worldRotationMatrix() * nextPose.position;
}
// Transform position based on focus node position
@@ -280,6 +247,10 @@ bool KeyframeInteractionMode::followingNodeRotation() const {
return false;
}
Timeline<KeyframeInteractionMode::CameraPose>& KeyframeInteractionMode::timeline() {
return _cameraPoseTimeline;
}
// OrbitalInteractionMode
OrbitalInteractionMode::MouseStates::MouseStates(double sensitivity, double velocityScaleFactor)
: _sensitivity(sensitivity)

View File

@@ -24,6 +24,7 @@
#include <openspace/network/networkengine.h>
#include <openspace/util/time.h>
#include <openspace/util/timemanager.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/engine/wrapper/windowwrapper.h>
@@ -95,11 +96,13 @@ void NetworkEngine::publishStatusMessage() {
// 8 bytes: delta time as double
// Total: 40
Time& currentTime = OsEng.timeManager().time();
uint16_t messageSize = 0;
double time = Time::ref().j2000Seconds();
std::string timeString = Time::ref().UTC();
double delta = Time::ref().deltaTime();
double time = currentTime.j2000Seconds();
std::string timeString = currentTime.UTC();
double delta = currentTime.deltaTime();
messageSize += sizeof(time);
messageSize += static_cast<uint16_t>(timeString.length());

View File

@@ -68,6 +68,7 @@
#include <openspace/interaction/interactionmode.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/util/timemanager.h>
#include <openspace/interaction/interactionmode.h>
#include <openspace/util/time.h>
#include <openspace/openspace.h>
#include <openspace/scripting/script_helper.h>
@@ -549,7 +550,13 @@ void ParallelConnection::dataMessageReceived(const std::vector<char>& messageCon
kf._timestamp = calculateBufferedKeyframeTime(kf._timestamp);
OsEng.interactionHandler().removeKeyframesAfter(kf._timestamp);
OsEng.interactionHandler().addKeyframe(kf);
interaction::KeyframeInteractionMode::CameraPose pose;
pose.focusNode = kf._focusNode;
pose.position = kf._position;
pose.rotation = kf._rotation;
pose.followFocusNodeRotation = kf._followNodeRotation;
OsEng.interactionHandler().addKeyframe(kf._timestamp, pose);
break;
}
case datamessagestructures::Type::TimeData: {
@@ -557,7 +564,12 @@ void ParallelConnection::dataMessageReceived(const std::vector<char>& messageCon
kf._timestamp = calculateBufferedKeyframeTime(kf._timestamp);
OsEng.timeManager().removeKeyframesAfter(kf._timestamp);
OsEng.timeManager().addKeyframe(kf);
Time time(kf._time);
time.setDeltaTime(kf._dt);
time.setPause(kf._paused);
time.setTimeJumped(kf._requiresTimeJump);
OsEng.timeManager().addKeyframe(kf._timestamp, time);
break;
}
case datamessagestructures::Type::ScriptData: {
@@ -970,7 +982,7 @@ void ParallelConnection::preSynchronization() {
}
if (status() == Status::Host) {
if (Time::ref().timeJumped()) {
if (OsEng.timeManager().time().timeJumped()) {
_timeJumped = true;
}
double now = OsEng.runTime();
@@ -1061,10 +1073,12 @@ void ParallelConnection::sendTimeKeyframe() {
// Create a keyframe with current position and orientation of camera
datamessagestructures::TimeKeyframe kf;
kf._dt = Time::ref().deltaTime();
kf._paused = Time::ref().paused();
Time& time = OsEng.timeManager().time();
kf._dt = time.deltaTime();
kf._paused = time.paused();
kf._requiresTimeJump = _timeJumped;
kf._time = Time::ref().j2000Seconds();
kf._time = time.j2000Seconds();
// Timestamp as current runtime of OpenSpace instance
kf._timestamp = OsEng.runTime();

View File

@@ -33,6 +33,7 @@
#include <openspace/scene/scene.h>
#include <openspace/util/camera.h>
#include <openspace/util/updatestructures.h>
#include <openspace/util/timemanager.h>
#include <openspace/performance/performancemeasurement.h>
@@ -264,7 +265,8 @@ void ABufferRenderer::render(float blackoutFactor, bool doPerformanceMeasurement
static_cast<int>(Renderable::RenderBin::Transparent) |
static_cast<int>(Renderable::RenderBin::Overlay);
RenderData data{ *_camera, psc(), doPerformanceMeasurements, renderBinMask };
Time time = OsEng.timeManager().time();
RenderData data{ *_camera, psc(), time, doPerformanceMeasurements, renderBinMask };
RendererTasks tasks;
_scene->render(data, tasks);
_blackoutFactor = blackoutFactor;

View File

@@ -29,6 +29,7 @@
#include <string>
#include <openspace/scene/scene.h>
#include <openspace/util/camera.h>
#include <openspace/util/timemanager.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/rendering/renderable.h>
#include <openspace/rendering/volumeraycaster.h>
@@ -336,7 +337,9 @@ void FramebufferRenderer::render(float blackoutFactor, bool doPerformanceMeasure
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
RenderData data = { *_camera, psc(), doPerformanceMeasurements, 0 };
Time time = OsEng.timeManager().time();
RenderData data = { *_camera, psc(), time, doPerformanceMeasurements, 0 };
RendererTasks tasks;
// Capture standard fbo

View File

@@ -45,6 +45,7 @@
#include <openspace/interaction/luaconsole.h>
#include <openspace/util/camera.h>
#include <openspace/util/time.h>
#include <openspace/util/timemanager.h>
#include <openspace/util/screenlog.h>
#include <openspace/util/spicemanager.h>
#include <openspace/rendering/raycastermanager.h>
@@ -308,12 +309,10 @@ void RenderEngine::deinitialize() {
}
void RenderEngine::updateScene() {
const Time& currentTime = OsEng.timeManager().time();
_scene->update({
{ glm::dvec3(0), glm::dmat3(1), 1.0 },
Time::ref().j2000Seconds(),
Time::ref().deltaTime(),
Time::ref().paused(),
Time::ref().timeJumped(),
currentTime,
_performanceManager != nullptr
});
@@ -484,8 +483,9 @@ void RenderEngine::renderShutdownInformation(float timer, float fullTime) {
}
void RenderEngine::postDraw() {
if (Time::ref().timeJumped()) {
Time::ref().setTimeJumped(false);
Time& currentTime = OsEng.timeManager().time();
if (currentTime.timeJumped()) {
currentTime.setTimeJumped(false);
}
if (_shouldTakeScreenshot) {
@@ -840,7 +840,7 @@ void RenderEngine::renderInformation() {
*_fontDate,
penPosition,
"Date: %s",
Time::ref().UTC().c_str()
OsEng.timeManager().time().UTC().c_str()
);
}
else {
@@ -851,7 +851,7 @@ void RenderEngine::renderInformation() {
*_fontInfo,
penPosition,
"Simulation increment (s): %.3f",
Time::ref().deltaTime()
OsEng.timeManager().time().deltaTime()
);
FrametimeType frametimeType = FrametimeType(_frametimeType.value());
@@ -936,7 +936,7 @@ void RenderEngine::renderInformation() {
#ifdef OPENSPACE_MODULE_NEWHORIZONS_ENABLED
bool hasNewHorizons = scene()->sceneGraphNode("NewHorizons");
double currentTime = Time::ref().j2000Seconds();
double currentTime = OsEng.timeManager().time().j2000Seconds();
if (MissionManager::ref().hasCurrentMission()) {

View File

@@ -321,6 +321,7 @@ void SceneGraphNode::render(const RenderData& data, RendererTasks& tasks) {
RenderData newData = {
data.camera,
thisPositionPSC,
data.time,
data.doPerformanceMeasurement,
data.renderBinMask,
{ _worldPositionCached, _worldRotationCached, _worldScaleCached }

View File

@@ -86,9 +86,6 @@ glm::dvec3 Translation::position(double time) {
update({
{},
time,
1.0,
false,
false,
false
});

View File

@@ -103,7 +103,7 @@ ScriptScheduler::ScheduledScript::ScheduledScript(const ghoul::Dictionary& dicti
: time(-std::numeric_limits<double>::max())
{
std::string timeStr = dictionary.value<std::string>(KeyTime);
time = Time::ref().convertTime(timeStr);
time = Time::convertTime(timeStr);
// If a universal script is specified, retrieve it and add a ; as a separator so that
// it can be added to the other scripts

View File

@@ -26,6 +26,8 @@
#include <time.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/util/timemanager.h>
#include "time_lua.inl"
#include <openspace/util/spicemanager.h>
@@ -36,9 +38,6 @@
namespace openspace {
Time* Time::_instance = nullptr;
double Time::convertTime(const std::string& time) {
ghoul_assert(!time.empty(), "timeString must not be empty");
return SpiceManager::ref().ephemerisTimeFromDate(time);
@@ -54,26 +53,11 @@ Time::Time(const Time& other)
: _time(other._time)
, _dt(other._dt)
, _timeJumped(other._timeJumped)
, _timePaused(other._timePaused)
{
}
void Time::initialize() {
ghoul_assert(_instance == nullptr, "Static time must not have been ininitialized");
_instance = new Time();
}
void Time::deinitialize() {
ghoul_assert(_instance, "Static time must have been ininitialized");
delete _instance;
_instance = nullptr;
}
Time& Time::ref() {
ghoul_assert(_instance, "Static time must have been ininitialized");
return *_instance;
}
Time Time::now() {
Time now;
time_t secondsSince1970;
@@ -85,10 +69,6 @@ Time Time::now() {
return now;
}
bool Time::isInitialized() {
return (_instance != nullptr);
}
void Time::setTime(double value, bool requireJump) {
_time = value;
_timeJumped = requireJump;
@@ -169,10 +149,6 @@ bool Time::paused() const {
return _timePaused;
}
std::vector<Syncable*> Time::getSyncables() {
return{ &_time, &_dt, &_timeJumped};
}
scripting::LuaLibrary Time::luaLibrary() {
return {
"time",

View File

@@ -46,7 +46,7 @@ int time_setDeltaTime(lua_State* L) {
const bool isNumber = (lua_isnumber(L, -1) != 0);
if (isNumber) {
double value = lua_tonumber(L, -1);
openspace::Time::ref().setDeltaTime(value);
OsEng.timeManager().time().setDeltaTime(value);
return 0;
}
else {
@@ -67,7 +67,7 @@ int time_setDeltaTime(lua_State* L) {
* Returns the delta time by calling the Time::deltaTime method
*/
int time_deltaTime(lua_State* L) {
lua_pushnumber(L, openspace::Time::ref().deltaTime());
lua_pushnumber(L, OsEng.timeManager().time().deltaTime());
return 1;
}
@@ -82,7 +82,7 @@ int time_togglePause(lua_State* L) {
return luaL_error(L, "Expected %i arguments, got %i", 0, nArguments);
}
openspace::Time::ref().togglePause();
OsEng.timeManager().time().togglePause();
return 0;
}
@@ -98,7 +98,7 @@ int time_setPause(lua_State* L) {
}
bool pause = lua_toboolean(L, -1) == 1;
openspace::Time::ref().setPause(pause);
OsEng.timeManager().time().setPause(pause);
return 0;
}
@@ -130,12 +130,12 @@ int time_setTime(lua_State* L) {
}
if (isNumber) {
double value = lua_tonumber(L, -1);
openspace::Time::ref().setTime(value);
OsEng.timeManager().time().setTime(value);
return 0;
}
if (isString) {
const char* time = lua_tostring(L, -1);
openspace::Time::ref().setTime(time);
OsEng.timeManager().time().setTime(time);
return 0;
}
return 0;
@@ -148,7 +148,7 @@ int time_setTime(lua_State* L) {
* It is returned by calling the Time::currentTime method.
*/
int time_currentTime(lua_State* L) {
lua_pushnumber(L, openspace::Time::ref().j2000Seconds());
lua_pushnumber(L, OsEng.timeManager().time().j2000Seconds());
return 1;
}
@@ -159,7 +159,7 @@ int time_currentTime(lua_State* L) {
* timezone by calling the Time::UTC method
*/
int time_currentTimeUTC(lua_State* L) {
lua_pushstring(L, openspace::Time::ref().UTC().c_str());
lua_pushstring(L, OsEng.timeManager().time().UTC().c_str());
return 1;
}

41
src/util/timeline.cpp Normal file
View File

@@ -0,0 +1,41 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2017 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <openspace/util/timeline.h>
namespace openspace {
bool compareKeyframeTimes(const KeyframeBase& a, const KeyframeBase& b) {
return a.timestamp < b.timestamp;
}
bool compareTimeWithKeyframeTime(double a, const KeyframeBase& b) {
return a < b.timestamp;
}
bool compareKeyframeTimeWithTime(const KeyframeBase& a, double b) {
return a.timestamp < b;
}
} // namespace

View File

@@ -24,8 +24,8 @@
#include <openspace/util/timemanager.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/util/time.h>
#include <openspace/network/parallelconnection.h>
#include <openspace/util/timeline.h>
namespace openspace {
@@ -34,8 +34,8 @@ using datamessagestructures::TimeKeyframe;
void TimeManager::preSynchronization(double dt) {
// double now = OsEng.runTime();
removeKeyframesBefore(_latestConsumedTimestamp);
if (_keyframes.size() == 0) {
Time::ref().advanceTime(dt);
if (_timeline.nKeyframes() == 0) {
time().advanceTime(dt);
} else {
consumeKeyframes(dt);
}
@@ -43,121 +43,116 @@ void TimeManager::preSynchronization(double dt) {
void TimeManager::consumeKeyframes(double dt) {
double now = OsEng.runTime();
TimeKeyframe kf;
kf._timestamp = now;
auto firstFutureKeyframe = std::lower_bound(_keyframes.begin(), _keyframes.end(), kf, &TimeManager::compareKeyframeTimes);
const std::deque<Keyframe<Time>>& keyframes = _timeline.keyframes();
auto firstFutureKeyframe = std::lower_bound(keyframes.begin(), keyframes.end(), now, &compareKeyframeTimeWithTime);
bool consumingTimeJump = std::find_if(_keyframes.begin(), firstFutureKeyframe, [] (const TimeKeyframe& f) {
return f._requiresTimeJump;
bool consumingTimeJump = std::find_if(keyframes.begin(), firstFutureKeyframe, [] (const Keyframe<Time>& f) {
return f.data.timeJumped();
}) != firstFutureKeyframe;
Time& time = Time::ref();
if (firstFutureKeyframe == _keyframes.end()) {
if (firstFutureKeyframe == keyframes.end()) {
// All keyframes are in the past.
// Consume the latest one.
TimeKeyframe& current = _keyframes.back();
time.setTime(current._time, consumingTimeJump);
time.setDeltaTime(current._dt);
time.setPause(current._paused);
_latestConsumedTimestamp = current._timestamp;
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;
}
else {
TimeKeyframe& next = *firstFutureKeyframe;
const Keyframe<Time>& next = *firstFutureKeyframe;
const Time& nextTime = next.data;
if (firstFutureKeyframe != _keyframes.begin()) {
TimeKeyframe& latest = *(firstFutureKeyframe - 1);
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(latest._paused);
time.setTimeJumped(consumingTimeJump);
time.setDeltaTime(latest._dt);
time().setPause(latestTime.paused());
time().setTimeJumped(consumingTimeJump);
time().setDeltaTime(latestTime.deltaTime());
if (consumingTimeJump || latest._paused) {
time.setTime(latest._time, consumingTimeJump);
if (consumingTimeJump || latestTime.paused()) {
time().setTime(latestTime.j2000Seconds(), consumingTimeJump);
}
_latestConsumedTimestamp = latest._timestamp;
_latestConsumedTimestamp = latest.timestamp;
}
// Do not interpolate with time jumping keyframes.
// Instead, wait until their timestamp and apply them directly.
if (next._requiresTimeJump) {
Time::ref().advanceTime(dt);
if (nextTime.timeJumped()) {
time().advanceTime(dt);
return;
}
if (time.paused()) {
if (time().paused()) {
return;
}
const double secondsOffTolerance = OsEng.parallelConnection().timeTolerance();
double predictedTime = time.j2000Seconds() + time.deltaTime() * (next._timestamp - now);
bool withinTolerance = std::abs(predictedTime - next._time) < std::abs(next._dt * secondsOffTolerance);
double predictedTime = time().j2000Seconds() + time().deltaTime() * (next.timestamp - now);
bool withinTolerance = std::abs(predictedTime - nextTime.j2000Seconds()) < std::abs(nextTime.deltaTime() * secondsOffTolerance);
if (next._dt == time.deltaTime() && withinTolerance) {
Time::ref().advanceTime(dt);
if (nextTime.deltaTime() == time().deltaTime() && withinTolerance) {
time().advanceTime(dt);
return;
}
double t0 = now - dt;
double t1 = now;
double t2 = next._timestamp;
double t2 = next.timestamp;
double parameter = (t1 - t0) / (t2 - t0);
double y0 = time.j2000Seconds();
double yPrime0 = time.deltaTime();
double y0 = time().j2000Seconds();
double yPrime0 = time().deltaTime();
double y2 = next._time;
double yPrime2 = next._dt;
double y2 = nextTime.j2000Seconds();
double yPrime2 = nextTime.deltaTime();
double y1 = (1 - parameter) * y0 + parameter * y2;
double y1Prime = (y1 - y0) / dt;
time.setDeltaTime(y1Prime);
time.setTime(y1, false);
// std::cout << "Correcting time to " << y1 << ", dt=" << y1Prime << "." << std::endl;
time().setDeltaTime(y1Prime);
time().setTime(y1, false);
}
}
void TimeManager::addKeyframe(const TimeKeyframe& kf) {
if (kf._timestamp < OsEng.runTime()) {
return;
}
auto iter = std::upper_bound(_keyframes.begin(), _keyframes.end(), kf, &TimeManager::compareKeyframeTimes);
_keyframes.insert(iter, kf);
void TimeManager::addKeyframe(double timestamp, Time time) {
_timeline.addKeyframe(timestamp, time);
}
void TimeManager::removeKeyframesAfter(double timestamp) {
datamessagestructures::TimeKeyframe kf;
kf._timestamp = timestamp;
auto iter = std::upper_bound(_keyframes.begin(), _keyframes.end(), kf, &TimeManager::compareKeyframeTimes);
_keyframes.erase(iter, _keyframes.end());
_timeline.removeKeyframesAfter(timestamp);
}
void TimeManager::removeKeyframesBefore(double timestamp) {
datamessagestructures::TimeKeyframe kf;
kf._timestamp = timestamp;
auto iter = std::upper_bound(_keyframes.begin(), _keyframes.end(), kf, &TimeManager::compareKeyframeTimes);
_keyframes.erase(_keyframes.begin(), iter);
_timeline.removeKeyframesBefore(timestamp);
}
void TimeManager::clearKeyframes() {
_keyframes.clear();
_timeline.clearKeyframes();
}
const std::deque<datamessagestructures::TimeKeyframe>& TimeManager::keyframes() const {
return _keyframes;
size_t TimeManager::nKeyframes() const {
return _timeline.nKeyframes();
}
bool TimeManager::compareKeyframeTimes(const TimeKeyframe& a, const TimeKeyframe& b) {
return a._timestamp < b._timestamp;
Time& TimeManager::time() {
return _currentTime;
}
std::vector<Syncable*> TimeManager::getSyncables() {
return{ &_currentTime };
}
}

View File

@@ -40,6 +40,7 @@
#include <test_common.inl>
#include <test_spicemanager.inl>
#include <test_sceneloader.inl>
#include <test_timeline.inl>
#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED
//#include <test_chunknode.inl>

View File

@@ -60,16 +60,16 @@ TEST_F(ScriptSchedulerTest, SimpleForward) {
{ "BackwardScript", "BackwardScript1"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 02"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 02"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03")
openspace::Time::convertTime("2000 JAN 03")
);
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
@@ -94,20 +94,20 @@ TEST_F(ScriptSchedulerTest, MultipleForwardSingleJump) {
ScriptScheduler scheduler;
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary1 },
{ "2", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 02"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 02"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 06"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 06"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript2", *(res.first));
}
@@ -130,16 +130,16 @@ TEST_F(ScriptSchedulerTest, MultipleForwardOrdering) {
ScriptScheduler scheduler;
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary1 },
{ "2", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 02"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 02"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 06"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 06"));
ASSERT_EQ(2, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
EXPECT_EQ("ForwardScript2", *(std::next(res.first)));
@@ -157,15 +157,15 @@ TEST_F(ScriptSchedulerTest, SimpleBackward) {
{ "BackwardScript", "BackwardScript1"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 05"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 05"));
scheduler.loadScripts({
{ "1", testDictionary }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 02"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 02"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript1", *(res.first));
}
@@ -188,20 +188,20 @@ TEST_F(ScriptSchedulerTest, MultipleBackwardSingleJump) {
{ "BackwardScript", "BackwardScript2"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 07"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 07"));
scheduler.loadScripts({
{ "1", testDictionary1 },
{ "2", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 06"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 06"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript2", *(res.first));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript1", *(res.first));
}
@@ -224,16 +224,16 @@ TEST_F(ScriptSchedulerTest, MultipleBackwardOrdering) {
{ "BackwardScript", "BackwardScript2"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 07"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 07"));
scheduler.loadScripts({
{ "1", testDictionary1 },
{ "2", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 06"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 06"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
ASSERT_EQ(2, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript2", *(res.first));
EXPECT_EQ("BackwardScript1", *(std::next(res.first)));
@@ -280,24 +280,24 @@ TEST_F(ScriptSchedulerTest, ForwardBackwards) {
{ "BackwardScript", "BackwardScript2"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary1 },
{ "2", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript1", *(res.first));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 07"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 07"));
ASSERT_EQ(2, std::distance(res.first, res.second));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript2", *(res.first));
}
@@ -320,18 +320,18 @@ TEST_F(ScriptSchedulerTest, Rewind) {
{ "BackwardScript", "BackwardScript2"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary1 },
{ "2", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 07"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 07"));
ASSERT_EQ(2, std::distance(res.first, res.second));
scheduler.rewind();
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
}
@@ -406,23 +406,23 @@ TEST_F(ScriptSchedulerTest, JumpEqual) {
});
auto res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 11:00:00")
openspace::Time::convertTime("2000 JAN 03 11:00:00")
);
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 12:00:00")
openspace::Time::convertTime("2000 JAN 03 12:00:00")
);
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 12:01:00")
openspace::Time::convertTime("2000 JAN 03 12:01:00")
);
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 12:00:00")
openspace::Time::convertTime("2000 JAN 03 12:00:00")
);
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript1", *(res.first));
@@ -445,13 +445,13 @@ TEST_F(ScriptSchedulerTest, SameTime) {
});
auto res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 12:00:00")
openspace::Time::convertTime("2000 JAN 03 12:00:00")
);
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 12:00:00")
openspace::Time::convertTime("2000 JAN 03 12:00:00")
);
ASSERT_EQ(res.first, res.second);
}
@@ -473,23 +473,23 @@ TEST_F(ScriptSchedulerTest, MultiInnerJump) {
});
auto res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 10:00:00")
openspace::Time::convertTime("2000 JAN 03 10:00:00")
);
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 11:00:00")
openspace::Time::convertTime("2000 JAN 03 11:00:00")
);
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 13:00:00")
openspace::Time::convertTime("2000 JAN 03 13:00:00")
);
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
res = scheduler.progressTo(
openspace::Time::ref().convertTime("2000 JAN 03 12:30:00")
openspace::Time::convertTime("2000 JAN 03 12:30:00")
);
ASSERT_EQ(res.first, res.second);
}
@@ -513,7 +513,7 @@ TEST_F(ScriptSchedulerTest, MultipleForwardSingleJumpMultipleLoad) {
ScriptScheduler scheduler;
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary1 }
});
@@ -522,14 +522,14 @@ TEST_F(ScriptSchedulerTest, MultipleForwardSingleJumpMultipleLoad) {
{ "1", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 02"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 02"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 06"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 06"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript2", *(res.first));
}
@@ -552,7 +552,7 @@ TEST_F(ScriptSchedulerTest, MultipleForwardOrderingMultipleLoad) {
ScriptScheduler scheduler;
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary1 }
});
@@ -560,10 +560,10 @@ TEST_F(ScriptSchedulerTest, MultipleForwardOrderingMultipleLoad) {
{ "1", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 02"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 02"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 06"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 06"));
ASSERT_EQ(2, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
EXPECT_EQ("ForwardScript2", *(std::next(res.first)));
@@ -587,7 +587,7 @@ TEST_F(ScriptSchedulerTest, MultipleBackwardSingleJumpMultipleLoad) {
{ "BackwardScript", "BackwardScript2"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 07"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 07"));
scheduler.loadScripts({
{ "1", testDictionary1 }
});
@@ -595,14 +595,14 @@ TEST_F(ScriptSchedulerTest, MultipleBackwardSingleJumpMultipleLoad) {
{ "1", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 06"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 06"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript2", *(res.first));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript1", *(res.first));
}
@@ -625,7 +625,7 @@ TEST_F(ScriptSchedulerTest, MultipleBackwardOrderingMultipleLoad) {
{ "BackwardScript", "BackwardScript2"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 07"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 07"));
scheduler.loadScripts({
{ "1", testDictionary1 }
});
@@ -633,10 +633,10 @@ TEST_F(ScriptSchedulerTest, MultipleBackwardOrderingMultipleLoad) {
{ "1", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 06"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 06"));
ASSERT_EQ(res.first, res.second);
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
ASSERT_EQ(2, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript2", *(res.first));
EXPECT_EQ("BackwardScript1", *(std::next(res.first)));
@@ -660,7 +660,7 @@ TEST_F(ScriptSchedulerTest, ForwardBackwardsMultipleLoad) {
{ "BackwardScript", "BackwardScript2"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary1 }
});
@@ -668,18 +668,18 @@ TEST_F(ScriptSchedulerTest, ForwardBackwardsMultipleLoad) {
{ "1", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript1", *(res.first));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 07"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 07"));
ASSERT_EQ(2, std::distance(res.first, res.second));
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("BackwardScript2", *(res.first));
}
@@ -702,7 +702,7 @@ TEST_F(ScriptSchedulerTest, RewindMultipleLoad) {
{ "BackwardScript", "BackwardScript2"s }
};
scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 01"));
scheduler.progressTo(openspace::Time::convertTime("2000 JAN 01"));
scheduler.loadScripts({
{ "1", testDictionary1 }
});
@@ -710,12 +710,12 @@ TEST_F(ScriptSchedulerTest, RewindMultipleLoad) {
{ "1", testDictionary2 }
});
auto res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 07"));
auto res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 07"));
ASSERT_EQ(2, std::distance(res.first, res.second));
scheduler.rewind();
res = scheduler.progressTo(openspace::Time::ref().convertTime("2000 JAN 04"));
res = scheduler.progressTo(openspace::Time::convertTime("2000 JAN 04"));
ASSERT_EQ(1, std::distance(res.first, res.second));
EXPECT_EQ("ForwardScript1", *(res.first));
}

100
tests/test_timeline.inl Normal file
View File

@@ -0,0 +1,100 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2017 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include "gtest/gtest.h"
#include <openspace/util/timeline.h>
#include <openspace/util/time.h>
using namespace openspace;
class TimelineTest : public testing::Test {};
TEST_F(TimelineTest, AddAndCountKeyframes) {
Timeline<Time> timeline;
timeline.addKeyframe(0.0, Time::now());
timeline.addKeyframe(1.0, Time::now());
ASSERT_EQ(timeline.nKeyframes(), 2);
}
TEST_F(TimelineTest, QueryKeyframes) {
Timeline<float> timeline;
timeline.addKeyframe(0.0, 0.f);
timeline.addKeyframe(1.0, 1.f);
ASSERT_EQ(timeline.nKeyframes(), 2);
ASSERT_EQ(timeline.firstKeyframeAfter(0.0)->data, 1.f) << "Incorrect keyframe returned";
ASSERT_EQ(timeline.firstKeyframeAfter(0.0, false)->data, 1.f) << "Incorrect keyframe returned";
ASSERT_EQ(timeline.firstKeyframeAfter(0.0, true)->data, 0.f) << "Incorrect keyframe returned";
ASSERT_EQ(timeline.lastKeyframeBefore(1.0)->data, 0.f) << "Incorrect keyframe returned";
ASSERT_EQ(timeline.lastKeyframeBefore(1.0, false)->data, 0.f) << "Incorrect keyframe returned";
ASSERT_EQ(timeline.lastKeyframeBefore(1.0, true)->data, 1.f) << "Incorrect keyframe returned";
}
TEST_F(TimelineTest, RemoveKeyframes) {
Timeline<float> timeline;
timeline.addKeyframe(0.0, 0.f);
timeline.addKeyframe(1.0, 1.f);
timeline.removeKeyframesBefore(0.0);
ASSERT_EQ(timeline.nKeyframes(), 2);
timeline.removeKeyframesBefore(0.0, false);
ASSERT_EQ(timeline.nKeyframes(), 2);
timeline.removeKeyframesBefore(0.0, true);
ASSERT_EQ(timeline.nKeyframes(), 1);
timeline.removeKeyframesAfter(1.0);
ASSERT_EQ(timeline.nKeyframes(), 1);
timeline.removeKeyframesAfter(1.0, false);
ASSERT_EQ(timeline.nKeyframes(), 1);
timeline.removeKeyframesAfter(1.0, true);
ASSERT_EQ(timeline.nKeyframes(), 0);
}
TEST_F(TimelineTest, RemoveKeyframesInRange) {
Timeline<float> timeline;
timeline.addKeyframe(0.0, 0.f);
timeline.addKeyframe(1.0, 1.f);
timeline.addKeyframe(2.0, 2.f);
timeline.addKeyframe(3.0, 3.f);
timeline.removeKeyframesBetween(1.0, 2.0);
ASSERT_EQ(timeline.nKeyframes(), 4);
timeline.removeKeyframesBetween(1.0, 2.0, false, true);
ASSERT_EQ(timeline.nKeyframes(), 3);
timeline.removeKeyframesBetween(1.0, 2.0, true, true);
ASSERT_EQ(timeline.nKeyframes(), 2);
timeline.removeKeyframesBetween(-1.0, 4.0);
ASSERT_EQ(timeline.nKeyframes(), 0);
}