mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-03-13 00:38:33 -05:00
SessionRecording and KeyframeRecording redesign (#3399)
This commit is contained in:
@@ -57,9 +57,9 @@ namespace interaction {
|
||||
class ActionManager;
|
||||
class InteractionMonitor;
|
||||
class KeybindingManager;
|
||||
class KeyframeRecording;
|
||||
class KeyframeRecordingHandler;
|
||||
class NavigationHandler;
|
||||
class SessionRecording;
|
||||
class SessionRecordingHandler;
|
||||
} // namespace interaction
|
||||
namespace properties { class PropertyOwner; }
|
||||
namespace scripting {
|
||||
@@ -94,9 +94,9 @@ inline interaction::InteractionMonitor* interactionMonitor;
|
||||
inline interaction::JoystickInputStates* joystickInputStates;
|
||||
inline interaction::WebsocketInputStates* websocketInputStates;
|
||||
inline interaction::KeybindingManager* keybindingManager;
|
||||
inline interaction::KeyframeRecording* keyframeRecording;
|
||||
inline interaction::KeyframeRecordingHandler* keyframeRecording;
|
||||
inline interaction::NavigationHandler* navigationHandler;
|
||||
inline interaction::SessionRecording* sessionRecording;
|
||||
inline interaction::SessionRecordingHandler* sessionRecordingHandler;
|
||||
inline properties::PropertyOwner* rootPropertyOwner;
|
||||
inline properties::PropertyOwner* screenSpaceRootPropertyOwner;
|
||||
inline properties::PropertyOwner* userPropertyOwner;
|
||||
|
||||
@@ -22,63 +22,41 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_CORE___KEYFRAMERECORDING___H__
|
||||
#define __OPENSPACE_CORE___KEYFRAMERECORDING___H__
|
||||
#ifndef __OPENSPACE_CORE___KEYFRAMERECORDINGHANDLER___H__
|
||||
#define __OPENSPACE_CORE___KEYFRAMERECORDINGHANDLER___H__
|
||||
|
||||
#include <openspace/navigation/keyframenavigator.h>
|
||||
#include <openspace/properties/propertyowner.h>
|
||||
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/scripting/lualibrary.h>
|
||||
#include <json/json.hpp>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
class KeyframeRecording : public properties::PropertyOwner {
|
||||
class KeyframeRecordingHandler : public properties::PropertyOwner {
|
||||
public:
|
||||
struct Keyframe {
|
||||
struct TimeStamp {
|
||||
double application;
|
||||
double sequenceTime;
|
||||
double simulation;
|
||||
};
|
||||
|
||||
KeyframeNavigator::CameraPose camera;
|
||||
TimeStamp timestamp;
|
||||
};
|
||||
|
||||
KeyframeRecording();
|
||||
KeyframeRecordingHandler();
|
||||
|
||||
void newSequence();
|
||||
void addKeyframe(double sequenceTime);
|
||||
void addCameraKeyframe(double sequenceTime);
|
||||
void addScriptKeyframe(double sequenceTime, std::string script);
|
||||
void removeKeyframe(int index);
|
||||
void updateKeyframe(int index);
|
||||
void moveKeyframe(int index, double sequenceTime);
|
||||
bool saveSequence(std::optional<std::string> filename);
|
||||
void loadSequence(std::string filename);
|
||||
void preSynchronization(double dt);
|
||||
void saveSequence(std::filesystem::path filename);
|
||||
void loadSequence(std::filesystem::path filename);
|
||||
void play();
|
||||
void pause();
|
||||
void setSequenceTime(double sequenceTime);
|
||||
void jumpToKeyframe(int index);
|
||||
bool hasKeyframeRecording() const;
|
||||
std::vector<ghoul::Dictionary> keyframes() const;
|
||||
|
||||
static openspace::scripting::LuaLibrary luaLibrary();
|
||||
|
||||
private:
|
||||
void sortKeyframes();
|
||||
|
||||
Keyframe newKeyframe(double sequenceTime);
|
||||
bool isInRange(int index) const;
|
||||
|
||||
std::vector<Keyframe> _keyframes;
|
||||
std::string _filename;
|
||||
bool _isPlaying = false;
|
||||
bool _hasStateChanged = false;
|
||||
double _sequenceTime = 0.0;
|
||||
SessionRecording _timeline;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_CORE___KEYFRAMERECORDING___H__
|
||||
#endif // __OPENSPACE_CORE___KEYFRAMERECORDINGHANDLER___H__
|
||||
@@ -25,863 +25,62 @@
|
||||
#ifndef __OPENSPACE_CORE___SESSIONRECORDING___H__
|
||||
#define __OPENSPACE_CORE___SESSIONRECORDING___H__
|
||||
|
||||
#include <openspace/properties/propertyowner.h>
|
||||
|
||||
#include <openspace/navigation/keyframenavigator.h>
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/scripting/lualibrary.h>
|
||||
#include <ghoul/misc/dictionary.h>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
struct ConversionError : public ghoul::RuntimeError {
|
||||
explicit ConversionError(std::string msg);
|
||||
enum class DataMode {
|
||||
Ascii = 0,
|
||||
Binary
|
||||
};
|
||||
|
||||
class SessionRecording : public properties::PropertyOwner {
|
||||
public:
|
||||
struct SessionRecording {
|
||||
struct Entry {
|
||||
auto operator<=>(const SessionRecording::Entry&) const = default;
|
||||
|
||||
inline static const std::string FileHeaderTitle = "OpenSpace_record/playback";
|
||||
inline static const std::string HeaderCameraAscii = "camera";
|
||||
inline static const std::string HeaderTimeAscii = "time";
|
||||
inline static const std::string HeaderScriptAscii = "script";
|
||||
inline static const std::string HeaderCommentAscii = "#";
|
||||
inline static const char HeaderCameraBinary = 'c';
|
||||
inline static const char HeaderTimeBinary = 't';
|
||||
inline static const char HeaderScriptBinary = 's';
|
||||
inline static const std::string FileExtensionBinary = ".osrec";
|
||||
inline static const std::string FileExtensionAscii = ".osrectxt";
|
||||
using Camera = KeyframeNavigator::CameraPose;
|
||||
using Script = std::string;
|
||||
|
||||
enum class DataMode {
|
||||
Ascii = 0,
|
||||
Binary,
|
||||
Unknown
|
||||
double timestamp = 0.0;
|
||||
double simulationTime = 0.0;
|
||||
std::variant<Camera, Script> value;
|
||||
};
|
||||
|
||||
enum class SessionState {
|
||||
Idle = 0,
|
||||
Recording,
|
||||
Playback,
|
||||
PlaybackPaused
|
||||
};
|
||||
|
||||
struct Timestamps {
|
||||
double timeOs;
|
||||
double timeRec;
|
||||
double timeSim;
|
||||
};
|
||||
|
||||
/**
|
||||
* Struct for storing a script substring that, if found in a saved script, will be
|
||||
* replaced by its substringReplacement counterpart.
|
||||
*/
|
||||
struct ScriptSubstringReplace {
|
||||
std::string substringFound;
|
||||
std::string substringReplacement;
|
||||
ScriptSubstringReplace(std::string found, std::string replace)
|
||||
: substringFound(found)
|
||||
, substringReplacement(replace) {}
|
||||
};
|
||||
|
||||
static const size_t FileHeaderVersionLength = 5;
|
||||
char FileHeaderVersion[FileHeaderVersionLength+1] = "01.00";
|
||||
char TargetConvertVersion[FileHeaderVersionLength+1] = "01.00";
|
||||
static const char DataFormatAsciiTag = 'A';
|
||||
static const char DataFormatBinaryTag = 'B';
|
||||
static const size_t keyframeHeaderSize_bytes = 33;
|
||||
static const size_t saveBufferCameraSize_min = 82;
|
||||
static const size_t saveBufferStringSize_max = 2000;
|
||||
static const size_t _saveBufferMaxSize_bytes = keyframeHeaderSize_bytes +
|
||||
+ saveBufferCameraSize_min + saveBufferStringSize_max;
|
||||
|
||||
using CallbackHandle = int;
|
||||
using StateChangeCallback = std::function<void()>;
|
||||
|
||||
SessionRecording();
|
||||
SessionRecording(bool isGlobal);
|
||||
|
||||
~SessionRecording() override = default;
|
||||
|
||||
/**
|
||||
* Used to de-initialize the session recording feature. Any recording or playback
|
||||
* in progress will be stopped, files closed, and keyframes in memory deleted.
|
||||
*/
|
||||
void deinitialize();
|
||||
|
||||
/**
|
||||
* This is called with every rendered frame. If in recording state, the camera state
|
||||
* will be saved to the recording file (if its state has changed since last). If in
|
||||
* playback state, the next keyframe will be used (if it is time to do so).
|
||||
*/
|
||||
void preSynchronization();
|
||||
|
||||
/**
|
||||
* If enabled, calling this function will render information about the session
|
||||
* recording that is currently taking place to the screen.
|
||||
*/
|
||||
void render();
|
||||
|
||||
/**
|
||||
* Current time based on playback mode.
|
||||
*/
|
||||
double currentTime() const;
|
||||
|
||||
/**
|
||||
* Fixed delta time set by user for use during saving of frame during playback mode.
|
||||
*/
|
||||
double fixedDeltaTimeDuringFrameOutput() const;
|
||||
|
||||
/**
|
||||
* Returns the number of microseconds that have elapsed since playback started, if
|
||||
* playback is set to be in the mode where a screenshot is captured with every
|
||||
* rendered frame (enableTakeScreenShotDuringPlayback() is used to enable this mode).
|
||||
* At the start of playback, this timer is set to the current steady_clock value.
|
||||
* However, during playback it is incremented by the fixed framerate of the playback
|
||||
* rather than the actual clock value (as in normal operation).
|
||||
*
|
||||
* \return Number of microseconds elapsed since playback started in terms of the
|
||||
* number of rendered frames multiplied by the fixed time increment per frame
|
||||
*/
|
||||
std::chrono::steady_clock::time_point currentPlaybackInterpolationTime() const;
|
||||
|
||||
/**
|
||||
* Returns the simulated application time. This simulated application time is only
|
||||
* used when playback is set to be in the mode where a screenshot is captured with
|
||||
* every rendered frame (enableTakeScreenShotDuringPlayback() is used to enable this
|
||||
* mode). At the start of playback, this timer is set to the value of the current
|
||||
* applicationTime function provided by the window delegate (used during normal mode
|
||||
* or playback). However, during playback it is incremented by the fixed framerate of
|
||||
* the playback rather than the actual clock value.
|
||||
*
|
||||
* \return Application time in seconds, for use in playback-with-frames mode
|
||||
*/
|
||||
double currentApplicationInterpolationTime() const;
|
||||
|
||||
/**
|
||||
* Starts a recording session, which will save data to the provided filename according
|
||||
* to the data format specified, and will continue until recording is stopped using
|
||||
* stopRecording() method.
|
||||
*
|
||||
* \param filename File saved with recorded keyframes
|
||||
* \return `true` if recording to file starts without errors
|
||||
*/
|
||||
bool startRecording(const std::string& filename);
|
||||
|
||||
/**
|
||||
* Starts a recording session, which will save data to the provided filename in ASCII
|
||||
* data format until recording is stopped using stopRecording() method.
|
||||
*
|
||||
* \param dataMode The format in which the session recording is stored
|
||||
*/
|
||||
void setRecordDataFormat(DataMode dataMode);
|
||||
|
||||
/**
|
||||
* Used to stop a recording in progress. If open, the recording file will be closed,
|
||||
* and all keyframes deleted from memory.
|
||||
*/
|
||||
void stopRecording();
|
||||
|
||||
/**
|
||||
* Used to check if a session recording is in progress.
|
||||
*
|
||||
* \return `true` if recording is in progress
|
||||
*/
|
||||
bool isRecording() const;
|
||||
|
||||
/**
|
||||
* Starts a playback session, which can run in one of three different time modes.
|
||||
*
|
||||
* \param filename File containing recorded keyframes to play back. The file path is
|
||||
* relative to the base recordings directory specified in the config
|
||||
* file by the RECORDINGS variable
|
||||
* \param timeMode Which of the 3 time modes to use for time reference during
|
||||
* \param forceSimTimeAtStart If true simulation time is forced to that of playback
|
||||
* playback: recorded time, application time, or simulation time. See the
|
||||
* LuaLibrary entry for SessionRecording for details on these time modes
|
||||
* \param loop If true then the file will playback in loop mode, continuously looping
|
||||
* back to the beginning until it is manually stopped
|
||||
* \param shouldWaitForFinishedTiles If true, the playback will wait for tiles to be
|
||||
* finished before progressing to the next frame. This value is only used when
|
||||
* `enableTakeScreenShotDuringPlayback` was called before. Otherwise this value
|
||||
* will be ignored
|
||||
*
|
||||
* \return `true` if recording to file starts without errors
|
||||
*/
|
||||
bool startPlayback(std::string& filename, KeyframeTimeRef timeMode,
|
||||
bool forceSimTimeAtStart, bool loop, bool shouldWaitForFinishedTiles);
|
||||
|
||||
/**
|
||||
* Used to stop a playback in progress. If open, the playback file will be closed, and
|
||||
* all keyframes deleted from memory.
|
||||
*/
|
||||
void stopPlayback();
|
||||
|
||||
/**
|
||||
* Returns playback pause status.
|
||||
*
|
||||
* \return `true` if playback is paused
|
||||
*/
|
||||
bool isPlaybackPaused();
|
||||
|
||||
/**
|
||||
* Pauses a playback session. This does both the normal pause functionality of setting
|
||||
* simulation delta time to zero, and pausing the progression through the timeline.
|
||||
*
|
||||
* \param pause If `true`, then will set playback timeline progression to zero
|
||||
*/
|
||||
void setPlaybackPause(bool pause);
|
||||
|
||||
/**
|
||||
* Enables that rendered frames should be saved during playback.
|
||||
*
|
||||
* \param fps Number of frames per second.
|
||||
*/
|
||||
void enableTakeScreenShotDuringPlayback(int fps);
|
||||
|
||||
/**
|
||||
* Used to disable that renderings are saved during playback.
|
||||
*/
|
||||
void disableTakeScreenShotDuringPlayback();
|
||||
|
||||
/**
|
||||
* Used to check if a session playback is in progress.
|
||||
*
|
||||
* \return `true` if playback is in progress
|
||||
*/
|
||||
bool isPlayingBack() const;
|
||||
|
||||
/**
|
||||
* Is saving frames during playback.
|
||||
*/
|
||||
bool isSavingFramesDuringPlayback() const;
|
||||
|
||||
bool shouldWaitForTileLoading() const;
|
||||
|
||||
/**
|
||||
* Used to obtain the state of idle/recording/playback.
|
||||
*
|
||||
* \return int value of state as defined by struct SessionState
|
||||
*/
|
||||
SessionState state() const;
|
||||
|
||||
/**
|
||||
* Used to trigger a save of the camera states (position, rotation, focus node,
|
||||
* whether it is following the rotation of a node, and timestamp). The data will be
|
||||
* saved to the recording file only if a recording is currently in progress.
|
||||
*/
|
||||
void saveCameraKeyframeToTimeline();
|
||||
|
||||
/**
|
||||
* Used to trigger a save of the current timing states. The data will be saved to the
|
||||
* recording file only if a recording is currently in progress.
|
||||
*/
|
||||
void saveTimeKeyframeToTimeline();
|
||||
|
||||
/**
|
||||
* Used to trigger a save of a script to the recording file, but only if a recording
|
||||
* is currently in progress.
|
||||
*
|
||||
* \param script String of the Lua command to be saved
|
||||
*/
|
||||
void saveScriptKeyframeToTimeline(std::string script);
|
||||
|
||||
/**
|
||||
* \return The Lua library that contains all Lua functions available to affect the
|
||||
* interaction
|
||||
*/
|
||||
static openspace::scripting::LuaLibrary luaLibrary();
|
||||
|
||||
/**
|
||||
* Used to request a callback for notification of playback state change.
|
||||
*
|
||||
* \param cb Function handle for callback
|
||||
* \return CallbackHandle value of callback number
|
||||
*/
|
||||
CallbackHandle addStateChangeCallback(StateChangeCallback cb);
|
||||
|
||||
/**
|
||||
* Removes the callback for notification of playback state change.
|
||||
*
|
||||
* \param callback Function handle for the callback
|
||||
*/
|
||||
void removeStateChangeCallback(CallbackHandle handle);
|
||||
|
||||
/**
|
||||
* Provides list of available playback files.
|
||||
*
|
||||
* \return Vector of filenames in recordings dir
|
||||
*/
|
||||
std::vector<std::string> playbackList() const;
|
||||
|
||||
/**
|
||||
* Reads a camera keyframe from a binary format playback file, and populates input
|
||||
* references with the parameters of the keyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a camera keyframe which contains camera details
|
||||
* \param file An ifstream reference to the playback file being read
|
||||
* \param lineN Keyframe number in playback file where this keyframe resides
|
||||
* \return `true` if data read has no errors
|
||||
*/
|
||||
bool readCameraKeyframeBinary(Timestamps& times,
|
||||
datamessagestructures::CameraKeyframe& kf, std::ifstream& file, int lineN);
|
||||
|
||||
/**
|
||||
* Reads a camera keyframe from an ascii format playback file, and populates input
|
||||
* references with the parameters of the keyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a camera keyframe which contains camera details
|
||||
* \param currentParsingLine String containing the most current line that was read
|
||||
* \param lineN Line number in playback file where this keyframe resides
|
||||
* \return `true` if data read has no errors
|
||||
*/
|
||||
bool readCameraKeyframeAscii(Timestamps& times,
|
||||
datamessagestructures::CameraKeyframe& kf, const std::string& currentParsingLine,
|
||||
int lineN);
|
||||
|
||||
/**
|
||||
* Reads a time keyframe from a binary format playback file, and populates input
|
||||
* references with the parameters of the keyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a time keyframe which contains time details
|
||||
* \param file An ifstream reference to the playback file being read
|
||||
* \param lineN Keyframe number in playback file where this keyframe resides
|
||||
* \return `true` if data read has no errors
|
||||
*/
|
||||
bool readTimeKeyframeBinary(Timestamps& times,
|
||||
datamessagestructures::TimeKeyframe& kf, std::ifstream& file, int lineN);
|
||||
|
||||
/**
|
||||
* Reads a time keyframe from an ascii format playback file, and populates input
|
||||
* references with the parameters of the keyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a time keyframe which contains time details
|
||||
* \param currentParsingLine String containing the most current line that was read
|
||||
* \param lineN Line number in playback file where this keyframe resides
|
||||
* \return `true` if data read has no errors
|
||||
*/
|
||||
bool readTimeKeyframeAscii(Timestamps& times,
|
||||
datamessagestructures::TimeKeyframe& kf, const std::string& currentParsingLine,
|
||||
int lineN);
|
||||
|
||||
/**
|
||||
* Reads a script keyframe from a binary format playback file, and populates input
|
||||
* references with the parameters of the keyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a script keyframe which contains the size of the script (in
|
||||
* chars) and the text itself
|
||||
* \param file An ifstream reference to the playback file being read
|
||||
* \param lineN Keyframe number in playback file where this keyframe resides
|
||||
* \return `true` if data read has no errors
|
||||
*/
|
||||
bool readScriptKeyframeBinary(Timestamps& times,
|
||||
datamessagestructures::ScriptMessage& kf, std::ifstream& file, int lineN);
|
||||
|
||||
/**
|
||||
* Reads a script keyframe from an ascii format playback file, and populates input
|
||||
* references with the parameters of the keyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a script keyframe which contains the size of the script (in
|
||||
* chars) and the text itself
|
||||
* \param currentParsingLine String containing the most current line that was read
|
||||
* \param lineN Line number in playback file where this keyframe resides
|
||||
* \return `true` if data read has no errors
|
||||
*/
|
||||
bool readScriptKeyframeAscii(Timestamps& times,
|
||||
datamessagestructures::ScriptMessage& kf, const std::string& currentParsingLine,
|
||||
int lineN);
|
||||
|
||||
/**
|
||||
* Writes a camera keyframe to a binary format recording file using a CameraKeyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a camera keyframe which contains the camera details
|
||||
* \param kfBuffer A buffer temporarily used for preparing data to be written
|
||||
* \param file An ofstream reference to the recording file being written-to
|
||||
*/
|
||||
void saveCameraKeyframeBinary(Timestamps& times,
|
||||
datamessagestructures::CameraKeyframe& kf, unsigned char* kfBuffer,
|
||||
std::ofstream& file);
|
||||
|
||||
/**
|
||||
* Writes a camera keyframe to an ascii format recording file using a CameraKeyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a camera keyframe which contains the camera details
|
||||
* \param file An ofstream reference to the recording file being written-to
|
||||
*/
|
||||
void saveCameraKeyframeAscii(Timestamps& times,
|
||||
datamessagestructures::CameraKeyframe& kf, std::ofstream& file);
|
||||
|
||||
/**
|
||||
* Writes a time keyframe to a binary format recording file using a TimeKeyframe
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a time keyframe which contains the time details
|
||||
* \param kfBuffer A buffer temporarily used for preparing data to be written
|
||||
* \param file An ofstream reference to the recording file being written-to
|
||||
*/
|
||||
void saveTimeKeyframeBinary(Timestamps& times,
|
||||
datamessagestructures::TimeKeyframe& kf, unsigned char* kfBuffer,
|
||||
std::ofstream& file);
|
||||
|
||||
/**
|
||||
* Writes a time keyframe to an ascii format recording file using a TimeKeyframe.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param kf Reference to a time keyframe which contains the time details
|
||||
* \param file An ofstream reference to the recording file being written-to
|
||||
*/
|
||||
void saveTimeKeyframeAscii(Timestamps& times,
|
||||
datamessagestructures::TimeKeyframe& kf, std::ofstream& file);
|
||||
|
||||
/**
|
||||
* Writes a script keyframe to a binary format recording file using a ScriptMessage.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param sm Reference to a ScriptMessage object which contains the script details
|
||||
* \param smBuffer A buffer temporarily used for preparing data to be written
|
||||
* \param file An ofstream reference to the recording file being written-to
|
||||
*/
|
||||
void saveScriptKeyframeBinary(Timestamps& times,
|
||||
datamessagestructures::ScriptMessage& sm, unsigned char* smBuffer,
|
||||
std::ofstream& file);
|
||||
|
||||
/**
|
||||
* Writes a script keyframe to an ascii format recording file using a ScriptMessage.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param sm Reference to a ScriptMessage which contains the script details
|
||||
* \param file An ofstream reference to the recording file being written-to
|
||||
*/
|
||||
void saveScriptKeyframeAscii(Timestamps& times,
|
||||
datamessagestructures::ScriptMessage& sm, std::ofstream& file);
|
||||
|
||||
/**
|
||||
* Since session recordings only record changes, the initial conditions aren't
|
||||
* preserved when a playback starts. This function is called whenever a property value
|
||||
* is set and a recording is in progress. Before the set happens, this function will
|
||||
* read the current value of the property and store it so that when the recording is
|
||||
* finished, the initial state will be added as a set property command at the
|
||||
* beginning of the recording file, to be applied when playback starts.
|
||||
*
|
||||
* \param prop The property being set
|
||||
*/
|
||||
void savePropertyBaseline(properties::Property& prop);
|
||||
|
||||
/**
|
||||
* Reads header information from a session recording file.
|
||||
*
|
||||
* \param stream Reference to ifstream that contains the session recording file data
|
||||
* \param readLen_chars Number of characters to be read, which may be the expected
|
||||
* length of the header line, or an arbitrary number of characters within it
|
||||
*/
|
||||
static std::string readHeaderElement(std::ifstream& stream, size_t readLenChars);
|
||||
|
||||
/**
|
||||
* Reads header information from a session recording file.
|
||||
*
|
||||
* \param stream Reference to ifstream that contains the session recording file data
|
||||
* \param readLen_chars Number of characters to be read, which may be the expected
|
||||
* length of the header line, or an arbitrary number of characters within it
|
||||
*/
|
||||
static std::string readHeaderElement(std::stringstream& stream, size_t readLenChars);
|
||||
|
||||
/**
|
||||
* Writes a header to a binary recording file buffer.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param type Single character signifying the keyframe type
|
||||
* \param kfBuffer The char buffer holding the recording info to be written
|
||||
* \param idx Index into write buffer (this is updated with the num of chars written)
|
||||
*/
|
||||
static void saveHeaderBinary(Timestamps& times, char type, unsigned char* kfBuffer,
|
||||
size_t& idx);
|
||||
|
||||
/**
|
||||
* Writes a header to an ASCII recording file buffer.
|
||||
*
|
||||
* \param times Reference to a timestamps structure which contains recorded times
|
||||
* \param type String signifying the keyframe type
|
||||
* \param line The stringstream buffer being written to
|
||||
*/
|
||||
static void saveHeaderAscii(Timestamps& times, const std::string& type,
|
||||
std::stringstream& line);
|
||||
|
||||
/**
|
||||
* Saves a keyframe to an ASCII recording file.
|
||||
*
|
||||
* \param entry The ASCII string version of the keyframe (any type)
|
||||
* \param file `std::ofstream` object to write to
|
||||
*/
|
||||
static void saveKeyframeToFile(const std::string& entry, std::ofstream& file);
|
||||
|
||||
/**
|
||||
* Checks if a specified recording file ends with a particular file extension.
|
||||
*
|
||||
* \param filename The name of the file to record to
|
||||
* \param extension The file extension to check for
|
||||
*/
|
||||
static bool hasFileExtension(const std::string& filename,
|
||||
const std::string& extension);
|
||||
|
||||
/**
|
||||
* Converts file format of a session recording file to the current format version
|
||||
* (will determine the file format conversion to convert from based on the file's
|
||||
* header version number).
|
||||
*
|
||||
* \param filename Name of the file to convert
|
||||
* \param depth iteration number to prevent runaway recursion (init call with zero)
|
||||
* \return String containing the filename of the previous conversion step. This is
|
||||
* used if there are multiple conversion steps where each step has to use
|
||||
* the output of the previous.
|
||||
*/
|
||||
std::string convertFile(std::string filename, int depth = 0);
|
||||
|
||||
/**
|
||||
* Converts file format of a session recording file to the current format version
|
||||
* (will determine the file format conversion to convert from based on the file's
|
||||
* header version number). Accepts a relative path (currently from task runner dir)
|
||||
* rather than a path assumed to be relative to `${RECORDINGS}`.
|
||||
*
|
||||
* \param filenameRelative name of the file to convert
|
||||
*/
|
||||
void convertFileRelativePath(std::string filenameRelative);
|
||||
|
||||
/**
|
||||
* Goes to legacy session recording inherited class, and calls its #convertFile
|
||||
* method, and then returns the resulting conversion filename.
|
||||
*
|
||||
* \param filename Name of the file to convert
|
||||
* \param depth Iteration number to prevent runaway recursion (init call with zero)
|
||||
* \return string containing the filename of the conversion
|
||||
*/
|
||||
virtual std::string getLegacyConversionResult(std::string filename, int depth);
|
||||
|
||||
/**
|
||||
* Version string for file format version currently supported by this class.
|
||||
*
|
||||
* \return string of the file format version this class supports
|
||||
*/
|
||||
virtual std::string fileFormatVersion();
|
||||
|
||||
/**
|
||||
* Version string for file format version that a conversion operation will convert to
|
||||
* (e.g. upgrades to this version). This is only relevant for inherited classes that
|
||||
* support conversion from legacy versions (if called in the base class, it will
|
||||
* return its own current version).
|
||||
*
|
||||
* \return string of the file format version this class supports
|
||||
*/
|
||||
virtual std::string targetFileFormatVersion();
|
||||
|
||||
/**
|
||||
* Determines a filename for the conversion result based on the original filename and
|
||||
the file format version number.
|
||||
*
|
||||
* \param filename source filename to be converted
|
||||
* \param mode Whether the file is binary or text-based
|
||||
*
|
||||
* \return pathname of the converted version of the file
|
||||
*/
|
||||
std::string determineConversionOutFilename(const std::string& filename,
|
||||
DataMode mode);
|
||||
|
||||
protected:
|
||||
properties::BoolProperty _renderPlaybackInformation;
|
||||
properties::BoolProperty _ignoreRecordedScale;
|
||||
properties::BoolProperty _addModelMatrixinAscii;
|
||||
|
||||
enum class RecordedType {
|
||||
Camera = 0,
|
||||
Time,
|
||||
Script,
|
||||
Invalid
|
||||
};
|
||||
struct TimelineEntry {
|
||||
RecordedType keyframeType;
|
||||
unsigned int idxIntoKeyframeTypeArray;
|
||||
Timestamps t3stamps;
|
||||
};
|
||||
double _timestampRecordStarted = 0.0;
|
||||
Timestamps _timestamps3RecordStarted{ 0.0, 0.0, 0.0 };
|
||||
double _timestampPlaybackStarted_application = 0.0;
|
||||
double _timestampPlaybackStarted_simulation = 0.0;
|
||||
double _timestampApplicationStarted_simulation = 0.0;
|
||||
bool hasCameraChangedFromPrev(const datamessagestructures::CameraKeyframe& kfNew);
|
||||
double appropriateTimestamp(Timestamps t3stamps);
|
||||
double equivalentSimulationTime(double timeOs, double timeRec, double timeSim);
|
||||
double equivalentApplicationTime(double timeOs, double timeRec, double timeSim);
|
||||
void recordCurrentTimePauseState();
|
||||
void recordCurrentTimeRate();
|
||||
bool handleRecordingFile(std::string filenameIn);
|
||||
static bool isPath(std::string& filename);
|
||||
void removeTrailingPathSlashes(std::string& filename) const;
|
||||
bool playbackCamera();
|
||||
bool playbackTimeChange();
|
||||
bool playbackScript();
|
||||
bool playbackAddEntriesToTimeline();
|
||||
void signalPlaybackFinishedForComponent(RecordedType type);
|
||||
void handlePlaybackEnd();
|
||||
|
||||
bool findFirstCameraKeyframeInTimeline();
|
||||
Timestamps generateCurrentTimestamp3(double keyframeTime) const;
|
||||
static void saveStringToFile(const std::string& s, unsigned char* kfBuffer,
|
||||
size_t& idx, std::ofstream& file);
|
||||
static void saveKeyframeToFileBinary(unsigned char* buffer, size_t size,
|
||||
std::ofstream& file);
|
||||
|
||||
bool addKeyframe(Timestamps t3stamps,
|
||||
interaction::KeyframeNavigator::CameraPose keyframe, int lineNum);
|
||||
bool addKeyframe(Timestamps t3stamps,
|
||||
datamessagestructures::TimeKeyframe keyframe, int lineNum);
|
||||
bool addKeyframe(Timestamps t3stamps,
|
||||
std::string scriptToQueue, int lineNum);
|
||||
bool addKeyframeToTimeline(std::vector<TimelineEntry>& timeline, RecordedType type,
|
||||
size_t indexIntoTypeKeyframes, Timestamps t3stamps, int lineNum);
|
||||
|
||||
void initializePlayback_time(double now);
|
||||
void initializePlayback_modeFlags();
|
||||
bool initializePlayback_timeline();
|
||||
void initializePlayback_triggerStart();
|
||||
void moveAheadInTime();
|
||||
void lookForNonCameraKeyframesThatHaveComeDue(double currTime);
|
||||
void updateCameraWithOrWithoutNewKeyframes(double currTime);
|
||||
bool isTimeToHandleNextNonCameraKeyframe(double currTime);
|
||||
bool processNextNonCameraKeyframeAheadInTime();
|
||||
bool findNextFutureCameraIndex(double currTime);
|
||||
bool processCameraKeyframe(double now);
|
||||
bool processScriptKeyframe();
|
||||
bool readSingleKeyframeCamera(datamessagestructures::CameraKeyframe& kf,
|
||||
Timestamps& times, DataMode mode, std::ifstream& file,
|
||||
std::string& inLine, const int lineNum);
|
||||
void saveSingleKeyframeCamera(datamessagestructures::CameraKeyframe& kf,
|
||||
Timestamps& times, DataMode mode, std::ofstream& file, unsigned char* buffer);
|
||||
bool readSingleKeyframeTime(datamessagestructures::TimeKeyframe& kf,
|
||||
Timestamps& times, DataMode mode, std::ifstream& file, std::string& inLine,
|
||||
const int lineNum);
|
||||
void saveSingleKeyframeTime(datamessagestructures::TimeKeyframe& kf,
|
||||
Timestamps& times, DataMode mode, std::ofstream& file, unsigned char* buffer);
|
||||
bool readSingleKeyframeScript(datamessagestructures::ScriptMessage& kf,
|
||||
Timestamps& times, DataMode mode, std::ifstream& file, std::string& inLine,
|
||||
const int lineNum);
|
||||
void saveSingleKeyframeScript(datamessagestructures::ScriptMessage& kf,
|
||||
Timestamps& times, DataMode mode, std::ofstream& file, unsigned char* buffer);
|
||||
void saveScriptKeyframeToPropertiesBaseline(std::string script);
|
||||
bool isPropertyAllowedForBaseline(const std::string& propString);
|
||||
unsigned int findIndexOfLastCameraKeyframeInTimeline();
|
||||
bool doesTimelineEntryContainCamera(unsigned int index) const;
|
||||
void trimCommandsFromScriptIfFound(std::string& script);
|
||||
void replaceCommandsFromScriptIfFound(std::string& script);
|
||||
|
||||
RecordedType getNextKeyframeType();
|
||||
RecordedType getPrevKeyframeType();
|
||||
double getNextTimestamp();
|
||||
double getPrevTimestamp();
|
||||
void cleanUpPlayback();
|
||||
void cleanUpRecording();
|
||||
void cleanUpTimelinesAndKeyframes();
|
||||
bool convertEntries(std::string& inFilename, std::stringstream& inStream,
|
||||
DataMode mode, int lineNum, std::ofstream& outFile);
|
||||
virtual bool convertCamera(std::stringstream& inStream, DataMode mode, int lineNum,
|
||||
std::string& inputLine, std::ofstream& outFile, unsigned char* buffer);
|
||||
virtual bool convertTimeChange(std::stringstream& inStream, DataMode mode,
|
||||
int lineNum, std::string& inputLine, std::ofstream& outFile,
|
||||
unsigned char* buffer);
|
||||
virtual bool convertScript(std::stringstream& inStream, DataMode mode, int lineNum,
|
||||
std::string& inputLine, std::ofstream& outFile, unsigned char* buffer);
|
||||
DataMode readModeFromHeader(const std::string& filename);
|
||||
void readPlaybackHeader_stream(std::stringstream& conversionInStream,
|
||||
std::string& version, DataMode& mode);
|
||||
void populateListofLoadedSceneGraphNodes();
|
||||
|
||||
void checkIfScriptUsesScenegraphNode(std::string s);
|
||||
bool checkForScenegraphNodeAccessScene(const std::string& s);
|
||||
bool checkForScenegraphNodeAccessNav(std::string& navTerm);
|
||||
std::string extractScenegraphNodeFromScene(const std::string& s);
|
||||
bool checkIfInitialFocusNodeIsLoaded(unsigned int firstCamIndex);
|
||||
std::string isolateTermFromQuotes(std::string s);
|
||||
void eraseSpacesFromString(std::string& s);
|
||||
std::string getNameFromSurroundingQuotes(std::string& s);
|
||||
|
||||
static void writeToFileBuffer(unsigned char* buf, size_t& idx, double src);
|
||||
static void writeToFileBuffer(unsigned char* buf, size_t& idx, std::vector<char>& cv);
|
||||
static void writeToFileBuffer(unsigned char* buf, size_t& idx, unsigned char c);
|
||||
static void writeToFileBuffer(unsigned char* buf, size_t& idx, bool b);
|
||||
void readFileIntoStringStream(std::string filename,
|
||||
std::ifstream& inputFstream, std::stringstream& stream);
|
||||
|
||||
DataMode _recordingDataMode = DataMode::Binary;
|
||||
SessionState _state = SessionState::Idle;
|
||||
SessionState _lastState = SessionState::Idle;
|
||||
std::string _playbackFilename;
|
||||
std::ifstream _playbackFile;
|
||||
std::string _playbackLineParsing;
|
||||
std::ofstream _recordFile;
|
||||
int _playbackLineNum = 1;
|
||||
int _recordingEntryNum = 1;
|
||||
KeyframeTimeRef _playbackTimeReferenceMode;
|
||||
datamessagestructures::CameraKeyframe _prevRecordedCameraKeyframe;
|
||||
bool _playbackActive_camera = false;
|
||||
bool _playbackActive_time = false;
|
||||
bool _playbackActive_script = false;
|
||||
bool _hasHitEndOfCameraKeyframes = false;
|
||||
bool _playbackPausedWithinDeltaTimePause = false;
|
||||
bool _playbackLoopMode = false;
|
||||
bool _playbackForceSimTimeAtStart = false;
|
||||
double _playbackPauseOffset = 0.0;
|
||||
double _previousTime = 0.0;
|
||||
|
||||
bool _saveRenderingDuringPlayback = false;
|
||||
double _saveRenderingDeltaTime = 1.0 / 30.0;
|
||||
double _saveRenderingCurrentRecordedTime = 0.0;
|
||||
bool _shouldWaitForFinishLoadingWhenPlayback = false;
|
||||
std::chrono::steady_clock::duration _saveRenderingDeltaTime_interpolation_usec;
|
||||
std::chrono::steady_clock::time_point _saveRenderingCurrentRecordedTime_interpolation;
|
||||
double _saveRenderingCurrentApplicationTime_interpolation = 0.0;
|
||||
long long _saveRenderingClockInterpolation_countsPerSec = 1;
|
||||
bool _saveRendering_isFirstFrame = true;
|
||||
|
||||
unsigned char _keyframeBuffer[_saveBufferMaxSize_bytes];
|
||||
|
||||
bool _cleanupNeededRecording = false;
|
||||
bool _cleanupNeededPlayback = false;
|
||||
const std::string scriptReturnPrefix = "return ";
|
||||
|
||||
std::vector<interaction::KeyframeNavigator::CameraPose> _keyframesCamera;
|
||||
std::vector<datamessagestructures::TimeKeyframe> _keyframesTime;
|
||||
std::vector<std::string> _keyframesScript;
|
||||
std::vector<TimelineEntry> _timeline;
|
||||
|
||||
std::vector<std::string> _keyframesSavePropertiesBaseline_scripts;
|
||||
std::vector<TimelineEntry> _keyframesSavePropertiesBaseline_timeline;
|
||||
std::vector<std::string> _propertyBaselinesSaved;
|
||||
const std::vector<std::string> _propertyBaselineRejects = {
|
||||
"NavigationHandler.OrbitalNavigator.Anchor",
|
||||
"NavigationHandler.OrbitalNavigator.Aim",
|
||||
"NavigationHandler.OrbitalNavigator.RetargetAnchor",
|
||||
"NavigationHandler.OrbitalNavigator.RetargetAim"
|
||||
};
|
||||
|
||||
//A script that begins with an exact match of any of the strings contained in
|
||||
// _scriptRejects will not be recorded
|
||||
const std::vector<std::string> _scriptRejects = {
|
||||
"openspace.sessionRecording.enableTakeScreenShotDuringPlayback",
|
||||
"openspace.sessionRecording.startPlayback",
|
||||
"openspace.sessionRecording.stopPlayback",
|
||||
"openspace.sessionRecording.startRecording",
|
||||
"openspace.sessionRecording.stopRecording",
|
||||
"openspace.scriptScheduler.clear"
|
||||
};
|
||||
const std::vector<std::string> _navScriptsUsingNodes = {
|
||||
"RetargetAnchor",
|
||||
"Anchor",
|
||||
"Aim"
|
||||
};
|
||||
|
||||
//Any script snippet included in this vector will be trimmed from any script
|
||||
// from the script manager, before it is recorded in the session recording file.
|
||||
// The remainder of the script will be retained.
|
||||
const std::vector<std::string> _scriptsToBeTrimmed = {
|
||||
"openspace.sessionRecording.togglePlaybackPause"
|
||||
};
|
||||
|
||||
//Any script snippet included in this vector will be trimmed from any script
|
||||
// from the script manager, before it is recorded in the session recording file.
|
||||
// The remainder of the script will be retained.
|
||||
const std::vector<ScriptSubstringReplace> _scriptsToBeReplaced = {
|
||||
{
|
||||
"openspace.time.pauseToggleViaKeyboard",
|
||||
"openspace.time.interpolateTogglePause"
|
||||
}
|
||||
};
|
||||
std::vector<std::string> _loadedNodes;
|
||||
|
||||
unsigned int _idxTimeline_nonCamera = 0;
|
||||
unsigned int _idxTime = 0;
|
||||
unsigned int _idxScript = 0;
|
||||
|
||||
unsigned int _idxTimeline_cameraPtrNext = 0;
|
||||
unsigned int _idxTimeline_cameraPtrPrev = 0;
|
||||
|
||||
unsigned int _idxTimeline_cameraFirstInTimeline = 0;
|
||||
double _cameraFirstInTimeline_timestamp = 0;
|
||||
|
||||
int _nextCallbackHandle = 0;
|
||||
std::vector<std::pair<CallbackHandle, StateChangeCallback>> _stateChangeCallbacks;
|
||||
|
||||
DataMode _conversionDataMode = DataMode::Binary;
|
||||
int _conversionLineNum = 1;
|
||||
const int _maximumRecursionDepth = 50;
|
||||
};
|
||||
|
||||
// Instructions for bumping the file format version with new changes:
|
||||
//
|
||||
// 1. Create a new subclass with the current version # in its name, such as:
|
||||
// SessionRecording_legacy_####, which inherits from SessionRecording
|
||||
// 2. Override any method that changes in the new version. This includes both
|
||||
// methods in SessionRecording class and structs in
|
||||
// openspace::datamessagestructure that do data read/writes. Make the modified
|
||||
// method/struct virtual, and override it in the new legacy subclass. This
|
||||
// override will contain the code as it is before the new changes. This will
|
||||
// need to be done in every legacy subclass/struct that exists, but only if that
|
||||
// subclass does NOT already contain an override of that method/struct.
|
||||
// 3. Override FileHeaderVersion with the version # of the new subclass (which is
|
||||
// the version being replaced by the new changes).
|
||||
// 4. Override TargetConvertVersion with the version # with the new changes. This
|
||||
// is now the version that this legacy subclass converts up to.
|
||||
// 5. Override getLegacyConversionResult method so that it creates an instance of
|
||||
// the new version subclass. This is how the current version looks back to the
|
||||
// legacy version that preceded it.
|
||||
// 6. The convert method for frame types that changed will need to be changed
|
||||
// (for example SessionRecording_legacy_0085::convertScript uses its own
|
||||
// override of script keyframe for the conversion functionality).
|
||||
|
||||
class SessionRecording_legacy_0085 : public SessionRecording {
|
||||
public:
|
||||
SessionRecording_legacy_0085() : SessionRecording() {}
|
||||
~SessionRecording_legacy_0085() override {}
|
||||
char FileHeaderVersion[FileHeaderVersionLength+1] = "00.85";
|
||||
char TargetConvertVersion[FileHeaderVersionLength+1] = "01.00";
|
||||
std::string fileFormatVersion() override {
|
||||
return std::string(FileHeaderVersion);
|
||||
}
|
||||
std::string targetFileFormatVersion() override {
|
||||
return std::string(TargetConvertVersion);
|
||||
}
|
||||
std::string getLegacyConversionResult(std::string filename, int depth) override;
|
||||
|
||||
struct ScriptMessage_legacy_0085 : public datamessagestructures::ScriptMessage {
|
||||
void read(std::istream* in) override {
|
||||
size_t strLen;
|
||||
//Read string length from file
|
||||
in->read(reinterpret_cast<char*>(&strLen), sizeof(strLen));
|
||||
if (strLen > saveBufferStringSize_max) {
|
||||
throw ConversionError("Invalid script size for conversion read");
|
||||
auto operator<=>(const SessionRecording&) const = default;
|
||||
|
||||
std::vector<Entry> entries;
|
||||
|
||||
bool hasCameraFrame() const noexcept;
|
||||
|
||||
// Call the provided \p function for all entries of the specified type \tparam T. The
|
||||
// function calls will be ordered by the entries timestamps. If the callback function
|
||||
// returns `true`, the loop is aborted
|
||||
template <typename T>
|
||||
void forAll(std::function<bool (const T&)> function) {
|
||||
for (const Entry& e : entries) {
|
||||
if (std::holds_alternative<T>(e.value)) {
|
||||
bool cont = function(std::get<T>(e.value));
|
||||
if (cont) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Read back full string
|
||||
std::vector<char> temp(strLen + 1);
|
||||
in->read(temp.data(), strLen);
|
||||
temp[strLen] = '\0';
|
||||
|
||||
_script.erase();
|
||||
_script = temp.data();
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
bool convertScript(std::stringstream& inStream, DataMode mode, int lineNum,
|
||||
std::string& inputLine, std::ofstream& outFile, unsigned char* buffer) override;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
SessionRecording loadSessionRecording(const std::filesystem::path& filename);
|
||||
void saveSessionRecording(const std::filesystem::path& filename,
|
||||
const SessionRecording& sessionRecording, DataMode dataMode);
|
||||
|
||||
#include "sessionrecording.inl"
|
||||
std::vector<ghoul::Dictionary> sessionRecordingToDictionary(
|
||||
const SessionRecording& recording);
|
||||
|
||||
#endif // __OPENSPACE_CORE___SESSIONRECORDING___H__
|
||||
} // namespace openspace::interaction
|
||||
|
||||
#endif // __OPENSPACE_CORE___SESSIONRECORDINGHANDLER___H__
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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::interaction {
|
||||
|
||||
template <class T>
|
||||
T nextKeyframeObj(unsigned int index, const std::vector<T>& keyframeContainer,
|
||||
std::function<void()> finishedCallback)
|
||||
{
|
||||
if (index >= (keyframeContainer.size() - 1)) {
|
||||
if (index == (keyframeContainer.size() - 1)) {
|
||||
finishedCallback();
|
||||
}
|
||||
return keyframeContainer.back();
|
||||
}
|
||||
else if (index < keyframeContainer.size()) {
|
||||
return keyframeContainer[index];
|
||||
}
|
||||
else {
|
||||
return keyframeContainer.back();
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T prevKeyframeObj(unsigned int index, const std::vector<T>& keyframeContainer) {
|
||||
if (index >= keyframeContainer.size()) {
|
||||
return keyframeContainer.back();
|
||||
}
|
||||
else if (index > 0) {
|
||||
return keyframeContainer[index - 1];
|
||||
}
|
||||
else {
|
||||
return keyframeContainer.front();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T readFromPlayback(std::ifstream& stream) {
|
||||
T res;
|
||||
stream.read(reinterpret_cast<char*>(&res), sizeof(T));
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T readFromPlayback(std::stringstream& stream) {
|
||||
T res;
|
||||
stream.read(reinterpret_cast<char*>(&res), sizeof(T));
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace openspace::interaction
|
||||
288
include/openspace/interaction/sessionrecordinghandler.h
Normal file
288
include/openspace/interaction/sessionrecordinghandler.h
Normal file
@@ -0,0 +1,288 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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___SESSIONRECORDINGHANDLER___H__
|
||||
#define __OPENSPACE_CORE___SESSIONRECORDINGHANDLER___H__
|
||||
|
||||
#include <openspace/properties/propertyowner.h>
|
||||
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/scripting/lualibrary.h>
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
class SessionRecordingHandler : public properties::PropertyOwner {
|
||||
public:
|
||||
enum class SessionState {
|
||||
Idle = 0,
|
||||
Recording,
|
||||
Playback,
|
||||
PlaybackPaused
|
||||
};
|
||||
|
||||
using CallbackHandle = int;
|
||||
using StateChangeCallback = std::function<void()>;
|
||||
|
||||
SessionRecordingHandler();
|
||||
~SessionRecordingHandler() override = default;
|
||||
|
||||
/**
|
||||
* This is called with every rendered frame. If in recording state, the camera state
|
||||
* will be saved to the recording file (if its state has changed since last). If in
|
||||
* playback state, the next keyframe will be used (if it is time to do so).
|
||||
*/
|
||||
void preSynchronization(double dt);
|
||||
|
||||
/**
|
||||
* If enabled, calling this function will render information about the session
|
||||
* recording that is currently taking place to the screen.
|
||||
*/
|
||||
void render() const;
|
||||
|
||||
/**
|
||||
* Fixed delta time set by user for use during saving of frame during playback mode.
|
||||
*/
|
||||
double fixedDeltaTimeDuringFrameOutput() const;
|
||||
|
||||
/**
|
||||
* Returns the number of microseconds that have elapsed since playback started, if
|
||||
* playback is set to be in the mode where a screenshot is captured with every
|
||||
* rendered frame (enableTakeScreenShotDuringPlayback() is used to enable this mode).
|
||||
* At the start of playback, this timer is set to the current steady_clock value.
|
||||
* However, during playback it is incremented by the fixed framerate of the playback
|
||||
* rather than the actual clock value (as in normal operation).
|
||||
*
|
||||
* \return Number of microseconds elapsed since playback started in terms of the
|
||||
* number of rendered frames multiplied by the fixed time increment per frame
|
||||
*/
|
||||
std::chrono::steady_clock::time_point currentPlaybackInterpolationTime() const;
|
||||
|
||||
/**
|
||||
* Returns the simulated application time. This simulated application time is only
|
||||
* used when playback is set to be in the mode where a screenshot is captured with
|
||||
* every rendered frame (enableTakeScreenShotDuringPlayback() is used to enable this
|
||||
* mode). At the start of playback, this timer is set to the value of the current
|
||||
* applicationTime function provided by the window delegate (used during normal mode
|
||||
* or playback). However, during playback it is incremented by the fixed framerate of
|
||||
* the playback rather than the actual clock value.
|
||||
*
|
||||
* \return Application time in seconds, for use in playback-with-frames mode
|
||||
*/
|
||||
double currentApplicationInterpolationTime() const;
|
||||
|
||||
/**
|
||||
* Starts a recording session, which will save data to the provided filename according
|
||||
* to the data format specified, and will continue until recording is stopped using
|
||||
* stopRecording() method.
|
||||
*
|
||||
* \return `true` if recording to file starts without errors
|
||||
*/
|
||||
void startRecording();
|
||||
|
||||
/**
|
||||
* Used to stop a recording in progress. If open, the recording file will be closed,
|
||||
* and all keyframes deleted from memory.
|
||||
* \param filename File saved with recorded keyframes
|
||||
*/
|
||||
void stopRecording(const std::filesystem::path& filename, DataMode dataMode);
|
||||
|
||||
/**
|
||||
* Used to check if a session recording is in progress.
|
||||
*
|
||||
* \return `true` if recording is in progress
|
||||
*/
|
||||
bool isRecording() const;
|
||||
|
||||
/**
|
||||
* Starts a playback session, which can run in one of three different time modes.
|
||||
*
|
||||
* \param filename File containing recorded keyframes to play back. The file path is
|
||||
* relative to the base recordings directory specified in the config
|
||||
* file by the RECORDINGS variable
|
||||
* \param timeMode Which of the 3 time modes to use for time reference during
|
||||
* \param loop If true then the file will playback in loop mode, continuously looping
|
||||
* back to the beginning until it is manually stopped
|
||||
* \param shouldWaitForFinishedTiles If true, the playback will wait for tiles to be
|
||||
* finished before progressing to the next frame. This value is only used when
|
||||
* `enableTakeScreenShotDuringPlayback` was called before. Otherwise this value
|
||||
* will be ignored
|
||||
*/
|
||||
void startPlayback(SessionRecording timeline, bool loop,
|
||||
bool shouldWaitForFinishedTiles, std::optional<int> saveScreenshotFps);
|
||||
|
||||
/**
|
||||
* Used to stop a playback in progress. If open, the playback file will be closed, and
|
||||
* all keyframes deleted from memory.
|
||||
*/
|
||||
void stopPlayback();
|
||||
|
||||
/**
|
||||
* Returns playback pause status.
|
||||
*
|
||||
* \return `true` if playback is paused
|
||||
*/
|
||||
bool isPlaybackPaused() const;
|
||||
|
||||
/**
|
||||
* Pauses a playback session. This does both the normal pause functionality of setting
|
||||
* simulation delta time to zero, and pausing the progression through the timeline.
|
||||
*
|
||||
* \param pause If `true`, then will set playback timeline progression to zero
|
||||
*/
|
||||
void setPlaybackPause(bool pause);
|
||||
|
||||
/**
|
||||
* Enables that rendered frames should be saved during playback.
|
||||
*
|
||||
* \param fps Number of frames per second.
|
||||
*/
|
||||
//void enableTakeScreenShotDuringPlayback(int fps);
|
||||
|
||||
/**
|
||||
* Used to disable that renderings are saved during playback.
|
||||
*/
|
||||
//void disableTakeScreenShotDuringPlayback();
|
||||
|
||||
/**
|
||||
* Used to check if a session playback is in progress.
|
||||
*
|
||||
* \return `true` if playback is in progress
|
||||
*/
|
||||
bool isPlayingBack() const;
|
||||
|
||||
void seek(double recordingTime);
|
||||
|
||||
/**
|
||||
* Is saving frames during playback.
|
||||
*/
|
||||
bool isSavingFramesDuringPlayback() const;
|
||||
|
||||
bool shouldWaitForTileLoading() const;
|
||||
|
||||
/**
|
||||
* Used to obtain the state of idle/recording/playback.
|
||||
*
|
||||
* \return int value of state as defined by struct SessionState
|
||||
*/
|
||||
SessionState state() const;
|
||||
|
||||
/**
|
||||
* Used to trigger a save of a script to the recording file, but only if a recording
|
||||
* is currently in progress.
|
||||
*
|
||||
* \param script String of the Lua command to be saved
|
||||
*/
|
||||
void saveScriptKeyframeToTimeline(std::string script);
|
||||
|
||||
/**
|
||||
* \return The Lua library that contains all Lua functions available to affect the
|
||||
* interaction
|
||||
*/
|
||||
static openspace::scripting::LuaLibrary luaLibrary();
|
||||
|
||||
/**
|
||||
* Used to request a callback for notification of playback state change.
|
||||
*
|
||||
* \param cb Function handle for callback
|
||||
* \return CallbackHandle value of callback number
|
||||
*/
|
||||
CallbackHandle addStateChangeCallback(StateChangeCallback cb);
|
||||
|
||||
/**
|
||||
* Removes the callback for notification of playback state change.
|
||||
*
|
||||
* \param callback Function handle for the callback
|
||||
*/
|
||||
void removeStateChangeCallback(CallbackHandle handle);
|
||||
|
||||
/**
|
||||
* Provides list of available playback files.
|
||||
*
|
||||
* \return Vector of filenames in recordings dir
|
||||
*/
|
||||
std::vector<std::string> playbackList() const;
|
||||
|
||||
/**
|
||||
* Since session recordings only record changes, the initial conditions aren't
|
||||
* preserved when a playback starts. This function is called whenever a property value
|
||||
* is set and a recording is in progress. Before the set happens, this function will
|
||||
* read the current value of the property and store it so that when the recording is
|
||||
* finished, the initial state will be added as a set property command at the
|
||||
* beginning of the recording file, to be applied when playback starts.
|
||||
*
|
||||
* \param prop The property being set
|
||||
*/
|
||||
void savePropertyBaseline(properties::Property& prop);
|
||||
|
||||
private:
|
||||
void tickPlayback(double dt);
|
||||
void tickRecording(double dt);
|
||||
|
||||
void setupPlayback(double startTime);
|
||||
|
||||
void cleanUpTimelinesAndKeyframes();
|
||||
|
||||
void checkIfScriptUsesScenegraphNode(std::string_view s) const;
|
||||
|
||||
|
||||
properties::BoolProperty _renderPlaybackInformation;
|
||||
properties::BoolProperty _ignoreRecordedScale;
|
||||
properties::BoolProperty _addModelMatrixinAscii;
|
||||
|
||||
struct {
|
||||
double elapsedTime = 0.0;
|
||||
bool isLooping = false;
|
||||
bool playbackPausedWithDeltaTimePause = false;
|
||||
bool waitForLoading = false;
|
||||
|
||||
struct {
|
||||
bool enabled = false;
|
||||
double deltaTime = 1.0 / 30.0;
|
||||
std::chrono::steady_clock::time_point currentRecordedTime;
|
||||
double currentApplicationTime = 0.0;
|
||||
} saveScreenshots;
|
||||
} _playback;
|
||||
|
||||
struct {
|
||||
double elapsedTime = 0.0;
|
||||
} _recording;
|
||||
|
||||
SessionState _state = SessionState::Idle;
|
||||
SessionState _lastState = SessionState::Idle;
|
||||
|
||||
|
||||
SessionRecording _timeline;
|
||||
std::vector<SessionRecording::Entry>::const_iterator _currentEntry =
|
||||
_timeline.entries.end();
|
||||
std::unordered_map<std::string, std::string> _savePropertiesBaseline;
|
||||
std::vector<std::string> _loadedNodes;
|
||||
|
||||
int _nextCallbackHandle = 0;
|
||||
std::vector<std::pair<CallbackHandle, StateChangeCallback>> _stateChangeCallbacks;
|
||||
};
|
||||
|
||||
} // namespace openspace::interaction
|
||||
|
||||
#endif // __OPENSPACE_CORE___SESSIONRECORDINGHANDLER___H__
|
||||
@@ -1,55 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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___CONVERTRECFILEVERSIONTASK___H__
|
||||
#define __OPENSPACE_CORE___CONVERTRECFILEVERSIONTASK___H__
|
||||
|
||||
#include <openspace/util/task.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
|
||||
#include <ghoul/glm.h>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
class ConvertRecFileVersionTask : public Task {
|
||||
public:
|
||||
ConvertRecFileVersionTask(const ghoul::Dictionary& dictionary);
|
||||
~ConvertRecFileVersionTask() override;
|
||||
std::string description() override;
|
||||
void perform(const Task::ProgressCallback& progressCallback) override;
|
||||
static documentation::Documentation documentation();
|
||||
void convert();
|
||||
SessionRecording* sessRec;
|
||||
|
||||
private:
|
||||
std::string _inFilename;
|
||||
std::filesystem::path _inFilePath;
|
||||
std::string _valueFunctionLua;
|
||||
};
|
||||
|
||||
} // namespace openspace::interaction
|
||||
|
||||
#endif // __OPENSPACE_CORE___CONVERTRECFILEVERSIONTASK___H__
|
||||
@@ -26,7 +26,7 @@
|
||||
#define __OPENSPACE_CORE___CONVERTRECFORMATTASK___H__
|
||||
|
||||
#include <openspace/util/task.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
|
||||
#include <ghoul/glm.h>
|
||||
#include <filesystem>
|
||||
@@ -41,25 +41,15 @@ public:
|
||||
ToBinary
|
||||
};
|
||||
ConvertRecFormatTask(const ghoul::Dictionary& dictionary);
|
||||
~ConvertRecFormatTask() override;
|
||||
~ConvertRecFormatTask() override = default;
|
||||
std::string description() override;
|
||||
void perform(const Task::ProgressCallback& progressCallback) override;
|
||||
static documentation::Documentation documentation();
|
||||
void convert();
|
||||
|
||||
private:
|
||||
void convertToAscii();
|
||||
void convertToBinary();
|
||||
void determineFormatType();
|
||||
std::filesystem::path _inFilePath;
|
||||
std::filesystem::path _outFilePath;
|
||||
std::ifstream _iFile;
|
||||
std::ofstream _oFile;
|
||||
SessionRecording::DataMode _fileFormatType;
|
||||
std::string _version;
|
||||
|
||||
std::string _valueFunctionLua;
|
||||
SessionRecording* sessRec;
|
||||
DataMode _dataMode;
|
||||
};
|
||||
|
||||
} // namespace openspace::interaction
|
||||
|
||||
@@ -57,6 +57,7 @@ public:
|
||||
|
||||
CameraPose() = default;
|
||||
CameraPose(datamessagestructures::CameraKeyframe&& kf);
|
||||
auto operator<=>(const CameraPose&) const = default;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -241,7 +241,7 @@ public:
|
||||
* \return Vector of Property objs containing property names that matched the regex
|
||||
*/
|
||||
std::vector<properties::Property*> propertiesMatchingRegex(
|
||||
const std::string& propertyString);
|
||||
std::string_view propertyString);
|
||||
|
||||
/**
|
||||
* Returns a list of all unique tags that are used in the currently loaded scene.
|
||||
|
||||
@@ -120,19 +120,7 @@ public:
|
||||
std::vector<ScheduledScript> allScripts(
|
||||
std::optional<int> group = std::nullopt) const;
|
||||
|
||||
/**
|
||||
* Sets the mode for how each scheduled script's timestamp will be interpreted.
|
||||
* \param refType reference mode (for exact syntax, see definition of
|
||||
* interaction::KeyframeTimeRef) which is either relative to the application start
|
||||
* time, relative to the recorded session playback start time, or according to the
|
||||
* absolute simulation time in seconds from J2000 epoch.
|
||||
*/
|
||||
void setTimeReferenceMode(openspace::interaction::KeyframeTimeRef refType);
|
||||
|
||||
static LuaLibrary luaLibrary();
|
||||
void setModeApplicationTime();
|
||||
void setModeRecordedTime();
|
||||
void setModeSimulationTime();
|
||||
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
@@ -143,9 +131,6 @@ private:
|
||||
|
||||
int _currentIndex = 0;
|
||||
double _currentTime = 0;
|
||||
|
||||
openspace::interaction::KeyframeTimeRef _timeframeMode
|
||||
= openspace::interaction::KeyframeTimeRef::Absolute_simTimeJ2000;
|
||||
};
|
||||
|
||||
} // namespace openspace::scripting
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include <openspace/documentation/documentation.h>
|
||||
#include <openspace/documentation/verifier.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/query/query.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/scene/scenegraphnode.h>
|
||||
@@ -1363,8 +1363,8 @@ void RenderableGlobe::renderChunks(const RenderData& data, RendererTasks&,
|
||||
}
|
||||
_localRenderer.program->deactivate();
|
||||
|
||||
if (global::sessionRecording->isSavingFramesDuringPlayback() &&
|
||||
global::sessionRecording->shouldWaitForTileLoading())
|
||||
if (global::sessionRecordingHandler->isSavingFramesDuringPlayback() &&
|
||||
global::sessionRecordingHandler->shouldWaitForTileLoading())
|
||||
{
|
||||
// If our tile cache is very full, we assume we need to adjust the level of detail
|
||||
// dynamically to not keep rendering frames with unavailable data
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <openspace/engine/globalscallbacks.h>
|
||||
#include <openspace/engine/moduleengine.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <openspace/network/parallelpeer.h>
|
||||
#include <openspace/rendering/dashboard.h>
|
||||
@@ -258,7 +258,7 @@ void ImGUIModule::internalInitialize(const ghoul::Dictionary&) {
|
||||
global::screenSpaceRootPropertyOwner,
|
||||
global::moduleEngine,
|
||||
global::navigationHandler,
|
||||
global::sessionRecording,
|
||||
global::sessionRecordingHandler,
|
||||
global::timeManager,
|
||||
global::renderEngine,
|
||||
global::parallelPeer,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
#include <modules/server/include/topics/topic.h>
|
||||
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
@@ -48,8 +48,8 @@ private:
|
||||
// Provides the idle/recording/playback state int value in json message
|
||||
void sendJsonData();
|
||||
|
||||
interaction::SessionRecording::SessionState _lastState =
|
||||
interaction::SessionRecording::SessionState::Idle;
|
||||
interaction::SessionRecordingHandler::SessionState _lastState =
|
||||
interaction::SessionRecordingHandler::SessionState::Idle;
|
||||
int _stateCallbackHandle = UnsetOnChangeHandle;
|
||||
bool _isDone = false;
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ SessionRecordingTopic::SessionRecordingTopic() {
|
||||
|
||||
SessionRecordingTopic::~SessionRecordingTopic() {
|
||||
if (_stateCallbackHandle != UnsetOnChangeHandle) {
|
||||
global::sessionRecording->removeStateChangeCallback(_stateCallbackHandle);
|
||||
global::sessionRecordingHandler->removeStateChangeCallback(_stateCallbackHandle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +99,10 @@ void SessionRecordingTopic::handleJson(const nlohmann::json& json) {
|
||||
sendJsonData();
|
||||
|
||||
if (event == SubscribeEvent && _sendState) {
|
||||
_stateCallbackHandle = global::sessionRecording->addStateChangeCallback(
|
||||
_stateCallbackHandle = global::sessionRecordingHandler->addStateChangeCallback(
|
||||
[this]() {
|
||||
const interaction::SessionRecording::SessionState currentState =
|
||||
global::sessionRecording->state();
|
||||
const interaction::SessionRecordingHandler::SessionState currentState =
|
||||
global::sessionRecordingHandler->state();
|
||||
if (currentState != _lastState) {
|
||||
sendJsonData();
|
||||
_lastState = currentState;
|
||||
@@ -114,18 +114,17 @@ void SessionRecordingTopic::handleJson(const nlohmann::json& json) {
|
||||
|
||||
void SessionRecordingTopic::sendJsonData() {
|
||||
json stateJson;
|
||||
using SessionRecording = interaction::SessionRecording;
|
||||
using SessionRecordingHandler = interaction::SessionRecordingHandler;
|
||||
if (_sendState) {
|
||||
const SessionRecording::SessionState state = global::sessionRecording->state();
|
||||
std::string stateString;
|
||||
switch (state) {
|
||||
case SessionRecording::SessionState::Recording:
|
||||
switch (global::sessionRecordingHandler->state()) {
|
||||
case SessionRecordingHandler::SessionState::Recording:
|
||||
stateString = "recording";
|
||||
break;
|
||||
case SessionRecording::SessionState::Playback:
|
||||
case SessionRecordingHandler::SessionState::Playback:
|
||||
stateString = "playing";
|
||||
break;
|
||||
case SessionRecording::SessionState::PlaybackPaused:
|
||||
case SessionRecordingHandler::SessionState::PlaybackPaused:
|
||||
stateString = "playing-paused";
|
||||
break;
|
||||
default:
|
||||
@@ -135,7 +134,7 @@ void SessionRecordingTopic::sendJsonData() {
|
||||
stateJson[StateKey] = stateString;
|
||||
};
|
||||
if (_sendFiles) {
|
||||
stateJson[FilesKey] = global::sessionRecording->playbackList();
|
||||
stateJson[FilesKey] = global::sessionRecordingHandler->playbackList();
|
||||
}
|
||||
if (!stateJson.empty()) {
|
||||
_connection->sendJson(wrappedPayload(stateJson));
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <openspace/engine/syncengine.h>
|
||||
#include <openspace/engine/moduleengine.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/util/time.h>
|
||||
#include <openspace/util/timemanager.h>
|
||||
@@ -495,8 +495,8 @@ void VideoPlayer::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (global::sessionRecording->isSavingFramesDuringPlayback()) {
|
||||
const double dt = global::sessionRecording->fixedDeltaTimeDuringFrameOutput();
|
||||
if (global::sessionRecordingHandler->isSavingFramesDuringPlayback()) {
|
||||
const double dt = global::sessionRecordingHandler->fixedDeltaTimeDuringFrameOutput();
|
||||
if (_playbackMode == PlaybackMode::MapToSimulationTime) {
|
||||
_currentVideoTime = correctVideoPlaybackTime();
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ void LinearLruCache<ValueType>::set(size_t key, ValueType value) {
|
||||
|
||||
template <typename ValueType>
|
||||
ValueType& LinearLruCache<ValueType>::use(size_t key) {
|
||||
auto& pair = _cache[key];
|
||||
const auto& pair = _cache[key];
|
||||
const std::list<size_t>::iterator trackerIter = pair.second;
|
||||
_tracker.splice(_tracker.end(), _tracker, trackerIter);
|
||||
return pair.first;
|
||||
|
||||
@@ -58,16 +58,16 @@ set(OPENSPACE_SOURCE
|
||||
interaction/joystickcamerastates.cpp
|
||||
interaction/keybindingmanager.cpp
|
||||
interaction/keybindingmanager_lua.inl
|
||||
interaction/keyframerecording.cpp
|
||||
interaction/keyframerecording_lua.inl
|
||||
interaction/keyframerecordinghandler.cpp
|
||||
interaction/keyframerecordinghandler_lua.inl
|
||||
interaction/keyboardinputstate.cpp
|
||||
interaction/mousecamerastates.cpp
|
||||
interaction/scriptcamerastates.cpp
|
||||
interaction/sessionrecording.cpp
|
||||
interaction/sessionrecording_lua.inl
|
||||
interaction/sessionrecordinghandler.cpp
|
||||
interaction/sessionrecordinghandler_lua.inl
|
||||
interaction/websocketinputstate.cpp
|
||||
interaction/websocketcamerastates.cpp
|
||||
interaction/tasks/convertrecfileversiontask.cpp
|
||||
interaction/tasks/convertrecformattask.cpp
|
||||
mission/mission.cpp
|
||||
mission/missionmanager.cpp
|
||||
@@ -252,14 +252,13 @@ set(OPENSPACE_HEADER
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/joystickcamerastates.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/keybindingmanager.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/keyboardinputstate.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/keyframerecording.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/keyframerecordinghandler.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/mousecamerastates.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/scriptcamerastates.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/sessionrecording.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/sessionrecording.inl
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/sessionrecordinghandler.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/websocketinputstate.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/websocketcamerastates.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/tasks/convertrecfileversiontask.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/interaction/tasks/convertrecformattask.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/mission/mission.h
|
||||
${PROJECT_SOURCE_DIR}/include/openspace/mission/missionmanager.h
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
#include <openspace/events/eventengine.h>
|
||||
#include <openspace/interaction/actionmanager.h>
|
||||
#include <openspace/interaction/keybindingmanager.h>
|
||||
#include <openspace/interaction/keyframerecording.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/keyframerecordinghandler.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/mission/mission.h>
|
||||
#include <openspace/mission/missionmanager.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
@@ -107,11 +107,11 @@ void registerCoreClasses(scripting::ScriptEngine& engine) {
|
||||
engine.addLibrary(Time::luaLibrary());
|
||||
engine.addLibrary(interaction::ActionManager::luaLibrary());
|
||||
engine.addLibrary(interaction::KeybindingManager::luaLibrary());
|
||||
engine.addLibrary(interaction::KeyframeRecording::luaLibrary());
|
||||
engine.addLibrary(interaction::KeyframeRecordingHandler::luaLibrary());
|
||||
engine.addLibrary(interaction::NavigationHandler::luaLibrary());
|
||||
engine.addLibrary(interaction::OrbitalNavigator::luaLibrary());
|
||||
engine.addLibrary(interaction::PathNavigator::luaLibrary());
|
||||
engine.addLibrary(interaction::SessionRecording::luaLibrary());
|
||||
engine.addLibrary(interaction::SessionRecordingHandler::luaLibrary());
|
||||
engine.addLibrary(scripting::ScriptScheduler::luaLibrary());
|
||||
engine.addLibrary(scripting::generalSystemCapabilities());
|
||||
engine.addLibrary(scripting::openglSystemCapabilities());
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
#include <openspace/interaction/actionmanager.h>
|
||||
#include <openspace/interaction/interactionmonitor.h>
|
||||
#include <openspace/interaction/keybindingmanager.h>
|
||||
#include <openspace/interaction/keyframerecording.h>
|
||||
#include <openspace/interaction/keyframerecordinghandler.h>
|
||||
#include <openspace/interaction/joystickinputstate.h>
|
||||
#include <openspace/interaction/websocketinputstate.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/mission/missionmanager.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <openspace/network/parallelpeer.h>
|
||||
@@ -96,9 +96,9 @@ namespace {
|
||||
sizeof(interaction::JoystickInputStates) +
|
||||
sizeof(interaction::WebsocketInputStates) +
|
||||
sizeof(interaction::KeybindingManager) +
|
||||
sizeof(interaction::KeyframeRecording) +
|
||||
sizeof(interaction::KeyframeRecordingHandler) +
|
||||
sizeof(interaction::NavigationHandler) +
|
||||
sizeof(interaction::SessionRecording) +
|
||||
sizeof(interaction::SessionRecordingHandler) +
|
||||
sizeof(properties::PropertyOwner) +
|
||||
sizeof(properties::PropertyOwner) +
|
||||
sizeof(properties::PropertyOwner) +
|
||||
@@ -317,11 +317,11 @@ void create() {
|
||||
#endif // WIN32
|
||||
|
||||
#ifdef WIN32
|
||||
keyframeRecording = new (currentPos) interaction::KeyframeRecording;
|
||||
keyframeRecording = new (currentPos) interaction::KeyframeRecordingHandler;
|
||||
ghoul_assert(keyframeRecording, "No keyframeRecording");
|
||||
currentPos += sizeof(interaction::KeyframeRecording);
|
||||
currentPos += sizeof(interaction::KeyframeRecordingHandler);
|
||||
#else // ^^^ WIN32 / !WIN32 vvv
|
||||
keyframeRecording = new interaction::KeyframeRecording;
|
||||
keyframeRecording = new interaction::KeyframeRecordingHandler;
|
||||
#endif // WIN32
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -333,11 +333,11 @@ void create() {
|
||||
#endif // WIN32
|
||||
|
||||
#ifdef WIN32
|
||||
sessionRecording = new (currentPos) interaction::SessionRecording(true);
|
||||
ghoul_assert(sessionRecording, "No sessionRecording");
|
||||
currentPos += sizeof(interaction::SessionRecording);
|
||||
sessionRecordingHandler = new (currentPos) interaction::SessionRecordingHandler;
|
||||
ghoul_assert(sessionRecordingHandler, "No sessionRecording");
|
||||
currentPos += sizeof(interaction::SessionRecordingHandler);
|
||||
#else // ^^^ WIN32 / !WIN32 vvv
|
||||
sessionRecording = new interaction::SessionRecording(true);
|
||||
sessionRecordingHandler = new interaction::SessionRecordingHandler;
|
||||
#endif // WIN32
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -399,7 +399,7 @@ void initialize() {
|
||||
rootPropertyOwner->addPropertySubOwner(global::navigationHandler);
|
||||
rootPropertyOwner->addPropertySubOwner(global::keyframeRecording);
|
||||
rootPropertyOwner->addPropertySubOwner(global::interactionMonitor);
|
||||
rootPropertyOwner->addPropertySubOwner(global::sessionRecording);
|
||||
rootPropertyOwner->addPropertySubOwner(global::sessionRecordingHandler);
|
||||
rootPropertyOwner->addPropertySubOwner(global::timeManager);
|
||||
rootPropertyOwner->addPropertySubOwner(global::scriptScheduler);
|
||||
|
||||
@@ -456,11 +456,11 @@ void destroy() {
|
||||
delete rootPropertyOwner;
|
||||
#endif // WIN32
|
||||
|
||||
LDEBUGC("Globals", "Destroying 'SessionRecording'");
|
||||
LDEBUGC("Globals", "Destroying 'SessionRecordingHandler'");
|
||||
#ifdef WIN32
|
||||
sessionRecording->~SessionRecording();
|
||||
sessionRecordingHandler->~SessionRecordingHandler();
|
||||
#else // ^^^ WIN32 / !WIN32 vvv
|
||||
delete sessionRecording;
|
||||
delete sessionRecordingHandler;
|
||||
#endif // WIN32
|
||||
|
||||
LDEBUGC("Globals", "Destroying 'NavigationHandler'");
|
||||
@@ -470,9 +470,9 @@ void destroy() {
|
||||
delete navigationHandler;
|
||||
#endif // WIN32
|
||||
|
||||
LDEBUGC("Globals", "Destroying 'KeyframeRecording'");
|
||||
LDEBUGC("Globals", "Destroying 'KeyframeRecordingHandler'");
|
||||
#ifdef WIN32
|
||||
keyframeRecording->~KeyframeRecording();
|
||||
keyframeRecording->~KeyframeRecordingHandler();
|
||||
#else // ^^^ WIN32 / !WIN32 vvv
|
||||
delete keyframeRecording;
|
||||
#endif // WIN32
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
#include <openspace/interaction/actionmanager.h>
|
||||
#include <openspace/interaction/interactionmonitor.h>
|
||||
#include <openspace/interaction/keybindingmanager.h>
|
||||
#include <openspace/interaction/keyframerecording.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/interaction/tasks/convertrecformattask.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <openspace/navigation/orbitalnavigator.h>
|
||||
#include <openspace/navigation/waypoint.h>
|
||||
@@ -63,6 +63,7 @@
|
||||
#include <openspace/util/memorymanager.h>
|
||||
#include <openspace/util/screenlog.h>
|
||||
#include <openspace/util/spicemanager.h>
|
||||
#include <openspace/util/task.h>
|
||||
#include <openspace/util/timemanager.h>
|
||||
#include <openspace/util/transformationmanager.h>
|
||||
#include <ghoul/ghoul.h>
|
||||
@@ -231,6 +232,11 @@ OpenSpaceEngine::OpenSpaceEngine()
|
||||
addProperty(_fadeOnEnableDuration);
|
||||
addProperty(_disableAllMouseInputs);
|
||||
|
||||
|
||||
ghoul::TemplateFactory<Task>* fTask = FactoryManager::ref().factory<Task>();
|
||||
ghoul_assert(fTask, "No task factory existed");
|
||||
fTask->registerClass<interaction::ConvertRecFormatTask>("ConvertRecFormatTask");
|
||||
|
||||
#ifdef WIN32
|
||||
PDH_STATUS status = PdhOpenQueryA(nullptr, 0, &vramQuery);
|
||||
if (status != ERROR_SUCCESS) {
|
||||
@@ -875,7 +881,6 @@ void OpenSpaceEngine::deinitialize() {
|
||||
global::renderEngine->scene()->camera()->syncables()
|
||||
);
|
||||
}
|
||||
global::sessionRecording->deinitialize();
|
||||
global::versionChecker->cancel();
|
||||
|
||||
_assetManager = nullptr;
|
||||
@@ -1083,8 +1088,8 @@ void OpenSpaceEngine::preSynchronization() {
|
||||
global::syncEngine->preSynchronization(SyncEngine::IsMaster(master));
|
||||
if (master) {
|
||||
const double dt =
|
||||
global::sessionRecording->isSavingFramesDuringPlayback() ?
|
||||
global::sessionRecording->fixedDeltaTimeDuringFrameOutput() :
|
||||
global::sessionRecordingHandler->isSavingFramesDuringPlayback() ?
|
||||
global::sessionRecordingHandler->fixedDeltaTimeDuringFrameOutput() :
|
||||
global::windowDelegate->deltaTime();
|
||||
|
||||
global::timeManager->preSynchronization(dt);
|
||||
@@ -1113,8 +1118,7 @@ void OpenSpaceEngine::preSynchronization() {
|
||||
camera->invalidateCache();
|
||||
}
|
||||
}
|
||||
global::sessionRecording->preSynchronization();
|
||||
global::keyframeRecording->preSynchronization(dt);
|
||||
global::sessionRecordingHandler->preSynchronization(dt);
|
||||
global::parallelPeer->preSynchronization();
|
||||
global::interactionMonitor->updateActivityState();
|
||||
}
|
||||
@@ -1263,7 +1267,7 @@ void OpenSpaceEngine::drawOverlays() {
|
||||
if (isGuiWindow) {
|
||||
global::renderEngine->renderOverlays(_shutdown);
|
||||
global::luaConsole->render();
|
||||
global::sessionRecording->render();
|
||||
global::sessionRecordingHandler->render();
|
||||
}
|
||||
|
||||
for (const std::function<void()>& func : *global::callback::draw2D) {
|
||||
|
||||
@@ -1,430 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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/interaction/keyframerecording.h>
|
||||
|
||||
#include <openspace/camera/camera.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <openspace/navigation/orbitalnavigator.h>
|
||||
#include <openspace/scene/scene.h>
|
||||
#include <openspace/scene/scenegraphnode.h>
|
||||
#include <openspace/util/timemanager.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/glm.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "keyframerecording_lua.inl"
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "KeyframeRecording";
|
||||
} // namespace
|
||||
|
||||
namespace openspace::interaction::keys {
|
||||
// These keys are const char* since nlohmann::json do not support string_view in .at()
|
||||
constexpr const char* Camera = "camera";
|
||||
constexpr const char* Position = "position";
|
||||
constexpr const char* Rotation = "rotation";
|
||||
constexpr const char* Scale = "scale";
|
||||
constexpr const char* FollowFocusNodeRotation = "followFocusNodeRotation";
|
||||
constexpr const char* FocusNode = "focusNode";
|
||||
|
||||
constexpr const char* Timestamp = "timestamp";
|
||||
constexpr const char* Application = "application";
|
||||
constexpr const char* Sequence = "sequence";
|
||||
constexpr const char* Simulation = "simulation";
|
||||
constexpr const char* X = "x";
|
||||
constexpr const char* Y = "y";
|
||||
constexpr const char* Z = "z";
|
||||
constexpr const char* W = "w";
|
||||
}
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
void to_json(nlohmann::json& j, const KeyframeRecording::Keyframe& keyframe) {
|
||||
nlohmann::json position = {
|
||||
{ keys::X, keyframe.camera.position.x },
|
||||
{ keys::Y, keyframe.camera.position.y },
|
||||
{ keys::Z, keyframe.camera.position.z },
|
||||
};
|
||||
nlohmann::json rotation = {
|
||||
{ keys::X, keyframe.camera.rotation.x },
|
||||
{ keys::Y, keyframe.camera.rotation.y },
|
||||
{ keys::Z, keyframe.camera.rotation.z },
|
||||
{ keys::W, keyframe.camera.rotation.w },
|
||||
};
|
||||
nlohmann::json camera = {
|
||||
{ keys::Position, position },
|
||||
{ keys::Rotation, rotation },
|
||||
{ keys::Scale, keyframe.camera.scale },
|
||||
{ keys::FollowFocusNodeRotation, keyframe.camera.followFocusNodeRotation },
|
||||
{ keys::FocusNode, keyframe.camera.focusNode }
|
||||
};
|
||||
|
||||
nlohmann::json timestamp = {
|
||||
{ keys::Application, keyframe.timestamp.application },
|
||||
{ keys::Sequence, keyframe.timestamp.sequenceTime },
|
||||
{ keys::Simulation, keyframe.timestamp.simulation }
|
||||
};
|
||||
|
||||
j = {
|
||||
{ keys::Camera, camera },
|
||||
{ keys::Timestamp, timestamp }
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, KeyframeRecording::Keyframe::TimeStamp& ts) {
|
||||
j.at(keys::Application).get_to(ts.application);
|
||||
j.at(keys::Sequence).get_to(ts.sequenceTime);
|
||||
j.at(keys::Simulation).get_to(ts.simulation);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, KeyframeNavigator::CameraPose& pose) {
|
||||
j.at(keys::Position).at(keys::X).get_to(pose.position.x);
|
||||
j.at(keys::Position).at(keys::Y).get_to(pose.position.y);
|
||||
j.at(keys::Position).at(keys::Z).get_to(pose.position.z);
|
||||
|
||||
j.at(keys::Rotation).at(keys::X).get_to(pose.rotation.x);
|
||||
j.at(keys::Rotation).at(keys::Y).get_to(pose.rotation.y);
|
||||
j.at(keys::Rotation).at(keys::Z).get_to(pose.rotation.z);
|
||||
j.at(keys::Rotation).at(keys::W).get_to(pose.rotation.w);
|
||||
|
||||
j.at(keys::FocusNode).get_to(pose.focusNode);
|
||||
j.at(keys::Scale).get_to(pose.scale);
|
||||
j.at(keys::FollowFocusNodeRotation).get_to(pose.followFocusNodeRotation);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, KeyframeRecording::Keyframe& keyframe) {
|
||||
j.at(keys::Camera).get_to(keyframe.camera);
|
||||
j.at(keys::Timestamp).get_to(keyframe.timestamp);
|
||||
}
|
||||
|
||||
KeyframeRecording::KeyframeRecording()
|
||||
: properties::PropertyOwner({ "KeyframeRecording", "Keyframe Recording" })
|
||||
{}
|
||||
|
||||
void KeyframeRecording::newSequence() {
|
||||
_keyframes.clear();
|
||||
_filename.clear();
|
||||
LINFO("Created new sequence");
|
||||
}
|
||||
|
||||
void KeyframeRecording::addKeyframe(double sequenceTime) {
|
||||
ghoul_assert(sequenceTime >= 0, "Sequence time must be positive");
|
||||
|
||||
Keyframe keyframe = newKeyframe(sequenceTime);
|
||||
|
||||
auto it = std::find_if(
|
||||
_keyframes.begin(),
|
||||
_keyframes.end(),
|
||||
[&sequenceTime](const Keyframe& entry) {
|
||||
return sequenceTime < entry.timestamp.sequenceTime;
|
||||
}
|
||||
);
|
||||
_keyframes.insert(it, keyframe);
|
||||
LINFO(std::format(
|
||||
"Added new keyframe {} at time: {}",
|
||||
_keyframes.size() - 1 , sequenceTime
|
||||
));
|
||||
}
|
||||
|
||||
void KeyframeRecording::removeKeyframe(int index) {
|
||||
ghoul_assert(hasKeyframeRecording(), "Can't remove keyframe on empty sequence");
|
||||
|
||||
if (!isInRange(index)) {
|
||||
LERROR(std::format("Index {} out of range", index));
|
||||
return;
|
||||
}
|
||||
_keyframes.erase(_keyframes.begin() + index);
|
||||
LINFO(std::format("Removed keyframe with index {}", index));
|
||||
}
|
||||
|
||||
void KeyframeRecording::updateKeyframe(int index) {
|
||||
ghoul_assert(hasKeyframeRecording(), "Can't update keyframe on empty sequence");
|
||||
|
||||
if (!isInRange(index)) {
|
||||
LERROR(std::format("Index {} out of range", index));
|
||||
return;
|
||||
}
|
||||
Keyframe old = _keyframes[index];
|
||||
_keyframes[index] = newKeyframe(old.timestamp.sequenceTime);
|
||||
LINFO(std::format("Update camera position of keyframe {}", index));
|
||||
}
|
||||
|
||||
void KeyframeRecording::moveKeyframe(int index, double sequenceTime) {
|
||||
ghoul_assert(hasKeyframeRecording(), "can't move keyframe on empty sequence");
|
||||
ghoul_assert(sequenceTime >= 0, "Sequence time must be positive");
|
||||
|
||||
if (!isInRange(index)) {
|
||||
LERROR(std::format("Index {} out of range", index));
|
||||
return;
|
||||
}
|
||||
double oldSequenceTime = _keyframes[index].timestamp.sequenceTime;
|
||||
_keyframes[index].timestamp.sequenceTime = sequenceTime;
|
||||
sortKeyframes();
|
||||
LINFO(std::format(
|
||||
"Moved keyframe {} from sequence time: {} to {}",
|
||||
index, oldSequenceTime, sequenceTime
|
||||
));
|
||||
}
|
||||
|
||||
bool KeyframeRecording::saveSequence(std::optional<std::string> filename) {
|
||||
ghoul_assert(hasKeyframeRecording(), "Keyframe sequence can't be empty");
|
||||
|
||||
// If we didn't specify any filename we save the one we currently have stored
|
||||
if (filename.has_value()) {
|
||||
_filename = filename.value();
|
||||
}
|
||||
|
||||
if (_filename.empty()) {
|
||||
LERROR("Failed to save file, reason: Invalid empty file name");
|
||||
return false;
|
||||
}
|
||||
|
||||
nlohmann::json sequence = _keyframes;
|
||||
std::filesystem::path path = absPath(
|
||||
std::format("${{RECORDINGS}}/{}.json", _filename)
|
||||
);
|
||||
std::ofstream ofs(path);
|
||||
ofs << sequence.dump(2);
|
||||
LINFO(std::format("Saved keyframe sequence to '{}'", path.string()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void KeyframeRecording::loadSequence(std::string filename) {
|
||||
std::filesystem::path path = absPath(
|
||||
std::format("${{RECORDINGS}}/{}.json", filename)
|
||||
);
|
||||
if (!std::filesystem::exists(path)) {
|
||||
LERROR(std::format("File '{}' does not exist", path));
|
||||
return;
|
||||
}
|
||||
|
||||
LINFO(std::format("Loading keyframe sequence from '{}'", path));
|
||||
_keyframes.clear();
|
||||
std::ifstream file(path);
|
||||
std::vector<nlohmann::json> jsonKeyframes =
|
||||
nlohmann::json::parse(file).get<std::vector<nlohmann::json>>();
|
||||
|
||||
for (const nlohmann::json& keyframeJson : jsonKeyframes) {
|
||||
Keyframe keyframe = keyframeJson;
|
||||
_keyframes.push_back(keyframe);
|
||||
}
|
||||
_filename = filename;
|
||||
}
|
||||
|
||||
void KeyframeRecording::play() {
|
||||
ghoul_assert(hasKeyframeRecording(), "Keyframe sequence can't be empty");
|
||||
|
||||
LINFO("Keyframe sequence playing");
|
||||
_isPlaying = true;
|
||||
}
|
||||
|
||||
void KeyframeRecording::pause() {
|
||||
LINFO("Keyframe sequence paused");
|
||||
_isPlaying = false;
|
||||
}
|
||||
|
||||
void KeyframeRecording::setSequenceTime(double sequenceTime) {
|
||||
ghoul_assert(sequenceTime >= 0, "Sequence time must be positive");
|
||||
|
||||
_sequenceTime = sequenceTime;
|
||||
_hasStateChanged = true;
|
||||
LINFO(std::format("Set sequence time to {}", sequenceTime));
|
||||
}
|
||||
|
||||
void KeyframeRecording::jumpToKeyframe(int index) {
|
||||
if (!isInRange(index)) {
|
||||
LERROR(std::format("Index {} out of range", index));
|
||||
return;
|
||||
}
|
||||
const double time = _keyframes[index].timestamp.sequenceTime;
|
||||
LINFO(std::format("Jumped to keyframe {}", index));
|
||||
setSequenceTime(time);
|
||||
}
|
||||
|
||||
bool KeyframeRecording::hasKeyframeRecording() const {
|
||||
return !_keyframes.empty();
|
||||
}
|
||||
|
||||
std::vector<ghoul::Dictionary> KeyframeRecording::keyframes() const {
|
||||
std::vector<ghoul::Dictionary> result;
|
||||
for (const auto& keyframe : _keyframes) {
|
||||
ghoul::Dictionary camera;
|
||||
ghoul::Dictionary timestamp;
|
||||
ghoul::Dictionary entry;
|
||||
ghoul::Dictionary position;
|
||||
ghoul::Dictionary rotation;
|
||||
|
||||
// Add each entry to position & rotation to avoid ambiguity on the client side
|
||||
position.setValue(keys::X, keyframe.camera.position.x);
|
||||
position.setValue(keys::Y, keyframe.camera.position.y);
|
||||
position.setValue(keys::Z, keyframe.camera.position.z);
|
||||
camera.setValue(keys::Position, position);
|
||||
|
||||
rotation.setValue(keys::X, static_cast<double>(keyframe.camera.rotation.x));
|
||||
rotation.setValue(keys::Y, static_cast<double>(keyframe.camera.rotation.y));
|
||||
rotation.setValue(keys::Z, static_cast<double>(keyframe.camera.rotation.z));
|
||||
rotation.setValue(keys::W, static_cast<double>(keyframe.camera.rotation.w));
|
||||
camera.setValue(keys::Rotation, rotation);
|
||||
|
||||
camera.setValue(keys::Scale, static_cast<double>(keyframe.camera.scale));
|
||||
camera.setValue(keys::FocusNode, keyframe.camera.focusNode);
|
||||
camera.setValue(keys::FollowFocusNodeRotation, keyframe.camera.followFocusNodeRotation);
|
||||
|
||||
timestamp.setValue(keys::Application, keyframe.timestamp.application);
|
||||
timestamp.setValue(keys::Sequence, keyframe.timestamp.sequenceTime);
|
||||
timestamp.setValue(keys::Simulation, keyframe.timestamp.simulation);
|
||||
|
||||
entry.setValue(keys::Camera, camera);
|
||||
entry.setValue(keys::Timestamp, timestamp);
|
||||
result.push_back(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void KeyframeRecording::preSynchronization(double dt) {
|
||||
if (_hasStateChanged) {
|
||||
auto it = std::find_if(
|
||||
_keyframes.rbegin(),
|
||||
_keyframes.rend(),
|
||||
[timestamp = _sequenceTime](const Keyframe& entry) {
|
||||
return timestamp >= entry.timestamp.sequenceTime;
|
||||
}
|
||||
);
|
||||
|
||||
Keyframe currKeyframe;
|
||||
Keyframe nextKeyframe;
|
||||
double factor = 0.0;
|
||||
|
||||
// Before first keyframe
|
||||
if (it == _keyframes.rend()) {
|
||||
currKeyframe = nextKeyframe = _keyframes.front();
|
||||
}
|
||||
// At or after last keyframe
|
||||
else if (it == _keyframes.rbegin()) {
|
||||
currKeyframe = nextKeyframe = _keyframes.back();
|
||||
_isPlaying = false;
|
||||
}
|
||||
else {
|
||||
currKeyframe = *it;
|
||||
nextKeyframe = *(--it);
|
||||
double t0 = currKeyframe.timestamp.sequenceTime;
|
||||
double t1 = nextKeyframe.timestamp.sequenceTime;
|
||||
factor = (_sequenceTime - t0) / (t1 - t0);
|
||||
}
|
||||
|
||||
interaction::KeyframeNavigator::CameraPose curr = currKeyframe.camera;
|
||||
interaction::KeyframeNavigator::CameraPose next = nextKeyframe.camera;
|
||||
|
||||
Camera* camera = global::navigationHandler->camera();
|
||||
Scene* scene = camera->parent()->scene();
|
||||
SceneGraphNode* node = scene->sceneGraphNode(curr.focusNode);
|
||||
|
||||
global::navigationHandler->orbitalNavigator().setFocusNode(node);
|
||||
interaction::KeyframeNavigator::updateCamera(
|
||||
global::navigationHandler->camera(),
|
||||
curr,
|
||||
next,
|
||||
factor,
|
||||
false
|
||||
);
|
||||
|
||||
_hasStateChanged = false;
|
||||
}
|
||||
|
||||
if (_isPlaying) {
|
||||
_sequenceTime += dt;
|
||||
_hasStateChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
scripting::LuaLibrary KeyframeRecording::luaLibrary() {
|
||||
return {
|
||||
"keyframeRecording",
|
||||
{
|
||||
codegen::lua::NewSequence,
|
||||
codegen::lua::AddKeyframe,
|
||||
codegen::lua::RemoveKeyframe,
|
||||
codegen::lua::UpdateKeyframe,
|
||||
codegen::lua::MoveKeyframe,
|
||||
codegen::lua::SaveSequence,
|
||||
codegen::lua::LoadSequence,
|
||||
codegen::lua::Play,
|
||||
codegen::lua::Pause,
|
||||
codegen::lua::Resume,
|
||||
codegen::lua::SetTime,
|
||||
codegen::lua::JumpToKeyframe,
|
||||
codegen::lua::HasKeyframeRecording,
|
||||
codegen::lua::Keyframes
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void KeyframeRecording::sortKeyframes() {
|
||||
std::sort(
|
||||
_keyframes.begin(),
|
||||
_keyframes.end(),
|
||||
[](Keyframe lhs, Keyframe rhs) {
|
||||
return lhs.timestamp.sequenceTime < rhs.timestamp.sequenceTime;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
KeyframeRecording::Keyframe KeyframeRecording::newKeyframe(double sequenceTime) {
|
||||
interaction::NavigationHandler& handler = *global::navigationHandler;
|
||||
interaction::OrbitalNavigator& navigator = handler.orbitalNavigator();
|
||||
const SceneGraphNode* node = navigator.anchorNode();
|
||||
|
||||
glm::dvec3 position = navigator.anchorNodeToCameraVector();
|
||||
glm::dquat rotation = handler.camera()->rotationQuaternion();
|
||||
float scale = handler.camera()->scaling();
|
||||
bool followNodeRotation = navigator.followingAnchorRotation();
|
||||
|
||||
if (followNodeRotation) {
|
||||
position = glm::inverse(node->worldRotationMatrix()) * position;
|
||||
rotation = navigator.anchorNodeToCameraRotation();
|
||||
}
|
||||
|
||||
Keyframe keyframe;
|
||||
keyframe.camera.position = position;
|
||||
keyframe.camera.rotation = rotation;
|
||||
keyframe.camera.focusNode = navigator.anchorNode()->identifier();
|
||||
keyframe.camera.scale = scale;
|
||||
keyframe.camera.followFocusNodeRotation = followNodeRotation;
|
||||
|
||||
keyframe.timestamp.application = global::windowDelegate->applicationTime();
|
||||
keyframe.timestamp.sequenceTime = sequenceTime;
|
||||
keyframe.timestamp.simulation = global::timeManager->time().j2000Seconds();
|
||||
|
||||
return keyframe;
|
||||
}
|
||||
|
||||
bool KeyframeRecording::isInRange(int index) const {
|
||||
return index >= 0 && index < _keyframes.size();
|
||||
}
|
||||
|
||||
} // namespace openspace::interaction
|
||||
@@ -1,190 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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/engine/globals.h>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Starts a new sequence of keyframes. Any previously loaded sequence is discarded.
|
||||
*/
|
||||
[[codegen::luawrap]] void newSequence() {
|
||||
openspace::global::keyframeRecording->newSequence();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a keyframe at the specified time in the sequence.
|
||||
*
|
||||
* \param sequenceTime The time at which to add the new keyframe in the sequence given in
|
||||
* seconds
|
||||
*/
|
||||
[[codegen::luawrap]] void addKeyframe(double sequenceTime) {
|
||||
if (sequenceTime < 0) {
|
||||
throw ghoul::lua::LuaError("Error can't add keyframe with negative time value");
|
||||
}
|
||||
openspace::global::keyframeRecording->addKeyframe(sequenceTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a keyframe at the specified index.
|
||||
*
|
||||
* \param index The 0-based index of the keyframe to remove
|
||||
*/
|
||||
[[codegen::luawrap]] void removeKeyframe(int index) {
|
||||
if (!openspace::global::keyframeRecording->hasKeyframeRecording()) {
|
||||
throw ghoul::lua::LuaError("Can't remove keyframe on empty sequence");
|
||||
}
|
||||
if (index < 0) {
|
||||
throw ghoul::lua::LuaError("Index value must be positive");
|
||||
}
|
||||
openspace::global::keyframeRecording->removeKeyframe(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the camera position of a keyframe at the specified index.
|
||||
*
|
||||
* \param index The 0-based index of the keyframe to update
|
||||
*/
|
||||
[[codegen::luawrap]] void updateKeyframe(int index) {
|
||||
if (!openspace::global::keyframeRecording->hasKeyframeRecording()) {
|
||||
throw ghoul::lua::LuaError("Can't update keyframe on empty sequence");
|
||||
}
|
||||
if (index < 0) {
|
||||
throw ghoul::lua::LuaError("Index value must be positive");
|
||||
}
|
||||
openspace::global::keyframeRecording->updateKeyframe(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an existing keyframe in time.
|
||||
*
|
||||
* \param index The index of the keyframe to move
|
||||
* \param sequenceTime The new time in seconds to update the keyframe to
|
||||
*/
|
||||
[[codegen::luawrap]] void moveKeyframe(int index, double sequenceTime) {
|
||||
if (!openspace::global::keyframeRecording->hasKeyframeRecording()) {
|
||||
throw ghoul::lua::LuaError("Can't move keyframe on empty sequence");
|
||||
}
|
||||
if (index < 0) {
|
||||
throw ghoul::lua::LuaError("Index value must be positive");
|
||||
}
|
||||
if (sequenceTime < 0) {
|
||||
throw ghoul::lua::LuaError("Error can't add keyframe with negative time value");
|
||||
}
|
||||
openspace::global::keyframeRecording->moveKeyframe(index, sequenceTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current sequence of keyframes to disk by the optionally specified `filename`.
|
||||
* `filename` can be omitted if the sequence was previously saved or loaded from file.
|
||||
*
|
||||
* \param filename The name of the file to save
|
||||
*/
|
||||
[[codegen::luawrap]] void saveSequence(std::optional<std::string> filename) {
|
||||
if (!openspace::global::keyframeRecording->hasKeyframeRecording()) {
|
||||
throw ghoul::lua::LuaError("No keyframe sequence to save");
|
||||
}
|
||||
openspace::global::keyframeRecording->saveSequence(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a keyframe recording sequence from the specified file.
|
||||
*
|
||||
* \param filename The name of the file to load
|
||||
*/
|
||||
[[codegen::luawrap]] void loadSequence(std::string filename) {
|
||||
openspace::global::keyframeRecording->loadSequence(std::move(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Playback keyframe recording sequence optionally from the specified `sequenceTime` or if
|
||||
* not specified starts playing from the beginning.
|
||||
*
|
||||
* \param sequenceTime The time in seconds at which to start playing the sequence. If
|
||||
* omitted, the playback starts at the beginning of the sequence.
|
||||
*/
|
||||
[[codegen::luawrap]] void play(std::optional<double> sequenceTime) {
|
||||
if (!openspace::global::keyframeRecording->hasKeyframeRecording()) {
|
||||
throw ghoul::lua::LuaError("No keyframe sequence to play");
|
||||
}
|
||||
openspace::global::keyframeRecording->setSequenceTime(sequenceTime.value_or(0.0));
|
||||
openspace::global::keyframeRecording->play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses a playing keyframe recording sequence.
|
||||
*/
|
||||
[[codegen::luawrap]] void pause() {
|
||||
openspace::global::keyframeRecording->pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume playing a keyframe recording sequence that has been paused.
|
||||
*/
|
||||
[[codegen::luawrap]] void resume() {
|
||||
openspace::global::keyframeRecording->play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Jumps to a specified time within the keyframe recording sequence.
|
||||
*
|
||||
* \param sequenceTime The time in seconds to jump to
|
||||
*/
|
||||
[[codegen::luawrap]] void setTime(double sequenceTime) {
|
||||
if (sequenceTime < 0) {
|
||||
throw ghoul::lua::LuaError("Sequence time must be greater or equal than 0");
|
||||
}
|
||||
openspace::global::keyframeRecording->setSequenceTime(sequenceTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jumps to a specified keyframe within the keyframe recording sequence.
|
||||
*
|
||||
* \param index The index of the keyframe to jump to
|
||||
*/
|
||||
[[codegen::luawrap]] void jumpToKeyframe(int index) {
|
||||
if (index < 0) {
|
||||
throw ghoul::lua::LuaError("Index must be positive");
|
||||
}
|
||||
openspace::global::keyframeRecording->jumpToKeyframe(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there currently is a sequence loaded, otherwise false.
|
||||
*/
|
||||
[[codegen::luawrap]] bool hasKeyframeRecording() {
|
||||
return openspace::global::keyframeRecording->hasKeyframeRecording();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the sequence keyframes as a JSON object.
|
||||
*/
|
||||
[[codegen::luawrap]] std::vector<ghoul::Dictionary> keyframes() {
|
||||
return openspace::global::keyframeRecording->keyframes();
|
||||
}
|
||||
|
||||
#include "keyframerecording_lua_codegen.cpp"
|
||||
|
||||
} // namespace
|
||||
166
src/interaction/keyframerecordinghandler.cpp
Normal file
166
src/interaction/keyframerecordinghandler.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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/interaction/keyframerecordinghandler.h>
|
||||
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/network/messagestructureshelper.h>
|
||||
#include <openspace/util/timemanager.h>
|
||||
|
||||
#include "keyframerecordinghandler_lua.inl"
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "KeyframeRecording";
|
||||
} // namespace
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
KeyframeRecordingHandler::KeyframeRecordingHandler()
|
||||
: properties::PropertyOwner({ "KeyframeRecording", "Keyframe Recording" })
|
||||
{}
|
||||
|
||||
void KeyframeRecordingHandler::newSequence() {
|
||||
_timeline = SessionRecording();
|
||||
}
|
||||
|
||||
void KeyframeRecordingHandler::addCameraKeyframe(double sequenceTime) {
|
||||
using namespace datamessagestructures;
|
||||
CameraKeyframe kf = datamessagestructures::generateCameraKeyframe();
|
||||
|
||||
SessionRecording::Entry entry = {
|
||||
sequenceTime,
|
||||
global::timeManager->time().j2000Seconds(),
|
||||
KeyframeNavigator::CameraPose(std::move(kf))
|
||||
};
|
||||
|
||||
auto it = std::upper_bound(
|
||||
_timeline.entries.begin(),
|
||||
_timeline.entries.end(),
|
||||
sequenceTime,
|
||||
[](double value, const SessionRecording::Entry& e) {
|
||||
return value < e.timestamp;
|
||||
}
|
||||
);
|
||||
_timeline.entries.insert(it, std::move(entry));
|
||||
}
|
||||
|
||||
void KeyframeRecordingHandler::addScriptKeyframe(double sequenceTime, std::string script)
|
||||
{
|
||||
SessionRecording::Entry entry = {
|
||||
sequenceTime,
|
||||
global::timeManager->time().j2000Seconds(),
|
||||
std::move(script)
|
||||
};
|
||||
|
||||
auto it = std::upper_bound(
|
||||
_timeline.entries.begin(),
|
||||
_timeline.entries.end(),
|
||||
sequenceTime,
|
||||
[](double value, const SessionRecording::Entry& e) {
|
||||
return value < e.timestamp;
|
||||
}
|
||||
);
|
||||
_timeline.entries.insert(it, std::move(entry));
|
||||
}
|
||||
|
||||
void KeyframeRecordingHandler::removeKeyframe(int index) {
|
||||
if (index < 0 || static_cast<size_t>(index) >(_timeline.entries.size() - 1)) {
|
||||
throw ghoul::RuntimeError(std::format("Index {} out of range", index));
|
||||
}
|
||||
_timeline.entries.erase(_timeline.entries.begin() + index);
|
||||
}
|
||||
|
||||
void KeyframeRecordingHandler::updateKeyframe(int index) {
|
||||
using namespace datamessagestructures;
|
||||
if (index < 0 || static_cast<size_t>(index) > (_timeline.entries.size() - 1)) {
|
||||
throw ghoul::RuntimeError(std::format("Index {} out of range", index));
|
||||
}
|
||||
|
||||
SessionRecording::Entry& entry = _timeline.entries[index];
|
||||
if (!std::holds_alternative<SessionRecording::Entry::Camera>(entry.value)) {
|
||||
throw ghoul::RuntimeError(std::format("Index {} is not a camera frame", index));
|
||||
}
|
||||
auto& camera = std::get<SessionRecording::Entry::Camera>(entry.value);
|
||||
camera = KeyframeNavigator::CameraPose(generateCameraKeyframe());
|
||||
}
|
||||
|
||||
void KeyframeRecordingHandler::moveKeyframe(int index, double sequenceTime) {
|
||||
if (index < 0 || static_cast<size_t>(index) >(_timeline.entries.size() - 1)) {
|
||||
throw ghoul::RuntimeError(std::format("Index {} out of range", index));
|
||||
}
|
||||
|
||||
_timeline.entries[index].timestamp = sequenceTime;
|
||||
std::sort(
|
||||
_timeline.entries.begin(),
|
||||
_timeline.entries.end(),
|
||||
[](const SessionRecording::Entry& lhs, const SessionRecording::Entry& rhs) {
|
||||
return lhs.timestamp < rhs.timestamp;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void KeyframeRecordingHandler::saveSequence(std::filesystem::path filename) {
|
||||
if (filename.empty()) {
|
||||
throw ghoul::RuntimeError("Failed to save file, reason: Invalid empty file name");
|
||||
}
|
||||
|
||||
saveSessionRecording(filename, _timeline, DataMode::Ascii);
|
||||
}
|
||||
|
||||
void KeyframeRecordingHandler::loadSequence(std::filesystem::path filename) {
|
||||
_timeline = loadSessionRecording(filename);
|
||||
}
|
||||
|
||||
void KeyframeRecordingHandler::play() {
|
||||
global::sessionRecordingHandler->startPlayback(_timeline, false, false, std::nullopt);
|
||||
}
|
||||
|
||||
bool KeyframeRecordingHandler::hasKeyframeRecording() const {
|
||||
return !_timeline.entries.empty();
|
||||
}
|
||||
|
||||
std::vector<ghoul::Dictionary> KeyframeRecordingHandler::keyframes() const {
|
||||
return sessionRecordingToDictionary(_timeline);
|
||||
}
|
||||
|
||||
scripting::LuaLibrary KeyframeRecordingHandler::luaLibrary() {
|
||||
return {
|
||||
"keyframeRecording",
|
||||
{
|
||||
codegen::lua::NewSequence,
|
||||
codegen::lua::AddCameraKeyframe,
|
||||
codegen::lua::AddScriptKeyframe,
|
||||
codegen::lua::RemoveKeyframe,
|
||||
codegen::lua::UpdateKeyframe,
|
||||
codegen::lua::MoveKeyframe,
|
||||
codegen::lua::SaveSequence,
|
||||
codegen::lua::LoadSequence,
|
||||
codegen::lua::Play,
|
||||
codegen::lua::Pause,
|
||||
codegen::lua::Keyframes
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace openspace::interaction
|
||||
108
src/interaction/keyframerecordinghandler_lua.inl
Normal file
108
src/interaction/keyframerecordinghandler_lua.inl
Normal file
@@ -0,0 +1,108 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2023 *
|
||||
* *
|
||||
* 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/engine/globals.h>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
|
||||
// Starts a new sequence of keyframes, any previously loaded sequence is discarded
|
||||
[[codegen::luawrap]] void newSequence() {
|
||||
using namespace openspace;
|
||||
global::keyframeRecording->newSequence();
|
||||
}
|
||||
|
||||
// Adds a keyframe at the specified sequence-time
|
||||
[[codegen::luawrap]] void addCameraKeyframe(double sequenceTime) {
|
||||
using namespace openspace;
|
||||
global::keyframeRecording->addCameraKeyframe(sequenceTime);
|
||||
}
|
||||
|
||||
// Adds a keyframe at the specified sequence-time
|
||||
[[codegen::luawrap]] void addScriptKeyframe(double sequenceTime, std::string script) {
|
||||
using namespace openspace;
|
||||
global::keyframeRecording->addScriptKeyframe(sequenceTime, std::move(script));
|
||||
}
|
||||
|
||||
// Removes a keyframe at the specified 0-based index
|
||||
[[codegen::luawrap]] void removeKeyframe(int index) {
|
||||
using namespace openspace;
|
||||
global::keyframeRecording->removeKeyframe(index);
|
||||
}
|
||||
|
||||
// Update the camera position at keyframe specified by the 0-based index
|
||||
[[codegen::luawrap]] void updateKeyframe(int index) {
|
||||
using namespace openspace;
|
||||
global::keyframeRecording->updateKeyframe(index);
|
||||
}
|
||||
|
||||
// Move keyframe of `index` to the new specified `sequenceTime`
|
||||
[[codegen::luawrap]] void moveKeyframe(int index, double sequenceTime) {
|
||||
using namespace openspace;
|
||||
global::keyframeRecording->moveKeyframe(index, sequenceTime);
|
||||
}
|
||||
|
||||
// Saves the current sequence of keyframes to disk by the optionally specified `filename`.
|
||||
[[codegen::luawrap]] void saveSequence(std::filesystem::path filename) {
|
||||
using namespace openspace;
|
||||
global::keyframeRecording->saveSequence(std::move(filename));
|
||||
}
|
||||
|
||||
// Loads a sequence from the specified file
|
||||
[[codegen::luawrap]] void loadSequence(std::filesystem::path filename) {
|
||||
using namespace openspace;
|
||||
global::keyframeRecording->loadSequence(std::move(filename));
|
||||
}
|
||||
|
||||
// Playback sequence optionally from the specified `sequenceTime` or if not specified
|
||||
// starts playing from the current time set within the sequence
|
||||
[[codegen::luawrap]] void play(std::optional<double> sequenceTime) {
|
||||
using namespace openspace;
|
||||
|
||||
global::keyframeRecording->play();
|
||||
if (sequenceTime.has_value()) {
|
||||
global::sessionRecordingHandler->seek(*sequenceTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Pauses a playing sequence
|
||||
[[codegen::luawrap]] void pause() {
|
||||
using namespace openspace;
|
||||
global::sessionRecordingHandler->setPlaybackPause(true);
|
||||
}
|
||||
|
||||
// Returns `true` if there currently is a sequence loaded, otherwise `false`
|
||||
[[codegen::luawrap]] bool hasKeyframeRecording() {
|
||||
using namespace openspace;
|
||||
return global::keyframeRecording->hasKeyframeRecording();
|
||||
}
|
||||
|
||||
[[codegen::luawrap]] std::vector<ghoul::Dictionary> keyframes() {
|
||||
using namespace openspace;
|
||||
return global::keyframeRecording->keyframes();
|
||||
}
|
||||
|
||||
#include "keyframerecordinghandler_lua_codegen.cpp"
|
||||
|
||||
} // namespace
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,212 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* Starts a recording session. The string argument is the filename used for the file where
|
||||
* the recorded keyframes are saved. The file data format is binary.
|
||||
*/
|
||||
[[codegen::luawrap]] void startRecording(std::string recordFilePath) {
|
||||
using namespace openspace;
|
||||
|
||||
if (recordFilePath.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string is empty");
|
||||
}
|
||||
global::sessionRecording->setRecordDataFormat(
|
||||
interaction::SessionRecording::DataMode::Binary
|
||||
);
|
||||
global::sessionRecording->startRecording(recordFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a recording session. The string argument is the filename used for the file where
|
||||
* the recorded keyframes are saved. The file data format is ASCII.
|
||||
*/
|
||||
[[codegen::luawrap]] void startRecordingAscii(std::string recordFilePath) {
|
||||
using namespace openspace;
|
||||
|
||||
if (recordFilePath.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string is empty");
|
||||
}
|
||||
global::sessionRecording->setRecordDataFormat(
|
||||
interaction::SessionRecording::DataMode::Ascii
|
||||
);
|
||||
global::sessionRecording->startRecording(recordFilePath);
|
||||
}
|
||||
|
||||
// Stops a recording session.
|
||||
[[codegen::luawrap]] void stopRecording() {
|
||||
openspace::global::sessionRecording->stopRecording();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a playback session with keyframe times that are relative to the time since the
|
||||
* recording was started (the same relative time applies to the playback). When playback
|
||||
* starts, the simulation time is automatically set to what it was at recording time. The
|
||||
* string argument is the filename to pull playback keyframes from (the file path is
|
||||
* relative to the RECORDINGS variable specified in the config file). If a second input
|
||||
* value of true is given, then playback will continually loop until it is manually
|
||||
* stopped.
|
||||
*/
|
||||
[[codegen::luawrap("startPlayback")]] void startPlaybackDefault(std::string file,
|
||||
bool loop = false,
|
||||
bool shouldWaitForTiles = true)
|
||||
{
|
||||
using namespace openspace;
|
||||
|
||||
if (file.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string is empty");
|
||||
}
|
||||
global::sessionRecording->startPlayback(
|
||||
file,
|
||||
interaction::KeyframeTimeRef::Relative_recordedStart,
|
||||
true,
|
||||
loop,
|
||||
shouldWaitForTiles
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a playback session with keyframe times that are relative to application time
|
||||
* (seconds since OpenSpace application started). The string argument is the filename to
|
||||
* pull playback keyframes from (the file path is relative to the RECORDINGS variable
|
||||
* specified in the config file).
|
||||
*/
|
||||
[[codegen::luawrap]] void startPlaybackApplicationTime(std::string file) {
|
||||
using namespace openspace;
|
||||
|
||||
if (file.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string is empty");
|
||||
}
|
||||
global::sessionRecording->startPlayback(
|
||||
file,
|
||||
interaction::KeyframeTimeRef::Relative_applicationStart,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a playback session with keyframe times that are relative to the time since the
|
||||
* recording was started (the same relative time applies to the playback). The string
|
||||
* argument is the filename to pull playback keyframes from (the file path is relative to
|
||||
* the RECORDINGS variable specified in the config file). If a second input value of true
|
||||
* is given, then playback will continually loop until it is manually stopped.
|
||||
*/
|
||||
[[codegen::luawrap]] void startPlaybackRecordedTime(std::string file, bool loop = false) {
|
||||
using namespace openspace;
|
||||
|
||||
if (file.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string is empty");
|
||||
}
|
||||
global::sessionRecording->startPlayback(
|
||||
file,
|
||||
interaction::KeyframeTimeRef::Relative_recordedStart,
|
||||
false,
|
||||
loop,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a playback session with keyframe times that are relative to the simulated date &
|
||||
* time. The string argument is the filename to pull playback keyframes from (the file
|
||||
* path is relative to the RECORDINGS variable specified in the config file).
|
||||
*/
|
||||
[[codegen::luawrap]] void startPlaybackSimulationTime(std::string file) {
|
||||
using namespace openspace;
|
||||
|
||||
if (file.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string is empty");
|
||||
}
|
||||
global::sessionRecording->startPlayback(
|
||||
file,
|
||||
interaction::KeyframeTimeRef::Absolute_simTimeJ2000,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// Stops a playback session before playback of all keyframes is complete.
|
||||
[[codegen::luawrap]] void stopPlayback() {
|
||||
openspace::global::sessionRecording->stopPlayback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables that rendered frames should be saved during playback. The parameter determines
|
||||
* the number of frames that are exported per second if this value is not provided, 60
|
||||
* frames per second will be exported.
|
||||
*/
|
||||
[[codegen::luawrap]] void enableTakeScreenShotDuringPlayback(int fps = 60) {
|
||||
openspace::global::sessionRecording->enableTakeScreenShotDuringPlayback(fps);
|
||||
}
|
||||
|
||||
// Used to disable that renderings are saved during playback.
|
||||
[[codegen::luawrap]] void disableTakeScreenShotDuringPlayback() {
|
||||
openspace::global::sessionRecording->disableTakeScreenShotDuringPlayback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a conversion of the specified file to the most most recent file format,
|
||||
* creating a copy of the recording file.
|
||||
*/
|
||||
[[codegen::luawrap]] void fileFormatConversion(std::string convertFilePath) {
|
||||
if (convertFilePath.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string must not be empty");
|
||||
}
|
||||
openspace::global::sessionRecording->convertFile(convertFilePath);
|
||||
}
|
||||
|
||||
// Pauses or resumes the playback progression through keyframes.
|
||||
[[codegen::luawrap]] void setPlaybackPause(bool pause) {
|
||||
openspace::global::sessionRecording->setPlaybackPause(pause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the pause function, i.e. temporarily setting the delta time to 0 and restoring
|
||||
* it afterwards.
|
||||
*/
|
||||
[[codegen::luawrap]] void togglePlaybackPause() {
|
||||
using namespace openspace;
|
||||
|
||||
bool isPlaybackPaused = global::sessionRecording->isPlaybackPaused();
|
||||
global::sessionRecording->setPlaybackPause(!isPlaybackPaused);
|
||||
}
|
||||
|
||||
// Returns true if session recording is currently playing back a recording.
|
||||
[[codegen::luawrap]] bool isPlayingBack() {
|
||||
return openspace::global::sessionRecording->isPlayingBack();
|
||||
}
|
||||
|
||||
// Returns true if session recording is currently recording a recording.
|
||||
[[codegen::luawrap]] bool isRecording() {
|
||||
return openspace::global::sessionRecording->isRecording();
|
||||
}
|
||||
|
||||
#include "sessionrecording_lua_codegen.cpp"
|
||||
|
||||
} // namespace
|
||||
786
src/interaction/sessionrecordinghandler.cpp
Normal file
786
src/interaction/sessionrecordinghandler.cpp
Normal file
@@ -0,0 +1,786 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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/interaction/sessionrecordinghandler.h>
|
||||
|
||||
#include <openspace/camera/camera.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/openspaceengine.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/events/eventengine.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <openspace/network/messagestructureshelper.h>
|
||||
#include <openspace/query/query.h>
|
||||
#include <openspace/rendering/renderable.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/scene/scene.h>
|
||||
#include <openspace/scene/scenegraphnode.h>
|
||||
#include <openspace/util/timemanager.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/font/fontmanager.h>
|
||||
#include <ghoul/font/fontrenderer.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <Windows.h>
|
||||
#endif // WIN32
|
||||
|
||||
#include "sessionrecordinghandler_lua.inl"
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "SessionRecording";
|
||||
|
||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo RenderPlaybackInfo = {
|
||||
"RenderInfo",
|
||||
"Render Playback Information",
|
||||
"If enabled, information about a currently played back session recording is "
|
||||
"rendering to screen.",
|
||||
openspace::properties::Property::Visibility::AdvancedUser
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo IgnoreRecordedScaleInfo = {
|
||||
"IgnoreRecordedScale",
|
||||
"Ignore Recorded Scale",
|
||||
"If this value is enabled, the scale value from a recording is ignored and the "
|
||||
"computed values are used instead.",
|
||||
openspace::properties::Property::Visibility::AdvancedUser
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo AddModelMatrixinAsciiInfo = {
|
||||
"AddModelMatrixinAscii",
|
||||
"Add Model Matrix in ASCII recording",
|
||||
"If this is 'true', the model matrix is written into the ASCII recording format "
|
||||
"in the line before each camera keyframe. The model matrix is the full matrix "
|
||||
"that converts the position into a J2000+Galactic reference frame.",
|
||||
openspace::properties::Property::Visibility::Developer
|
||||
};
|
||||
|
||||
const std::string FileExtensionBinary = ".osrec";
|
||||
const std::string FileExtensionAscii = ".osrectxt";
|
||||
|
||||
constexpr std::string_view ScriptReturnPrefix = "return ";
|
||||
} // namespace
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
SessionRecordingHandler::SessionRecordingHandler()
|
||||
: properties::PropertyOwner({ "SessionRecording", "Session Recording" })
|
||||
, _renderPlaybackInformation(RenderPlaybackInfo, false)
|
||||
, _ignoreRecordedScale(IgnoreRecordedScaleInfo, false)
|
||||
, _addModelMatrixinAscii(AddModelMatrixinAsciiInfo, false)
|
||||
{
|
||||
addProperty(_renderPlaybackInformation);
|
||||
addProperty(_ignoreRecordedScale);
|
||||
addProperty(_addModelMatrixinAscii);
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::preSynchronization(double dt) {
|
||||
ZoneScoped;
|
||||
|
||||
if (_state == SessionState::Recording) {
|
||||
tickRecording(dt);
|
||||
}
|
||||
else if (isPlayingBack()) {
|
||||
tickPlayback(dt);
|
||||
}
|
||||
|
||||
// Handle callback(s) for change in idle/record/playback state
|
||||
if (_state != _lastState) {
|
||||
using K = CallbackHandle;
|
||||
using V = StateChangeCallback;
|
||||
for (const std::pair<const K, V>& it : _stateChangeCallbacks) {
|
||||
it.second();
|
||||
}
|
||||
_lastState = _state;
|
||||
}
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::tickPlayback(double dt) {
|
||||
if (_state == SessionState::PlaybackPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double previousTime = _playback.elapsedTime;
|
||||
_playback.elapsedTime += dt;
|
||||
|
||||
// Find the first value whose recording time is past now
|
||||
std::vector<SessionRecording::Entry>::const_iterator probe = _currentEntry;
|
||||
while (probe != _timeline.entries.end() && _playback.elapsedTime > probe->timestamp) {
|
||||
probe++;
|
||||
}
|
||||
|
||||
// All script entries between _previous and now have to be applied
|
||||
for (auto& it = _currentEntry; it != probe; it++) {
|
||||
if (std::holds_alternative<SessionRecording::Entry::Script>(it->value)) {
|
||||
global::scriptEngine->queueScript(
|
||||
std::get<SessionRecording::Entry::Script>(it->value)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ... < _previous < ... < prevCamera <= now <= nextCamera < ...
|
||||
std::vector<SessionRecording::Entry>::const_iterator prevCamera = probe - 1;
|
||||
while (prevCamera != _timeline.entries.begin() &&
|
||||
!std::holds_alternative<SessionRecording::Entry::Camera>(prevCamera->value))
|
||||
{
|
||||
prevCamera--;
|
||||
}
|
||||
// We need to check this here as there might be a chance that the first camera entry
|
||||
// is after the current elapsed time, which would result in `prevCamera` being equal
|
||||
// to `begin()` but not being a camera keyframe
|
||||
const bool hasValidPrevCamera =
|
||||
std::holds_alternative<SessionRecording::Entry::Camera>(prevCamera->value);
|
||||
|
||||
std::vector<SessionRecording::Entry>::const_iterator nextCamera = probe;
|
||||
while (nextCamera != _timeline.entries.end() &&
|
||||
!std::holds_alternative<SessionRecording::Entry::Camera>(nextCamera->value))
|
||||
{
|
||||
nextCamera++;
|
||||
}
|
||||
// Same argument but in reverse from the `hasValidPrevCamera` comment
|
||||
const bool hasValidNextCamera =
|
||||
(nextCamera != _timeline.entries.end()) &&
|
||||
std::holds_alternative<SessionRecording::Entry::Camera>(nextCamera->value);
|
||||
|
||||
|
||||
if (!hasValidPrevCamera && !hasValidNextCamera) {
|
||||
throw ghoul::RuntimeError("No valid camera keyframes found in recording");
|
||||
}
|
||||
|
||||
// update camera with or without new keyframes
|
||||
const auto& prevPose =
|
||||
hasValidPrevCamera ?
|
||||
std::get<SessionRecording::Entry::Camera>(prevCamera->value) :
|
||||
std::get<SessionRecording::Entry::Camera>(nextCamera->value);
|
||||
const double prevTime =
|
||||
hasValidPrevCamera ?
|
||||
prevCamera->timestamp :
|
||||
0.0;
|
||||
|
||||
const auto& nextPose =
|
||||
hasValidNextCamera ?
|
||||
std::get<SessionRecording::Entry::Camera>(nextCamera->value) :
|
||||
std::get<SessionRecording::Entry::Camera>(prevCamera->value);
|
||||
const double nextTime =
|
||||
hasValidNextCamera ?
|
||||
nextCamera->timestamp :
|
||||
_timeline.entries.back().timestamp;
|
||||
|
||||
// Need to actively update the focusNode position of the camera in relation to
|
||||
// the rendered objects will be unstable and actually incorrect
|
||||
const SceneGraphNode* n = sceneGraphNode(prevPose.focusNode);
|
||||
if (n) {
|
||||
global::navigationHandler->orbitalNavigator().setFocusNode(n->identifier());
|
||||
}
|
||||
|
||||
const double t = std::clamp(
|
||||
(_playback.elapsedTime - prevTime) / (nextTime - prevTime),
|
||||
0.0,
|
||||
1.0
|
||||
);
|
||||
|
||||
interaction::KeyframeNavigator::updateCamera(
|
||||
global::navigationHandler->camera(),
|
||||
prevPose,
|
||||
nextPose,
|
||||
t,
|
||||
_ignoreRecordedScale
|
||||
);
|
||||
|
||||
if (isSavingFramesDuringPlayback()) {
|
||||
ghoul_assert(dt == _playback.saveScreenshots.deltaTime, "Misaligned delta times");
|
||||
|
||||
// Check if renderable in focus is still resolving tile loading
|
||||
// do not adjust time while we are doing this, or take screenshot
|
||||
const SceneGraphNode* focusNode =
|
||||
global::navigationHandler->orbitalNavigator().anchorNode();
|
||||
const Renderable* focusRenderable = focusNode->renderable();
|
||||
if (!_playback.waitForLoading ||
|
||||
!focusRenderable || focusRenderable->renderedWithDesiredData())
|
||||
{
|
||||
_playback.saveScreenshots.currentRecordedTime +=
|
||||
std::chrono::microseconds(static_cast<long>(dt * 1000000));
|
||||
_playback.saveScreenshots.currentApplicationTime += dt;
|
||||
|
||||
// Unfortunately the first frame is sometimes rendered because globebrowsing
|
||||
// reports that all chunks are rendered when they apparently are not.
|
||||
// previousTime == 0.0 -> rendering the first frame
|
||||
if (previousTime != 0.0) {
|
||||
global::renderEngine->takeScreenshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_currentEntry = probe;
|
||||
if (probe == _timeline.entries.end()) {
|
||||
if (_playback.isLooping) {
|
||||
_playback.saveScreenshots.enabled = false;
|
||||
setupPlayback(global::windowDelegate->applicationTime());
|
||||
}
|
||||
else {
|
||||
stopPlayback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::tickRecording(double dt) {
|
||||
_recording.elapsedTime += dt;
|
||||
|
||||
using namespace datamessagestructures;
|
||||
CameraKeyframe kf = generateCameraKeyframe();
|
||||
_timeline.entries.emplace_back(
|
||||
_recording.elapsedTime,
|
||||
global::timeManager->time().j2000Seconds(),
|
||||
KeyframeNavigator::CameraPose(std::move(kf))
|
||||
);
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::render() const {
|
||||
if (!_renderPlaybackInformation || !isPlayingBack()) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr std::string_view FontName = "Mono";
|
||||
constexpr float FontSizeFrameinfo = 32.f;
|
||||
const std::shared_ptr<ghoul::fontrendering::Font> font =
|
||||
global::fontManager->font(FontName, FontSizeFrameinfo);
|
||||
|
||||
glm::vec2 penPosition = global::renderEngine->fontResolution() - glm::ivec2(150, 0);
|
||||
const std::string text = std::format(
|
||||
"Elapsed: {:.3f} / {}\n"
|
||||
"Keyframe: {} / {}\n"
|
||||
"Is Looping: {}\n"
|
||||
"Saving frames: {}\n"
|
||||
"Wait for Loading: {}\n"
|
||||
"Scale: {}",
|
||||
_playback.elapsedTime, _timeline.entries.back().timestamp,
|
||||
std::distance(_timeline.entries.begin(), _currentEntry), _timeline.entries.size(),
|
||||
_playback.isLooping ? "true" : "false",
|
||||
_playback.saveScreenshots.enabled ? "true" : "false",
|
||||
_playback.waitForLoading ? "true" : "false",
|
||||
global::navigationHandler->camera()->scaling()
|
||||
);
|
||||
ghoul::fontrendering::RenderFont(
|
||||
*font,
|
||||
penPosition,
|
||||
text,
|
||||
glm::vec4(1.f),
|
||||
ghoul::fontrendering::CrDirection::Down
|
||||
);
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::startRecording() {
|
||||
if (_state == SessionState::Recording) {
|
||||
throw ghoul::RuntimeError(
|
||||
"Unable to start recording while already in recording mode",
|
||||
"SessionRecordingHandler"
|
||||
);
|
||||
}
|
||||
else if (isPlayingBack()) {
|
||||
throw ghoul::RuntimeError(
|
||||
"Unable to start recording while in session playback mode",
|
||||
"SessionRecordingHandler"
|
||||
);
|
||||
}
|
||||
|
||||
LINFO("Session recording started");
|
||||
|
||||
_state = SessionState::Recording;
|
||||
_timeline = SessionRecording();
|
||||
_savePropertiesBaseline.clear();
|
||||
_recording.elapsedTime = 0.0;
|
||||
|
||||
// Record the current delta time as the first property to save in the file.
|
||||
// This needs to be saved as a baseline whether or not it changes during recording
|
||||
// Dummy `_time` "property" to store the time setup in the baseline
|
||||
_savePropertiesBaseline["_time"] = std::format(
|
||||
"openspace.time.setPause({});openspace.time.setDeltaTime({});",
|
||||
global::timeManager->isPaused() ? "true" : "false",
|
||||
global::timeManager->targetDeltaTime()
|
||||
);
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::stopRecording(const std::filesystem::path& filename,
|
||||
DataMode dataMode)
|
||||
{
|
||||
if (_state != SessionState::Recording) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::filesystem::is_regular_file(filename)) {
|
||||
throw ghoul::RuntimeError(std::format(
|
||||
"Unable to start recording. File '{}' already exists", filename
|
||||
), "SessionRecording");
|
||||
}
|
||||
|
||||
for (const auto& [prop, script] : _savePropertiesBaseline) {
|
||||
_timeline.entries.insert(_timeline.entries.begin(), { 0.0, 0.0, script });
|
||||
}
|
||||
|
||||
saveSessionRecording(filename, _timeline, dataMode);
|
||||
_state = SessionState::Idle;
|
||||
cleanUpTimelinesAndKeyframes();
|
||||
LINFO("Session recording stopped");
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::startPlayback(SessionRecording timeline, bool loop,
|
||||
bool shouldWaitForFinishedTiles,
|
||||
std::optional<int> saveScreenshotFps)
|
||||
{
|
||||
OpenSpaceEngine::Mode prevMode = global::openSpaceEngine->currentMode();
|
||||
const bool canTriggerPlayback = global::openSpaceEngine->setMode(
|
||||
OpenSpaceEngine::Mode::SessionRecordingPlayback
|
||||
);
|
||||
if (!canTriggerPlayback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_state == SessionState::Recording) {
|
||||
global::openSpaceEngine->setMode(prevMode);
|
||||
throw ghoul::RuntimeError(
|
||||
"Unable to start playback while in session recording mode"
|
||||
);
|
||||
}
|
||||
else if (isPlayingBack()) {
|
||||
global::openSpaceEngine->setMode(prevMode);
|
||||
throw ghoul::RuntimeError(
|
||||
"Unable to start new playback while in session playback mode"
|
||||
);
|
||||
}
|
||||
|
||||
_playback.isLooping = loop;
|
||||
_playback.waitForLoading = shouldWaitForFinishedTiles;
|
||||
|
||||
timeline.forAll<SessionRecording::Entry::Script>(
|
||||
[this](const SessionRecording::Entry::Script& script) {
|
||||
checkIfScriptUsesScenegraphNode(script);
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
if (timeline.entries.empty()) {
|
||||
global::openSpaceEngine->setMode(prevMode);
|
||||
throw ghoul::RuntimeError("Session recording is empty");
|
||||
}
|
||||
if (!timeline.hasCameraFrame()) {
|
||||
global::openSpaceEngine->setMode(prevMode);
|
||||
throw ghoul::RuntimeError("Session recording did not contain camera keyframes");
|
||||
}
|
||||
|
||||
_timeline = std::move(timeline);
|
||||
|
||||
// Populate list of loaded scene graph nodes
|
||||
_loadedNodes.clear();
|
||||
const std::vector<SceneGraphNode*> nodes = sceneGraph()->allSceneGraphNodes();
|
||||
for (SceneGraphNode* n : nodes) {
|
||||
_loadedNodes.push_back(n->identifier());
|
||||
}
|
||||
|
||||
double now = global::windowDelegate->applicationTime();
|
||||
setupPlayback(now);
|
||||
_playback.saveScreenshots.enabled = saveScreenshotFps.has_value();
|
||||
if (saveScreenshotFps.has_value()) {
|
||||
_playback.saveScreenshots.deltaTime = 1.0 / *saveScreenshotFps;
|
||||
}
|
||||
|
||||
global::navigationHandler->orbitalNavigator().updateOnCameraInteraction();
|
||||
|
||||
LINFO("Playback session started");
|
||||
global::eventEngine->publishEvent<events::EventSessionRecordingPlayback>(
|
||||
events::EventSessionRecordingPlayback::State::Started
|
||||
);
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::setupPlayback(double startTime) {
|
||||
_playback.elapsedTime = 0.0;
|
||||
_playback.saveScreenshots.currentRecordedTime = std::chrono::steady_clock::now();
|
||||
_playback.saveScreenshots.currentApplicationTime =
|
||||
global::windowDelegate->applicationTime();
|
||||
global::navigationHandler->keyframeNavigator().setTimeReferenceMode(
|
||||
KeyframeTimeRef::Relative_recordedStart, startTime);
|
||||
|
||||
|
||||
auto firstCamera = _timeline.entries.begin();
|
||||
while (firstCamera != _timeline.entries.end() &&
|
||||
!std::holds_alternative<SessionRecording::Entry::Camera>(firstCamera->value))
|
||||
{
|
||||
firstCamera++;
|
||||
}
|
||||
|
||||
std::string startFocusNode =
|
||||
std::get<SessionRecording::Entry::Camera>(firstCamera->value).focusNode;
|
||||
auto it = std::find(_loadedNodes.begin(), _loadedNodes.end(), startFocusNode);
|
||||
if (it == _loadedNodes.end()) {
|
||||
throw ghoul::RuntimeError(std::format(
|
||||
"Playback file requires scenegraph node '{}', which is "
|
||||
"not currently loaded", startFocusNode
|
||||
));
|
||||
}
|
||||
|
||||
global::timeManager->setTimeNextFrame(Time(firstCamera->simulationTime));
|
||||
_currentEntry = _timeline.entries.begin();
|
||||
_state = SessionState::Playback;
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::seek(double recordingTime) {
|
||||
_currentEntry = std::upper_bound(
|
||||
_timeline.entries.begin(),
|
||||
_timeline.entries.end(),
|
||||
recordingTime,
|
||||
[](double recordingTime, const SessionRecording::Entry& e) {
|
||||
return recordingTime < e.timestamp;
|
||||
}
|
||||
);
|
||||
_playback.elapsedTime = recordingTime;
|
||||
}
|
||||
|
||||
bool SessionRecordingHandler::isPlaybackPaused() const {
|
||||
return (_state == SessionState::PlaybackPaused);
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::setPlaybackPause(bool pause) {
|
||||
if (pause && _state == SessionState::Playback) {
|
||||
_playback.playbackPausedWithDeltaTimePause = global::timeManager->isPaused();
|
||||
if (!_playback.playbackPausedWithDeltaTimePause) {
|
||||
global::timeManager->setPause(true);
|
||||
}
|
||||
_state = SessionState::PlaybackPaused;
|
||||
global::eventEngine->publishEvent<events::EventSessionRecordingPlayback>(
|
||||
events::EventSessionRecordingPlayback::State::Paused
|
||||
);
|
||||
}
|
||||
else if (!pause && _state == SessionState::PlaybackPaused) {
|
||||
if (!_playback.playbackPausedWithDeltaTimePause) {
|
||||
global::timeManager->setPause(false);
|
||||
}
|
||||
_state = SessionState::Playback;
|
||||
global::eventEngine->publishEvent<events::EventSessionRecordingPlayback>(
|
||||
events::EventSessionRecordingPlayback::State::Resumed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::stopPlayback() {
|
||||
if (!isPlayingBack()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LINFO("Session playback finished");
|
||||
_state = SessionState::Idle;
|
||||
cleanUpTimelinesAndKeyframes();
|
||||
global::eventEngine->publishEvent<events::EventSessionRecordingPlayback>(
|
||||
events::EventSessionRecordingPlayback::State::Finished
|
||||
);
|
||||
global::openSpaceEngine->resetMode();
|
||||
global::navigationHandler->resetNavigationUpdateVariables();
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::cleanUpTimelinesAndKeyframes() {
|
||||
_timeline = SessionRecording();
|
||||
_savePropertiesBaseline.clear();
|
||||
_loadedNodes.clear();
|
||||
_currentEntry = _timeline.entries.end();
|
||||
_playback.saveScreenshots.enabled = false;
|
||||
_playback.isLooping = false;
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::saveScriptKeyframeToTimeline(std::string script) {
|
||||
constexpr std::array<std::string_view, 6> ScriptRejects = {
|
||||
"openspace.sessionRecording.enableTakeScreenShotDuringPlayback",
|
||||
"openspace.sessionRecording.startPlayback",
|
||||
"openspace.sessionRecording.stopPlayback",
|
||||
"openspace.sessionRecording.startRecording",
|
||||
"openspace.sessionRecording.stopRecording",
|
||||
"openspace.scriptScheduler.clear"
|
||||
};
|
||||
|
||||
constexpr std::array<std::string_view, 1> ScriptsToBeTrimmed = {
|
||||
"openspace.sessionRecording.togglePlaybackPause"
|
||||
};
|
||||
|
||||
if (script.starts_with(ScriptReturnPrefix)) {
|
||||
script = script.substr(ScriptReturnPrefix.length());
|
||||
}
|
||||
for (std::string_view reject : ScriptRejects) {
|
||||
if (script.starts_with(reject)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim commands from script if found
|
||||
for (std::string_view trimSnippet : ScriptsToBeTrimmed) {
|
||||
auto findIdx = script.find(trimSnippet);
|
||||
if (findIdx != std::string::npos) {
|
||||
auto findClosingParens = script.find_first_of(')', findIdx);
|
||||
script.erase(findIdx, findClosingParens + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Any script snippet included in this vector will be trimmed from any script
|
||||
// from the script manager, before it is recorded in the session recording file.
|
||||
// The remainder of the script will be retained.
|
||||
using ScriptSubstringReplace = std::pair<std::string_view, std::string_view>;
|
||||
constexpr std::array<ScriptSubstringReplace, 2> ScriptsToBeReplaced = {
|
||||
std::pair {
|
||||
"openspace.time.pauseToggleViaKeyboard",
|
||||
"openspace.time.interpolateTogglePause"
|
||||
}
|
||||
};
|
||||
|
||||
// Replace commands from script if found
|
||||
for (const ScriptSubstringReplace& replacementSnippet : ScriptsToBeReplaced) {
|
||||
auto findIdx = script.find(replacementSnippet.first);
|
||||
if (findIdx != std::string::npos) {
|
||||
script.erase(findIdx, replacementSnippet.first.length());
|
||||
script.insert(findIdx, replacementSnippet.second);
|
||||
}
|
||||
}
|
||||
|
||||
_timeline.entries.emplace_back(
|
||||
_recording.elapsedTime,
|
||||
global::timeManager->time().j2000Seconds(),
|
||||
std::move(script)
|
||||
);
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::savePropertyBaseline(properties::Property& prop) {
|
||||
constexpr std::array<std::string_view, 4> PropertyBaselineRejects{
|
||||
"NavigationHandler.OrbitalNavigator.Anchor",
|
||||
"NavigationHandler.OrbitalNavigator.Aim",
|
||||
"NavigationHandler.OrbitalNavigator.RetargetAnchor",
|
||||
"NavigationHandler.OrbitalNavigator.RetargetAim"
|
||||
};
|
||||
|
||||
const std::string propIdentifier = prop.uri();
|
||||
for (std::string_view reject : PropertyBaselineRejects) {
|
||||
if (propIdentifier.starts_with(reject)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const bool isPropAlreadySaved = _savePropertiesBaseline.contains(propIdentifier);
|
||||
if (!isPropAlreadySaved) {
|
||||
const std::string initialScriptCommand = std::format(
|
||||
"openspace.setPropertyValueSingle(\"{}\", {})",
|
||||
propIdentifier, prop.stringValue()
|
||||
);
|
||||
_savePropertiesBaseline[propIdentifier] = initialScriptCommand;
|
||||
}
|
||||
}
|
||||
|
||||
bool SessionRecordingHandler::isRecording() const {
|
||||
return _state == SessionState::Recording;
|
||||
}
|
||||
|
||||
bool SessionRecordingHandler::isPlayingBack() const {
|
||||
return _state == SessionState::Playback || _state == SessionState::PlaybackPaused;
|
||||
}
|
||||
|
||||
bool SessionRecordingHandler::isSavingFramesDuringPlayback() const {
|
||||
return isPlayingBack() && _playback.saveScreenshots.enabled;
|
||||
}
|
||||
|
||||
bool SessionRecordingHandler::shouldWaitForTileLoading() const {
|
||||
return _playback.waitForLoading;
|
||||
}
|
||||
|
||||
SessionRecordingHandler::SessionState SessionRecordingHandler::state() const {
|
||||
return _state;
|
||||
}
|
||||
|
||||
double SessionRecordingHandler::fixedDeltaTimeDuringFrameOutput() const {
|
||||
// Check if renderable in focus is still resolving tile loading
|
||||
// do not adjust time while we are doing this
|
||||
const SceneGraphNode* an = global::navigationHandler->orbitalNavigator().anchorNode();
|
||||
const Renderable* focusRenderable = an->renderable();
|
||||
if (!focusRenderable || focusRenderable->renderedWithDesiredData()) {
|
||||
return _playback.saveScreenshots.deltaTime;
|
||||
}
|
||||
else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point
|
||||
SessionRecordingHandler::currentPlaybackInterpolationTime() const {
|
||||
return _playback.saveScreenshots.currentRecordedTime;
|
||||
}
|
||||
|
||||
double SessionRecordingHandler::currentApplicationInterpolationTime() const {
|
||||
return _playback.saveScreenshots.currentApplicationTime;
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::checkIfScriptUsesScenegraphNode(std::string_view s) const {
|
||||
auto isolateTermFromQuotes = [](std::string_view s) -> std::string_view {
|
||||
// Remove any leading spaces
|
||||
s.remove_prefix(s.find_first_not_of(" "));
|
||||
|
||||
// Find the first substring that is surrounded by possible quotes
|
||||
constexpr std::string_view PossibleQuotes = "\'\"[]";
|
||||
s.remove_prefix(s.find_first_not_of(PossibleQuotes));
|
||||
size_t end = s.find_first_of(PossibleQuotes);
|
||||
if (end != std::string::npos) {
|
||||
return s.substr(0, end);
|
||||
}
|
||||
else {
|
||||
// There were no closing quotes so we remove as much as possible
|
||||
constexpr std::string_view UnwantedChars = " );";
|
||||
s.remove_suffix(s.find_last_not_of(UnwantedChars));
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
auto checkForScenegraphNodeAccessNav = [](std::string_view navTerm) -> bool {
|
||||
constexpr std::array<std::string_view, 3> NavScriptsUsingNodes = {
|
||||
"NavigationHandler.OrbitalNavigator.RetargetAnchor",
|
||||
"NavigationHandler.OrbitalNavigator.Anchor",
|
||||
"NavigationHandler.OrbitalNavigator.Aim"
|
||||
};
|
||||
|
||||
for (std::string_view script : NavScriptsUsingNodes) {
|
||||
if (navTerm.find(script) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (s.starts_with(ScriptReturnPrefix)) {
|
||||
s.remove_prefix(ScriptReturnPrefix.length());
|
||||
}
|
||||
// This works for both setPropertyValue and setPropertyValueSingle
|
||||
if (!s.starts_with("openspace.setPropertyValue") || s.find('(') == std::string::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view subjectOfSetProp = isolateTermFromQuotes(s.substr(s.find('(') + 1));
|
||||
if (checkForScenegraphNodeAccessNav(subjectOfSetProp)) {
|
||||
std::string_view navNode = isolateTermFromQuotes(s.substr(s.find(',') + 1));
|
||||
if (navNode != "nil") {
|
||||
auto it = std::find(_loadedNodes.begin(), _loadedNodes.end(), navNode);
|
||||
if (it == _loadedNodes.end()) {
|
||||
LWARNING(std::format(
|
||||
"Playback file contains a property setting of navigation using "
|
||||
"scenegraph node '{}', which is not currently loaded", navNode
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (subjectOfSetProp.find("Scene.") != std::string::npos) {
|
||||
auto extractScenegraphNodeFromScene = [](std::string_view s) -> std::string_view {
|
||||
constexpr std::string_view Scene = "Scene.";
|
||||
size_t scene = s.find(Scene);
|
||||
if (scene == std::string_view::npos) {
|
||||
return "";
|
||||
}
|
||||
s.remove_prefix(scene + Scene.length());
|
||||
size_t end = s.find('.');
|
||||
return end != std::string_view::npos ? s.substr(0, end) : "";
|
||||
};
|
||||
|
||||
std::string_view found = extractScenegraphNodeFromScene(subjectOfSetProp);
|
||||
if (!found.empty()) {
|
||||
const std::vector<properties::Property*> matchHits =
|
||||
sceneGraph()->propertiesMatchingRegex(subjectOfSetProp);
|
||||
if (matchHits.empty()) {
|
||||
LWARNING(std::format(
|
||||
"Playback file contains a property setting of scenegraph "
|
||||
"node '{}', which is not currently loaded", found
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SessionRecordingHandler::CallbackHandle SessionRecordingHandler::addStateChangeCallback(
|
||||
StateChangeCallback cb)
|
||||
{
|
||||
const CallbackHandle handle = _nextCallbackHandle++;
|
||||
_stateChangeCallbacks.emplace_back(handle, std::move(cb));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void SessionRecordingHandler::removeStateChangeCallback(CallbackHandle handle) {
|
||||
const auto it = std::find_if(
|
||||
_stateChangeCallbacks.begin(),
|
||||
_stateChangeCallbacks.end(),
|
||||
[handle](const std::pair<CallbackHandle, std::function<void()>>& cb) {
|
||||
return cb.first == handle;
|
||||
}
|
||||
);
|
||||
|
||||
ghoul_assert(it != _stateChangeCallbacks.end(), "handle must be a valid callback");
|
||||
_stateChangeCallbacks.erase(it);
|
||||
}
|
||||
|
||||
std::vector<std::string> SessionRecordingHandler::playbackList() const {
|
||||
const std::filesystem::path path = absPath("${RECORDINGS}");
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
|
||||
std::vector<std::string> fileList;
|
||||
namespace fs = std::filesystem;
|
||||
for (const fs::directory_entry& e : fs::directory_iterator(path)) {
|
||||
if (!e.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove path and keep only the filename
|
||||
const std::string filename = e.path().filename().string();
|
||||
#ifdef WIN32
|
||||
DWORD attributes = GetFileAttributes(e.path().string().c_str());
|
||||
bool isHidden = attributes & FILE_ATTRIBUTE_HIDDEN;
|
||||
#else
|
||||
const bool isHidden = filename.find('.') == 0;
|
||||
#endif // WIN32
|
||||
if (!isHidden) {
|
||||
// Don't add hidden files
|
||||
fileList.push_back(filename);
|
||||
}
|
||||
}
|
||||
std::sort(fileList.begin(), fileList.end());
|
||||
return fileList;
|
||||
}
|
||||
|
||||
scripting::LuaLibrary SessionRecordingHandler::luaLibrary() {
|
||||
return {
|
||||
"sessionRecording",
|
||||
{
|
||||
codegen::lua::StartRecording,
|
||||
codegen::lua::StopRecording,
|
||||
codegen::lua::StartPlayback,
|
||||
codegen::lua::StopPlayback,
|
||||
codegen::lua::SetPlaybackPause,
|
||||
codegen::lua::TogglePlaybackPause,
|
||||
codegen::lua::IsPlayingBack,
|
||||
codegen::lua::IsRecording
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
121
src/interaction/sessionrecordinghandler_lua.inl
Normal file
121
src/interaction/sessionrecordinghandler_lua.inl
Normal file
@@ -0,0 +1,121 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* Starts a recording session. The string argument is the filename used for the file where
|
||||
* the recorded keyframes are saved.
|
||||
*/
|
||||
[[codegen::luawrap]] void startRecording() {
|
||||
openspace::global::sessionRecordingHandler->startRecording();
|
||||
}
|
||||
|
||||
// Stops a recording session. `dataMode` has to be "Ascii" or "Binary"
|
||||
[[codegen::luawrap]] void stopRecording(std::filesystem::path recordFilePath,
|
||||
std::string dataMode)
|
||||
{
|
||||
if (recordFilePath.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string is empty");
|
||||
}
|
||||
|
||||
if (dataMode != "Ascii" && dataMode != "Binary") {
|
||||
throw ghoul::lua::LuaError(std::format("Invalid data mode {}", dataMode));
|
||||
}
|
||||
|
||||
using DataMode = openspace::interaction::DataMode;
|
||||
openspace::global::sessionRecordingHandler->stopRecording(
|
||||
recordFilePath,
|
||||
dataMode == "Ascii" ? DataMode::Ascii : DataMode::Binary
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a playback session with keyframe times that are relative to the time since the
|
||||
* recording was started (the same relative time applies to the playback). When playback
|
||||
* starts, the simulation time is automatically set to what it was at recording time. The
|
||||
* string argument is the filename to pull playback keyframes from (the file path is
|
||||
* relative to the RECORDINGS variable specified in the config file). If a second input
|
||||
* value of true is given, then playback will continually loop until it is manually
|
||||
* stopped.
|
||||
*/
|
||||
[[codegen::luawrap]] void startPlayback(std::string file, bool loop = false,
|
||||
bool shouldWaitForTiles = true,
|
||||
std::optional<int> screenshotFps = std::nullopt)
|
||||
{
|
||||
using namespace openspace;
|
||||
|
||||
if (file.empty()) {
|
||||
throw ghoul::lua::LuaError("Filepath string is empty");
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_regular_file(file)) {
|
||||
throw ghoul::RuntimeError(std::format(
|
||||
"Cannot find the specified playback file '{}'", file
|
||||
));
|
||||
}
|
||||
|
||||
interaction::SessionRecording timeline = interaction::loadSessionRecording(file);
|
||||
global::sessionRecordingHandler->startPlayback(
|
||||
std::move(timeline),
|
||||
loop,
|
||||
shouldWaitForTiles,
|
||||
screenshotFps
|
||||
);
|
||||
}
|
||||
|
||||
// Stops a playback session before playback of all keyframes is complete.
|
||||
[[codegen::luawrap]] void stopPlayback() {
|
||||
openspace::global::sessionRecordingHandler->stopPlayback();
|
||||
}
|
||||
|
||||
// Pauses or resumes the playback progression through keyframes.
|
||||
[[codegen::luawrap]] void setPlaybackPause(bool pause) {
|
||||
openspace::global::sessionRecordingHandler->setPlaybackPause(pause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the pause function, i.e. temporarily setting the delta time to 0 and restoring
|
||||
* it afterwards.
|
||||
*/
|
||||
[[codegen::luawrap]] void togglePlaybackPause() {
|
||||
using namespace openspace;
|
||||
|
||||
bool isPlaybackPaused = global::sessionRecordingHandler->isPlaybackPaused();
|
||||
global::sessionRecordingHandler->setPlaybackPause(!isPlaybackPaused);
|
||||
}
|
||||
|
||||
// Returns true if session recording is currently playing back a recording.
|
||||
[[codegen::luawrap]] bool isPlayingBack() {
|
||||
return openspace::global::sessionRecordingHandler->isPlayingBack();
|
||||
}
|
||||
|
||||
// Returns true if session recording is currently recording a recording.
|
||||
[[codegen::luawrap]] bool isRecording() {
|
||||
return openspace::global::sessionRecordingHandler->isRecording();
|
||||
}
|
||||
|
||||
#include "sessionrecordinghandler_lua_codegen.cpp"
|
||||
|
||||
} // namespace
|
||||
@@ -1,117 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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/interaction/tasks/convertrecfileversiontask.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/documentation/verifier.h>
|
||||
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <ghoul/filesystem/file.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view _loggerCat = "ConvertRecFileVersionTask";
|
||||
|
||||
constexpr std::string_view KeyInFilePath = "InputFilePath";
|
||||
} // namespace
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
ConvertRecFileVersionTask::ConvertRecFileVersionTask(const ghoul::Dictionary& dictionary)
|
||||
{
|
||||
openspace::documentation::testSpecificationAndThrow(
|
||||
documentation(),
|
||||
dictionary,
|
||||
"ConvertRecFileVersionTask"
|
||||
);
|
||||
|
||||
_inFilename = dictionary.value<std::string>(KeyInFilePath);
|
||||
_inFilePath = absPath(_inFilename);
|
||||
|
||||
ghoul_assert(std::filesystem::is_regular_file(_inFilePath), "The file must exist");
|
||||
if (!std::filesystem::is_regular_file(_inFilePath)) {
|
||||
LERROR(std::format("Failed to load session recording file: {}", _inFilePath));
|
||||
}
|
||||
else {
|
||||
sessRec = new SessionRecording(false);
|
||||
}
|
||||
}
|
||||
|
||||
ConvertRecFileVersionTask::~ConvertRecFileVersionTask() {
|
||||
delete sessRec;
|
||||
}
|
||||
|
||||
std::string ConvertRecFileVersionTask::description() {
|
||||
std::string description = std::format(
|
||||
"Convert file format of session recording file '{}' to current version",
|
||||
_inFilePath
|
||||
);
|
||||
return description;
|
||||
}
|
||||
|
||||
void ConvertRecFileVersionTask::perform(const Task::ProgressCallback&) {
|
||||
convert();
|
||||
}
|
||||
|
||||
void ConvertRecFileVersionTask::convert() {
|
||||
const bool hasBinaryFileExtension = SessionRecording::hasFileExtension(
|
||||
_inFilename,
|
||||
SessionRecording::FileExtensionBinary
|
||||
);
|
||||
const bool hasAsciiFileExtension = SessionRecording::hasFileExtension(
|
||||
_inFilename,
|
||||
SessionRecording::FileExtensionAscii
|
||||
);
|
||||
if (!hasBinaryFileExtension && !hasAsciiFileExtension) {
|
||||
LERROR(std::format(
|
||||
"Input filename does not have expected '{}' or '{}' extension",
|
||||
SessionRecording::FileExtensionBinary, SessionRecording::FileExtensionAscii
|
||||
));
|
||||
return;
|
||||
}
|
||||
sessRec->convertFileRelativePath(_inFilename);
|
||||
}
|
||||
|
||||
documentation::Documentation ConvertRecFileVersionTask::documentation() {
|
||||
using namespace documentation;
|
||||
return {
|
||||
"ConvertRecFileVersionTask",
|
||||
"convert_file_version_task",
|
||||
"",
|
||||
{
|
||||
{
|
||||
"InputFilePath",
|
||||
new StringAnnotationVerifier("A valid filename to convert"),
|
||||
Optional::No,
|
||||
Private::No,
|
||||
"The filename to update to the current file format",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace openspace::interaction
|
||||
@@ -39,293 +39,54 @@ namespace {
|
||||
|
||||
constexpr std::string_view KeyInFilePath = "InputFilePath";
|
||||
constexpr std::string_view KeyOutFilePath = "OutputFilePath";
|
||||
|
||||
struct [[codegen::Dictionary(ConvertRecFormatTask)]] Parameters {
|
||||
std::filesystem::path inputFilePath;
|
||||
std::filesystem::path outputFilePath;
|
||||
|
||||
enum class DataMode {
|
||||
Ascii,
|
||||
Binary
|
||||
};
|
||||
DataMode outputMode;
|
||||
};
|
||||
|
||||
#include "convertrecformattask_codegen.cpp"
|
||||
} // namespace
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
documentation::Documentation ConvertRecFormatTask::documentation() {
|
||||
return codegen::doc<Parameters>("convert_format_task");
|
||||
}
|
||||
|
||||
ConvertRecFormatTask::ConvertRecFormatTask(const ghoul::Dictionary& dictionary) {
|
||||
openspace::documentation::testSpecificationAndThrow(
|
||||
documentation(),
|
||||
dictionary,
|
||||
"ConvertRecFormatTask"
|
||||
);
|
||||
const Parameters p = codegen::bake<Parameters>(dictionary);
|
||||
|
||||
_inFilePath = absPath(dictionary.value<std::string>(KeyInFilePath));
|
||||
_outFilePath = absPath(dictionary.value<std::string>(KeyOutFilePath));
|
||||
_inFilePath = p.inputFilePath;
|
||||
_outFilePath = p.outputFilePath;
|
||||
|
||||
switch (p.outputMode) {
|
||||
case Parameters::DataMode::Ascii:
|
||||
_dataMode = DataMode::Ascii;
|
||||
break;
|
||||
case Parameters::DataMode::Binary:
|
||||
_dataMode = DataMode::Binary;
|
||||
break;
|
||||
}
|
||||
|
||||
ghoul_assert(std::filesystem::is_regular_file(_inFilePath), "The file must exist");
|
||||
if (!std::filesystem::is_regular_file(_inFilePath)) {
|
||||
LERROR(std::format("Failed to load session recording file: {}", _inFilePath));
|
||||
}
|
||||
else {
|
||||
_iFile.open(_inFilePath, std::ifstream::in | std::ifstream::binary);
|
||||
determineFormatType();
|
||||
sessRec = new SessionRecording(false);
|
||||
}
|
||||
}
|
||||
|
||||
ConvertRecFormatTask::~ConvertRecFormatTask() {
|
||||
_iFile.close();
|
||||
_oFile.close();
|
||||
delete sessRec;
|
||||
}
|
||||
|
||||
std::string ConvertRecFormatTask::description() {
|
||||
std::string description =
|
||||
std::format("Convert session recording file '{}'", _inFilePath);
|
||||
if (_fileFormatType == SessionRecording::DataMode::Ascii) {
|
||||
description += "(ascii format) ";
|
||||
}
|
||||
else if (_fileFormatType == SessionRecording::DataMode::Binary) {
|
||||
description += "(binary format) ";
|
||||
}
|
||||
else {
|
||||
description += "(UNKNOWN format) ";
|
||||
}
|
||||
description += std::format("conversion to file '{}'", _outFilePath);
|
||||
return description;
|
||||
return "Convert session recording files between ASCII and Binary formats";
|
||||
}
|
||||
|
||||
void ConvertRecFormatTask::perform(const Task::ProgressCallback&) {
|
||||
convert();
|
||||
}
|
||||
|
||||
void ConvertRecFormatTask::convert() {
|
||||
std::string expectedFileExtension_in;
|
||||
std::string expectedFileExtension_out;
|
||||
std::string currentFormat;
|
||||
if (_fileFormatType == SessionRecording::DataMode::Binary) {
|
||||
currentFormat = "binary";
|
||||
expectedFileExtension_in = SessionRecording::FileExtensionBinary;
|
||||
expectedFileExtension_out = SessionRecording::FileExtensionAscii;
|
||||
}
|
||||
else if (_fileFormatType == SessionRecording::DataMode::Ascii) {
|
||||
currentFormat = "ascii";
|
||||
expectedFileExtension_in = SessionRecording::FileExtensionAscii;
|
||||
expectedFileExtension_out = SessionRecording::FileExtensionBinary;
|
||||
}
|
||||
|
||||
if (_inFilePath.extension() != expectedFileExtension_in) {
|
||||
LWARNING(std::format(
|
||||
"Input filename doesn't have expected '{}' format file extension",
|
||||
currentFormat
|
||||
));
|
||||
}
|
||||
if (_outFilePath.extension() == expectedFileExtension_in) {
|
||||
LERROR(std::format(
|
||||
"Output filename has '{}' file extension, but is conversion from '{}'",
|
||||
currentFormat, currentFormat
|
||||
));
|
||||
return;
|
||||
}
|
||||
else if (_outFilePath.extension() != expectedFileExtension_out) {
|
||||
_outFilePath += expectedFileExtension_out;
|
||||
}
|
||||
|
||||
if (_fileFormatType == SessionRecording::DataMode::Ascii) {
|
||||
_iFile.close();
|
||||
_iFile.open(_inFilePath, std::ifstream::in);
|
||||
//Throw out first line
|
||||
std::string throw_out;
|
||||
ghoul::getline(_iFile, throw_out);
|
||||
_oFile.open(_outFilePath);
|
||||
}
|
||||
else if (_fileFormatType == SessionRecording::DataMode::Binary) {
|
||||
_oFile.open(_outFilePath, std::ios::binary);
|
||||
}
|
||||
_oFile.write(
|
||||
SessionRecording::FileHeaderTitle.c_str(),
|
||||
SessionRecording::FileHeaderTitle.length()
|
||||
);
|
||||
_oFile.write(_version.c_str(), SessionRecording::FileHeaderVersionLength);
|
||||
_oFile.close();
|
||||
|
||||
if (_fileFormatType == SessionRecording::DataMode::Ascii) {
|
||||
convertToBinary();
|
||||
}
|
||||
else if (_fileFormatType == SessionRecording::DataMode::Binary) {
|
||||
convertToAscii();
|
||||
}
|
||||
else {
|
||||
// Add error output for file type not recognized
|
||||
LERROR("Session recording file unrecognized format type");
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertRecFormatTask::determineFormatType() {
|
||||
_fileFormatType = SessionRecording::DataMode::Unknown;
|
||||
std::string line;
|
||||
|
||||
line = SessionRecording::readHeaderElement(_iFile,
|
||||
SessionRecording::FileHeaderTitle.length());
|
||||
|
||||
if (line.substr(0, SessionRecording::FileHeaderTitle.length())
|
||||
!= SessionRecording::FileHeaderTitle)
|
||||
{
|
||||
LERROR(std::format(
|
||||
"Session recording file '{}' does not have expected header", _inFilePath
|
||||
));
|
||||
}
|
||||
else {
|
||||
//Read version string and throw it away (and also line feed character at end)
|
||||
_version = SessionRecording::readHeaderElement(_iFile,
|
||||
SessionRecording::FileHeaderVersionLength);
|
||||
line = SessionRecording::readHeaderElement(_iFile, 1);
|
||||
SessionRecording::readHeaderElement(_iFile, 1);
|
||||
|
||||
if (line.at(0) == SessionRecording::DataFormatAsciiTag) {
|
||||
_fileFormatType = SessionRecording::DataMode::Ascii;
|
||||
}
|
||||
else if (line.at(0) == SessionRecording::DataFormatBinaryTag) {
|
||||
_fileFormatType = SessionRecording::DataMode::Binary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertRecFormatTask::convertToAscii() {
|
||||
SessionRecording::Timestamps times;
|
||||
datamessagestructures::CameraKeyframe ckf;
|
||||
datamessagestructures::TimeKeyframe tkf;
|
||||
datamessagestructures::ScriptMessage skf;
|
||||
int lineNum = 1;
|
||||
_oFile.open(_outFilePath, std::ifstream::app);
|
||||
const char tmpType = SessionRecording::DataFormatAsciiTag;
|
||||
_oFile.write(&tmpType, 1);
|
||||
_oFile.write("\n", 1);
|
||||
|
||||
while (true) {
|
||||
const unsigned char frameType = readFromPlayback<unsigned char>(_iFile);
|
||||
// Check if have reached EOF
|
||||
if (!_iFile) {
|
||||
LINFO(std::format(
|
||||
"Finished converting {} entries from file '{}'", lineNum - 1, _inFilePath
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
std::stringstream keyframeLine = std::stringstream();
|
||||
keyframeLine.str(std::string());
|
||||
|
||||
if (frameType == SessionRecording::HeaderCameraBinary) {
|
||||
sessRec->readCameraKeyframeBinary(times, ckf, _iFile, lineNum);
|
||||
SessionRecording::saveHeaderAscii(
|
||||
times,
|
||||
SessionRecording::HeaderCameraAscii,
|
||||
keyframeLine
|
||||
);
|
||||
ckf.write(keyframeLine);
|
||||
}
|
||||
else if (frameType == SessionRecording::HeaderTimeBinary) {
|
||||
sessRec->readTimeKeyframeBinary(times, tkf, _iFile, lineNum);
|
||||
SessionRecording::saveHeaderAscii(
|
||||
times,
|
||||
SessionRecording::HeaderTimeAscii,
|
||||
keyframeLine
|
||||
);
|
||||
tkf.write(keyframeLine);
|
||||
}
|
||||
else if (frameType == SessionRecording::HeaderScriptBinary) {
|
||||
sessRec->readScriptKeyframeBinary(times, skf, _iFile, lineNum);
|
||||
SessionRecording::saveHeaderAscii(
|
||||
times,
|
||||
SessionRecording::HeaderScriptAscii,
|
||||
keyframeLine
|
||||
);
|
||||
skf.write(keyframeLine);
|
||||
}
|
||||
else {
|
||||
LERROR(std::format(
|
||||
"Unknown frame type @ index {} of playback file '{}'",
|
||||
lineNum - 1, _inFilePath
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
SessionRecording::saveKeyframeToFile(keyframeLine.str(), _oFile);
|
||||
lineNum++;
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertRecFormatTask::convertToBinary() {
|
||||
SessionRecording::Timestamps times;
|
||||
datamessagestructures::CameraKeyframe ckf;
|
||||
datamessagestructures::TimeKeyframe tkf;
|
||||
datamessagestructures::ScriptMessage skf;
|
||||
int lineNum = 1;
|
||||
std::string lineContents;
|
||||
std::array<unsigned char, SessionRecording::_saveBufferMaxSize_bytes> keyframeBuffer;
|
||||
_oFile.open(_outFilePath, std::ifstream::app | std::ios::binary);
|
||||
const char tmpType = SessionRecording::DataFormatBinaryTag;
|
||||
_oFile.write(&tmpType, 1);
|
||||
_oFile.write("\n", 1);
|
||||
|
||||
while (ghoul::getline(_iFile, lineContents)) {
|
||||
lineNum++;
|
||||
|
||||
std::istringstream iss(lineContents);
|
||||
std::string entryType;
|
||||
if (!(iss >> entryType)) {
|
||||
LERROR(std::format(
|
||||
"Error reading entry type @ line {} of file '{}'", lineNum, _inFilePath
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
if (entryType == SessionRecording::HeaderCameraAscii) {
|
||||
sessRec->readCameraKeyframeAscii(times, ckf, lineContents, lineNum);
|
||||
sessRec->saveCameraKeyframeBinary(times, ckf, keyframeBuffer.data(),
|
||||
_oFile);
|
||||
}
|
||||
else if (entryType == SessionRecording::HeaderTimeAscii) {
|
||||
sessRec->readTimeKeyframeAscii(times, tkf, lineContents, lineNum);
|
||||
sessRec->saveTimeKeyframeBinary(times, tkf, keyframeBuffer.data(),
|
||||
_oFile);
|
||||
}
|
||||
else if (entryType == SessionRecording::HeaderScriptAscii) {
|
||||
sessRec->readScriptKeyframeAscii(times, skf, lineContents, lineNum);
|
||||
sessRec->saveScriptKeyframeBinary(times, skf, keyframeBuffer.data(),
|
||||
_oFile);
|
||||
}
|
||||
else if (entryType.substr(0, 1) == SessionRecording::HeaderCommentAscii) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
LERROR(std::format(
|
||||
"Unknown frame type {} @ line {} of file '{}'",
|
||||
entryType, lineContents, _inFilePath
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
_oFile.close();
|
||||
LINFO(std::format(
|
||||
"Finished converting {} entries from file '{}'", lineNum, _inFilePath
|
||||
));
|
||||
}
|
||||
|
||||
documentation::Documentation ConvertRecFormatTask::documentation() {
|
||||
using namespace documentation;
|
||||
return {
|
||||
"ConvertRecFormatTask",
|
||||
"convert_format_task",
|
||||
"",
|
||||
{
|
||||
{
|
||||
"InputFilePath",
|
||||
new StringAnnotationVerifier("A valid filename to convert"),
|
||||
Optional::No,
|
||||
Private::No,
|
||||
"The filename to convert to the opposite format",
|
||||
},
|
||||
{
|
||||
"OutputFilePath",
|
||||
new StringAnnotationVerifier("A valid output filename"),
|
||||
Optional::No,
|
||||
Private::No,
|
||||
"The filename containing the converted result",
|
||||
},
|
||||
},
|
||||
};
|
||||
SessionRecording sessionRecording = loadSessionRecording(_inFilePath);
|
||||
saveSessionRecording(_outFilePath, sessionRecording, _dataMode);
|
||||
}
|
||||
|
||||
} // namespace openspace::interaction
|
||||
|
||||
@@ -134,7 +134,7 @@ void KeyframeNavigator::updateCamera(Camera* camera, const CameraPose& prevPose,
|
||||
|
||||
// Linear interpolation
|
||||
t = std::max(0.0, std::min(1.0, t));
|
||||
const glm::dvec3 nowCameraPosition =
|
||||
glm::dvec3 nowCameraPosition =
|
||||
prevKeyframeCameraPosition * (1.0 - t) + nextKeyframeCameraPosition * t;
|
||||
glm::dquat nowCameraRotation = glm::slerp(
|
||||
prevKeyframeCameraRotation,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/events/event.h>
|
||||
#include <openspace/events/eventengine.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/navigation/navigationhandler.h>
|
||||
#include <openspace/query/query.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
@@ -91,8 +91,8 @@ namespace {
|
||||
|
||||
std::chrono::steady_clock::time_point currentTimeForInterpolation() {
|
||||
using namespace openspace::global;
|
||||
if (sessionRecording->isSavingFramesDuringPlayback()) {
|
||||
return sessionRecording->currentPlaybackInterpolationTime();
|
||||
if (sessionRecordingHandler->isSavingFramesDuringPlayback()) {
|
||||
return sessionRecordingHandler->currentPlaybackInterpolationTime();
|
||||
}
|
||||
else {
|
||||
return std::chrono::steady_clock::now();
|
||||
@@ -775,7 +775,7 @@ PropertyValueType Scene::propertyValueType(const std::string& value) {
|
||||
}
|
||||
|
||||
std::vector<properties::Property*> Scene::propertiesMatchingRegex(
|
||||
const std::string& propertyString)
|
||||
std::string_view propertyString)
|
||||
{
|
||||
return findMatchesInAllProperties(propertyString, allProperties(), "");
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ openspace::properties::PropertyOwner* findPropertyOwnerWithMatchingGroupTag(T* p
|
||||
}
|
||||
|
||||
std::vector<openspace::properties::Property*> findMatchesInAllProperties(
|
||||
const std::string& regex,
|
||||
const std::vector<openspace::properties::Property*>& properties,
|
||||
const std::string& groupName)
|
||||
std::string_view regex,
|
||||
const std::vector<openspace::properties::Property*>& properties,
|
||||
const std::string& groupName)
|
||||
{
|
||||
using namespace openspace;
|
||||
|
||||
@@ -241,8 +241,8 @@ void applyRegularExpression(lua_State* L, const std::string& regex,
|
||||
// value from the stack, so we need to push it to the end
|
||||
lua_pushvalue(L, -1);
|
||||
|
||||
if (global::sessionRecording->isRecording()) {
|
||||
global::sessionRecording->savePropertyBaseline(*prop);
|
||||
if (global::sessionRecordingHandler->isRecording()) {
|
||||
global::sessionRecordingHandler->savePropertyBaseline(*prop);
|
||||
}
|
||||
if (interpolationDuration == 0.0) {
|
||||
global::renderEngine->scene()->removePropertyInterpolation(prop);
|
||||
@@ -312,8 +312,8 @@ int setPropertyCallSingle(properties::Property& prop, const std::string& uri,
|
||||
);
|
||||
}
|
||||
else {
|
||||
if (global::sessionRecording->isRecording()) {
|
||||
global::sessionRecording->savePropertyBaseline(prop);
|
||||
if (global::sessionRecordingHandler->isRecording()) {
|
||||
global::sessionRecordingHandler->savePropertyBaseline(prop);
|
||||
}
|
||||
if (duration == 0.0) {
|
||||
global::renderEngine->scene()->removePropertyInterpolation(&prop);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/network/parallelpeer.h>
|
||||
#include <openspace/util/syncbuffer.h>
|
||||
#include <openspace/documentation/documentation.h>
|
||||
@@ -482,8 +483,8 @@ void ScriptEngine::preSync(bool isMaster) {
|
||||
// Not really a received script but the master also needs to run the script...
|
||||
_masterScriptQueue.push(item);
|
||||
|
||||
if (global::sessionRecording->isRecording()) {
|
||||
global::sessionRecording->saveScriptKeyframeToTimeline(item.code);
|
||||
if (global::sessionRecordingHandler->isRecording()) {
|
||||
global::sessionRecordingHandler->saveScriptKeyframeToTimeline(item.code);
|
||||
}
|
||||
|
||||
// Sync out to other nodes (cluster)
|
||||
@@ -562,8 +563,8 @@ void ScriptEngine::postSync(bool isMaster) {
|
||||
}
|
||||
|
||||
double now =
|
||||
global::sessionRecording->isSavingFramesDuringPlayback() ?
|
||||
global::sessionRecording->currentApplicationInterpolationTime() :
|
||||
global::sessionRecordingHandler->isSavingFramesDuringPlayback() ?
|
||||
global::sessionRecordingHandler->currentApplicationInterpolationTime() :
|
||||
global::windowDelegate->applicationTime();
|
||||
for (RepeatedScriptInfo& info : _repeatedScripts) {
|
||||
if (now - info.lastRun >= info.timeout) {
|
||||
|
||||
@@ -244,10 +244,6 @@ std::vector<std::string> ScriptScheduler::progressTo(double newTime) {
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptScheduler::setTimeReferenceMode(interaction::KeyframeTimeRef refType) {
|
||||
_timeframeMode = refType;
|
||||
}
|
||||
|
||||
double ScriptScheduler::currentTime() const {
|
||||
return _currentTime;
|
||||
}
|
||||
@@ -276,27 +272,12 @@ std::vector<ScriptScheduler::ScheduledScript> ScriptScheduler::allScripts(
|
||||
return result;
|
||||
}
|
||||
|
||||
void ScriptScheduler::setModeApplicationTime() {
|
||||
_timeframeMode = interaction::KeyframeTimeRef::Relative_applicationStart;
|
||||
}
|
||||
|
||||
void ScriptScheduler::setModeRecordedTime() {
|
||||
_timeframeMode = interaction::KeyframeTimeRef::Relative_recordedStart;
|
||||
}
|
||||
|
||||
void ScriptScheduler::setModeSimulationTime() {
|
||||
_timeframeMode = interaction::KeyframeTimeRef::Absolute_simTimeJ2000;
|
||||
}
|
||||
|
||||
LuaLibrary ScriptScheduler::luaLibrary() {
|
||||
return {
|
||||
"scriptScheduler",
|
||||
{
|
||||
codegen::lua::LoadFile,
|
||||
codegen::lua::LoadScheduledScript,
|
||||
codegen::lua::SetModeApplicationTime,
|
||||
codegen::lua::SetModeRecordedTime,
|
||||
codegen::lua::SetModeSimulationTime,
|
||||
codegen::lua::Clear,
|
||||
codegen::lua::ScheduledScripts
|
||||
}
|
||||
|
||||
@@ -78,30 +78,6 @@ namespace {
|
||||
global::scriptScheduler->loadScripts(scripts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time reference for scheduled scripts to application time (seconds since
|
||||
* OpenSpace application started).
|
||||
*/
|
||||
[[codegen::luawrap]] void setModeApplicationTime() {
|
||||
openspace::global::scriptScheduler->setModeApplicationTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time reference for scheduled scripts to the time since the recording was
|
||||
* started (the same relative time applies to playback).
|
||||
*/
|
||||
[[codegen::luawrap]] void setModeRecordedTime() {
|
||||
openspace::global::scriptScheduler->setModeRecordedTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time reference for scheduled scripts to the simulated date & time (J2000 epoch
|
||||
* seconds).
|
||||
*/
|
||||
[[codegen::luawrap]] void setModeSimulationTime() {
|
||||
openspace::global::scriptScheduler->setModeSimulationTime();
|
||||
}
|
||||
|
||||
// Clears all scheduled scripts.
|
||||
[[codegen::luawrap]] void clear(std::optional<int> group) {
|
||||
openspace::global::scriptScheduler->clearSchedule(group);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <openspace/engine/openspaceengine.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/scene/profile.h>
|
||||
#include <openspace/scene/scene.h>
|
||||
|
||||
@@ -178,8 +178,8 @@ namespace {
|
||||
|
||||
OpenSpaceEngine::Mode m = global::openSpaceEngine->currentMode();
|
||||
if (m == OpenSpaceEngine::Mode::SessionRecordingPlayback) {
|
||||
bool isPlaybackPaused = global::sessionRecording->isPlaybackPaused();
|
||||
global::sessionRecording->setPlaybackPause(!isPlaybackPaused);
|
||||
bool isPlaybackPaused = global::sessionRecordingHandler->isPlaybackPaused();
|
||||
global::sessionRecordingHandler->setPlaybackPause(!isPlaybackPaused);
|
||||
}
|
||||
else {
|
||||
const bool isPaused = !global::timeManager->isPaused();
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/interaction/actionmanager.h>
|
||||
#include <openspace/interaction/keybindingmanager.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/interaction/sessionrecordinghandler.h>
|
||||
#include <openspace/network/parallelpeer.h>
|
||||
#include <openspace/scripting/scriptscheduler.h>
|
||||
#include <openspace/util/keys.h>
|
||||
@@ -88,8 +88,8 @@ namespace {
|
||||
double currentApplicationTimeForInterpolation() {
|
||||
using namespace openspace;
|
||||
|
||||
if (global::sessionRecording->isSavingFramesDuringPlayback()) {
|
||||
return global::sessionRecording->currentApplicationInterpolationTime();
|
||||
if (global::sessionRecordingHandler->isSavingFramesDuringPlayback()) {
|
||||
return global::sessionRecordingHandler->currentApplicationInterpolationTime();
|
||||
}
|
||||
else {
|
||||
return global::windowDelegate->applicationTime();
|
||||
|
||||
@@ -38,6 +38,7 @@ add_executable(
|
||||
test_profile.cpp
|
||||
test_rawvolumeio.cpp
|
||||
test_scriptscheduler.cpp
|
||||
test_sessionrecording.cpp
|
||||
test_settings.cpp
|
||||
test_sgctedit.cpp
|
||||
test_spicemanager.cpp
|
||||
|
||||
7
tests/sessionrecording/0100_ascii_linux.osrectxt
Normal file
7
tests/sessionrecording/0100_ascii_linux.osrectxt
Normal file
@@ -0,0 +1,7 @@
|
||||
OpenSpace_record/playback01.00A
|
||||
script 212.227 0 762933560.401 1 openspace.setPropertyValueSingle("Modules.CefWebGui.Visible", true)
|
||||
camera 212.237 0.0107105 762933560.412 -13521714.7579869 -18987604.3886074 -1780842.2573154 0.6953145 -0.2337213 -0.1973068 0.6503710 0.00126470590475947 F Earth
|
||||
camera 212.248 0.0210999 762933560.423 -13521714.7579868 -18987604.3885993 -1780842.2573175 0.6953145 -0.2337212 -0.1973068 0.6503710 0.00126470590475947 F Earth
|
||||
script 214.708 2.48093 762933562.877 1 openspace.pathnavigation.flyTo("Mars")
|
||||
camera 214.709 2.48222 762933562.886 -13521714.7579865 -18987604.3886450 -1780842.2572641 0.6953144 -0.2337212 -0.1973068 0.6503708 0.00126470590475947 F Earth
|
||||
camera 214.717 2.49067 762933562.886 -13523362.1802436 -18989917.8348593 -1781059.2290947 0.6953144 -0.2337212 -0.1973068 0.6503708 0.00126449402887374 F Earth
|
||||
7
tests/sessionrecording/0100_ascii_windows.osrectxt
Normal file
7
tests/sessionrecording/0100_ascii_windows.osrectxt
Normal file
@@ -0,0 +1,7 @@
|
||||
OpenSpace_record/playback01.00A
|
||||
script 10.0 0.0 100.0 1 openspace.time.setPause(false)
|
||||
camera 11.0 1.0 101.0 101.123 201.456 301.789 0.06 0.26 0.49 0.82 1.26e-03 F Earth
|
||||
camera 11.5 2.0 102.0 102.123 202.456 302.789 0.12 0.19 0.48 0.84 1.25e-03 F Earth
|
||||
script 12.0 4.0 104.0 1 openspace.setPropertyValueSingle("Scene.Earth.Renderable.Fade", 1.0)
|
||||
camera 12.5 4.0 104.0 104.123 204.456 304.789 0.30 -0.01 0.41 0.85 1.27e-03 F Earth
|
||||
camera 13.0 5.0 105.0 105.123 205.456 305.789 0.31 -0.02 0.41 0.85 1.28e-03 F Earth
|
||||
BIN
tests/sessionrecording/0100_binary_linux.osrec
Normal file
BIN
tests/sessionrecording/0100_binary_linux.osrec
Normal file
Binary file not shown.
BIN
tests/sessionrecording/0100_binary_windows.osrec
Normal file
BIN
tests/sessionrecording/0100_binary_windows.osrec
Normal file
Binary file not shown.
7
tests/sessionrecording/0200_ascii_linux.osrectxt
Normal file
7
tests/sessionrecording/0200_ascii_linux.osrectxt
Normal file
@@ -0,0 +1,7 @@
|
||||
OpenSpace_record/playback02.00A
|
||||
script 0 0 1 openspace.time.setPause(false);openspace.time.setDeltaTime(1);
|
||||
script 0 0 1 openspace.setPropertyValueSingle("Scene.hoverCircle.Renderable.Fade", 0)
|
||||
camera 0.002065126085653901 779267322.4886187 123389190187.6973 -987938150497.865 346357762351.6706 -0.36478543 0.4468942 -0.6903772 -0.4365736 2.0395897e-08 - Venus
|
||||
camera 0.004236564040184021 779267322.4907901 123389190187.6973 -987938150497.865 346357762351.6706 -0.36478543 0.4468942 -0.6903772 -0.4365736 2.0395897e-08 - Venus
|
||||
script 8.958355146809481 779267331.4449104 1 openspace.setPropertyValueSingle('Scene.hoverCircle.Renderable.Fade', 0.0);
|
||||
camera 26.381116207921878 779267348.8676736 62165003943664156672 482784744565750824960 1117492338753629847552 -0.16281216 -0.117395274 -0.6741872 0.7107617 1.7638751e-17 - Venus
|
||||
7
tests/sessionrecording/0200_ascii_windows.osrectxt
Normal file
7
tests/sessionrecording/0200_ascii_windows.osrectxt
Normal file
@@ -0,0 +1,7 @@
|
||||
OpenSpace_record/playback02.00A
|
||||
script 0.0 100.0 1 openspace.time.setPause(false)
|
||||
camera 1.0 101.0 101.123 201.456 301.789 0.06 0.26 0.49 0.82 1.26e-03 F Earth
|
||||
camera 2.0 102.0 102.123 202.456 302.789 0.12 0.19 0.48 0.84 1.25e-03 F Earth
|
||||
script 4.0 104.0 1 openspace.setPropertyValueSingle("Scene.Earth.Renderable.Fade", 1.0)
|
||||
camera 4.0 104.0 104.123 204.456 304.789 0.30 -0.01 0.41 0.85 1.27e-03 F Earth
|
||||
camera 5.0 105.0 105.123 205.456 305.789 0.31 -0.02 0.41 0.85 1.28e-03 F Earth
|
||||
BIN
tests/sessionrecording/0200_binary_linux.osrec
Normal file
BIN
tests/sessionrecording/0200_binary_linux.osrec
Normal file
Binary file not shown.
BIN
tests/sessionrecording/0200_binary_windows.osrec
Normal file
BIN
tests/sessionrecording/0200_binary_windows.osrec
Normal file
Binary file not shown.
854
tests/test_sessionrecording.cpp
Normal file
854
tests/test_sessionrecording.cpp
Normal file
@@ -0,0 +1,854 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2024 *
|
||||
* *
|
||||
* 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 <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace openspace::interaction;
|
||||
|
||||
namespace {
|
||||
std::filesystem::path test(std::string_view file) {
|
||||
return absPath(std::format("${{TESTDIR}}/sessionrecording/{}", file));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 Ascii Windows", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0100_ascii_windows.osrectxt"));
|
||||
REQUIRE(rec.entries.size() == 6);
|
||||
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[0];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 100.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.time.setPause(false)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[1];
|
||||
CHECK(e.timestamp == 1.0);
|
||||
CHECK(e.simulationTime == 101.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(101.123, 201.456, 301.789));
|
||||
CHECK(camera.rotation == glm::quat(0.82f, 0.06f, 0.26f, 0.49f));
|
||||
CHECK(camera.scale == 1.26e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[2];
|
||||
CHECK(e.timestamp == 2.0);
|
||||
CHECK(e.simulationTime == 102.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(102.123, 202.456, 302.789));
|
||||
CHECK(camera.rotation == glm::quat(0.84f, 0.12f, 0.19f, 0.48f));
|
||||
CHECK(camera.scale == 1.25e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[3];
|
||||
CHECK(e.timestamp == 4.0);
|
||||
CHECK(e.simulationTime == 104.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(
|
||||
script ==
|
||||
"openspace.setPropertyValueSingle(\"Scene.Earth.Renderable.Fade\", 1.0)"
|
||||
);
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[4];
|
||||
CHECK(e.timestamp == 4.0);
|
||||
CHECK(e.simulationTime == 104.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(104.123, 204.456, 304.789));
|
||||
CHECK(camera.rotation == glm::quat(0.85f, 0.30f, -0.01f, 0.41f));
|
||||
CHECK(camera.scale == 1.27e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[5];
|
||||
CHECK(e.timestamp == 5.0);
|
||||
CHECK(e.simulationTime == 105.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(105.123, 205.456, 305.789));
|
||||
CHECK(camera.rotation == glm::quat(0.85f, 0.31f, -0.02f, 0.41f));
|
||||
CHECK(camera.scale == 1.28e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 Ascii Windows Roundtrip", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0100_ascii_windows.osrectxt"));
|
||||
saveSessionRecording(absPath("${TEMPORARY}/ascii"), rec, DataMode::Ascii);
|
||||
saveSessionRecording(absPath("${TEMPORARY}/binary"), rec, DataMode::Binary);
|
||||
SessionRecording a = loadSessionRecording(absPath("${TEMPORARY}/ascii"));
|
||||
SessionRecording b = loadSessionRecording(absPath("${TEMPORARY}/binary"));
|
||||
|
||||
CHECK(rec == a);
|
||||
CHECK(rec == b);
|
||||
CHECK(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Ascii Windows", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0200_ascii_windows.osrectxt"));
|
||||
REQUIRE(rec.entries.size() == 6);
|
||||
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[0];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 100.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.time.setPause(false)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[1];
|
||||
CHECK(e.timestamp == 1.0);
|
||||
CHECK(e.simulationTime == 101.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(101.123, 201.456, 301.789));
|
||||
CHECK(camera.rotation == glm::quat(0.82f, 0.06f, 0.26f, 0.49f));
|
||||
CHECK(camera.scale == 1.26e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[2];
|
||||
CHECK(e.timestamp == 2.0);
|
||||
CHECK(e.simulationTime == 102.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(102.123, 202.456, 302.789));
|
||||
CHECK(camera.rotation == glm::quat(0.84f, 0.12f, 0.19f, 0.48f));
|
||||
CHECK(camera.scale == 1.25e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[3];
|
||||
CHECK(e.timestamp == 4.0);
|
||||
CHECK(e.simulationTime == 104.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(
|
||||
script ==
|
||||
"openspace.setPropertyValueSingle(\"Scene.Earth.Renderable.Fade\", 1.0)"
|
||||
);
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[4];
|
||||
CHECK(e.timestamp == 4.0);
|
||||
CHECK(e.simulationTime == 104.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(104.123, 204.456, 304.789));
|
||||
CHECK(camera.rotation == glm::quat(0.85f, 0.30f, -0.01f, 0.41f));
|
||||
CHECK(camera.scale == 1.27e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[5];
|
||||
CHECK(e.timestamp == 5.0);
|
||||
CHECK(e.simulationTime == 105.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(105.123, 205.456, 305.789));
|
||||
CHECK(camera.rotation == glm::quat(0.85f, 0.31f, -0.02f, 0.41f));
|
||||
CHECK(camera.scale == 1.28e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Ascii Windows Roundtrip", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0200_ascii_windows.osrectxt"));
|
||||
saveSessionRecording(absPath("${TEMPORARY}/ascii"), rec, DataMode::Ascii);
|
||||
saveSessionRecording(absPath("${TEMPORARY}/binary"), rec, DataMode::Binary);
|
||||
SessionRecording a = loadSessionRecording(absPath("${TEMPORARY}/ascii"));
|
||||
SessionRecording b = loadSessionRecording(absPath("${TEMPORARY}/binary"));
|
||||
|
||||
CHECK(rec == a);
|
||||
CHECK(rec == b);
|
||||
CHECK(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 <-> 02.00 Ascii Windows", "[sessionrecording]") {
|
||||
SessionRecording v0100 = loadSessionRecording(test("0100_ascii_windows.osrectxt"));
|
||||
SessionRecording v0200 = loadSessionRecording(test("0200_ascii_windows.osrectxt"));
|
||||
CHECK(v0100 == v0200);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 Binary Windows", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0100_binary_windows.osrec"));
|
||||
REQUIRE(rec.entries.size() == 6);
|
||||
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[0];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 100.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.time.setPause(false)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[1];
|
||||
CHECK(e.timestamp == 1.0);
|
||||
CHECK(e.simulationTime == 101.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(101.123, 201.456, 301.789));
|
||||
CHECK(camera.rotation == glm::quat(0.82f, 0.06f, 0.26f, 0.49f));
|
||||
CHECK(camera.scale == 1.26e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[2];
|
||||
CHECK(e.timestamp == 2.0);
|
||||
CHECK(e.simulationTime == 102.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(102.123, 202.456, 302.789));
|
||||
CHECK(camera.rotation == glm::quat(0.84f, 0.12f, 0.19f, 0.48f));
|
||||
CHECK(camera.scale == 1.25e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[3];
|
||||
CHECK(e.timestamp == 4.0);
|
||||
CHECK(e.simulationTime == 104.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(
|
||||
script ==
|
||||
"openspace.setPropertyValueSingle(\"Scene.Earth.Renderable.Fade\", 1.0)"
|
||||
);
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[4];
|
||||
CHECK(e.timestamp == 4.0);
|
||||
CHECK(e.simulationTime == 104.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(104.123, 204.456, 304.789));
|
||||
CHECK(camera.rotation == glm::quat(0.85f, 0.30f, -0.01f, 0.41f));
|
||||
CHECK(camera.scale == 1.27e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[5];
|
||||
CHECK(e.timestamp == 5.0);
|
||||
CHECK(e.simulationTime == 105.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(105.123, 205.456, 305.789));
|
||||
CHECK(camera.rotation == glm::quat(0.85f, 0.31f, -0.02f, 0.41f));
|
||||
CHECK(camera.scale == 1.28e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 Binary Windows Roundtrip", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0100_binary_windows.osrec"));
|
||||
saveSessionRecording(absPath("${TEMPORARY}/ascii"), rec, DataMode::Ascii);
|
||||
saveSessionRecording(absPath("${TEMPORARY}/binary"), rec, DataMode::Binary);
|
||||
SessionRecording a = loadSessionRecording(absPath("${TEMPORARY}/ascii"));
|
||||
SessionRecording b = loadSessionRecording(absPath("${TEMPORARY}/binary"));
|
||||
|
||||
CHECK(rec == a);
|
||||
CHECK(rec == b);
|
||||
CHECK(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Binary Windows", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0200_binary_windows.osrec"));
|
||||
REQUIRE(rec.entries.size() == 6);
|
||||
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[0];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 100.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.time.setPause(false)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[1];
|
||||
CHECK(e.timestamp == 1.0);
|
||||
CHECK(e.simulationTime == 101.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(101.123, 201.456, 301.789));
|
||||
CHECK(camera.rotation == glm::quat(0.82f, 0.06f, 0.26f, 0.49f));
|
||||
CHECK(camera.scale == 1.26e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[2];
|
||||
CHECK(e.timestamp == 2.0);
|
||||
CHECK(e.simulationTime == 102.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(102.123, 202.456, 302.789));
|
||||
CHECK(camera.rotation == glm::quat(0.84f, 0.12f, 0.19f, 0.48f));
|
||||
CHECK(camera.scale == 1.25e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[3];
|
||||
CHECK(e.timestamp == 4.0);
|
||||
CHECK(e.simulationTime == 104.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(
|
||||
script ==
|
||||
"openspace.setPropertyValueSingle(\"Scene.Earth.Renderable.Fade\", 1.0)"
|
||||
);
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[4];
|
||||
CHECK(e.timestamp == 4.0);
|
||||
CHECK(e.simulationTime == 104.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(104.123, 204.456, 304.789));
|
||||
CHECK(camera.rotation == glm::quat(0.85f, 0.30f, -0.01f, 0.41f));
|
||||
CHECK(camera.scale == 1.27e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[5];
|
||||
CHECK(e.timestamp == 5.0);
|
||||
CHECK(e.simulationTime == 105.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(camera.position == glm::dvec3(105.123, 205.456, 305.789));
|
||||
CHECK(camera.rotation == glm::quat(0.85f, 0.31f, -0.02f, 0.41f));
|
||||
CHECK(camera.scale == 1.28e-03f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Binary Windows Roundtrip", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0200_binary_windows.osrec"));
|
||||
saveSessionRecording(absPath("${TEMPORARY}/ascii"), rec, DataMode::Ascii);
|
||||
saveSessionRecording(absPath("${TEMPORARY}/binary"), rec, DataMode::Binary);
|
||||
SessionRecording a = loadSessionRecording(absPath("${TEMPORARY}/ascii"));
|
||||
SessionRecording b = loadSessionRecording(absPath("${TEMPORARY}/binary"));
|
||||
|
||||
CHECK(rec == a);
|
||||
CHECK(rec == b);
|
||||
CHECK(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 <-> 02.00 Binary Windows", "[sessionrecording]") {
|
||||
SessionRecording v0100 = loadSessionRecording(test("0100_binary_windows.osrec"));
|
||||
SessionRecording v0200 = loadSessionRecording(test("0200_binary_windows.osrec"));
|
||||
CHECK(v0100 == v0200);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Ascii <-> Binary Windows", "[sessionrecording]") {
|
||||
SessionRecording ascii = loadSessionRecording(test("0200_ascii_windows.osrectxt"));
|
||||
SessionRecording binary = loadSessionRecording(test("0200_binary_windows.osrec"));
|
||||
CHECK(ascii == binary);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 Ascii Linux", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0100_ascii_linux.osrectxt"));
|
||||
REQUIRE(rec.entries.size() == 6);
|
||||
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[0];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 762933560.401);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.setPropertyValueSingle(\"Modules.CefWebGui.Visible\", true)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[1];
|
||||
CHECK(e.timestamp == 0.0107105);
|
||||
CHECK(e.simulationTime == 762933560.412);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-13521714.7579869, -18987604.3886074, -1780842.2573154)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(0.6503710f, 0.6953145f, -0.2337213f, -0.1973068f)
|
||||
);
|
||||
CHECK(camera.scale == 0.00126470590475947f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[2];
|
||||
CHECK(e.timestamp == 0.0210999);
|
||||
CHECK(e.simulationTime == 762933560.423);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-13521714.7579868, -18987604.3885993, -1780842.2573175)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(0.6503710f, 0.6953145f, -0.2337212f, -0.1973068f)
|
||||
);
|
||||
CHECK(camera.scale == 0.00126470590475947f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[3];
|
||||
CHECK(e.timestamp == 2.48093);
|
||||
CHECK(e.simulationTime == 762933562.877);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.pathnavigation.flyTo(\"Mars\")");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[4];
|
||||
CHECK(e.timestamp == 2.48222);
|
||||
CHECK(e.simulationTime == 762933562.886);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-13521714.7579865, -18987604.3886450, -1780842.2572641)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(0.6503708f, 0.6953144f, -0.2337212f, -0.1973068f)
|
||||
);
|
||||
CHECK(camera.scale == 0.00126470590475947f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[5];
|
||||
CHECK(e.timestamp == 2.49067);
|
||||
CHECK(e.simulationTime == 762933562.886);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-13523362.1802436, -18989917.8348593, -1781059.2290947)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(0.6503708f, 0.6953144f, -0.2337212f, -0.1973068f)
|
||||
);
|
||||
CHECK(camera.scale == 0.00126449402887374f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 Ascii Linux Roundtrip", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0100_ascii_linux.osrectxt"));
|
||||
saveSessionRecording(absPath("${TEMPORARY}/ascii"), rec, DataMode::Ascii);
|
||||
saveSessionRecording(absPath("${TEMPORARY}/binary"), rec, DataMode::Binary);
|
||||
SessionRecording a = loadSessionRecording(absPath("${TEMPORARY}/ascii"));
|
||||
SessionRecording b = loadSessionRecording(absPath("${TEMPORARY}/binary"));
|
||||
|
||||
CHECK(rec == a);
|
||||
CHECK(rec == b);
|
||||
CHECK(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 Binary Linux", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0100_binary_linux.osrec"));
|
||||
REQUIRE(rec.entries.size() == 6);
|
||||
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[0];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 763463598.23217);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.time.setPause(false)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[1];
|
||||
CHECK(e.timestamp == 0.01045432000000801);
|
||||
CHECK(e.simulationTime == 763463598.2421138);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-15942288.9063634, 8217794.03633486, -14994951.65751712)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(
|
||||
-0.05070944130420685f,
|
||||
0.8491553664207458f,
|
||||
-0.31565767526626587f,
|
||||
-0.42038553953170776f
|
||||
)
|
||||
);
|
||||
CHECK(camera.scale == 0.0012647059047594666f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[2];
|
||||
CHECK(e.timestamp == 0.21673793700000488);
|
||||
CHECK(e.simulationTime == 763463598.44845);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-15942288.906364493, 8217794.036353504, -14994951.657487243)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(
|
||||
-0.05070945620536804f,
|
||||
0.8491553664207458f,
|
||||
-0.31565767526626587f,
|
||||
-0.420385479927063f
|
||||
)
|
||||
);
|
||||
CHECK(camera.scale == 0.0012647059047594666f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[3];
|
||||
CHECK(e.timestamp == 9.940045733000005);
|
||||
CHECK(e.simulationTime == 763463608.1701536);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.setPropertyValueSingle(\"Scene.Earth.Renderable.Fade\",0,1)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[4];
|
||||
CHECK(e.timestamp == 9.94726788300001);
|
||||
CHECK(e.simulationTime == 763463608.1800178);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-15741144.947559996, 14747679.124696942, -9014411.005969524)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(
|
||||
0.23194797337055206f,
|
||||
-0.7898141741752625f,
|
||||
0.2626582682132721f,
|
||||
0.5033928751945496f
|
||||
)
|
||||
);
|
||||
CHECK(camera.scale == 0.001264723134227097f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[5];
|
||||
CHECK(e.timestamp == 11.485186747);
|
||||
CHECK(e.simulationTime == 763463609.7179065);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-15741006.774582125, 14747877.286725827, -9014328.087388767)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(
|
||||
0.2319525182247162f,
|
||||
-0.7898122668266296f,
|
||||
0.26266056299209595f,
|
||||
0.5033925771713257f
|
||||
)
|
||||
);
|
||||
CHECK(camera.scale == 0.001264723134227097f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Earth");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 01.00 Binary Linux Roundtrip", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0100_binary_linux.osrec"));
|
||||
saveSessionRecording(absPath("${TEMPORARY}/ascii"), rec, DataMode::Ascii);
|
||||
saveSessionRecording(absPath("${TEMPORARY}/binary"), rec, DataMode::Binary);
|
||||
SessionRecording a = loadSessionRecording(absPath("${TEMPORARY}/ascii"));
|
||||
SessionRecording b = loadSessionRecording(absPath("${TEMPORARY}/binary"));
|
||||
|
||||
CHECK(rec == a);
|
||||
CHECK(rec == b);
|
||||
CHECK(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Ascii Linux", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0200_ascii_linux.osrectxt"));
|
||||
REQUIRE(rec.entries.size() == 6);
|
||||
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[0];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 0.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.time.setPause(false);openspace.time.setDeltaTime(1);");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[1];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 0.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.setPropertyValueSingle(\"Scene.hoverCircle.Renderable.Fade\", 0)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[2];
|
||||
CHECK(e.timestamp == 0.002065126085653901);
|
||||
CHECK(e.simulationTime == 779267322.4886187);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(123389190187.6973, -987938150497.865, 346357762351.6706)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(-0.4365736f, -0.36478543f, 0.4468942f, -0.6903772f)
|
||||
);
|
||||
CHECK(camera.scale == 2.0395897e-08f);
|
||||
CHECK(!camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Venus");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[3];
|
||||
CHECK(e.timestamp == 0.004236564040184021);
|
||||
CHECK(e.simulationTime == 779267322.4907901);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(123389190187.6973, -987938150497.865, 346357762351.6706)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(-0.4365736f, -0.36478543f, 0.4468942f, -0.6903772f)
|
||||
);
|
||||
CHECK(camera.scale == 2.0395897e-08f);
|
||||
CHECK(!camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Venus");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[4];
|
||||
CHECK(e.timestamp == 8.958355146809481);
|
||||
CHECK(e.simulationTime == 779267331.4449104);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.setPropertyValueSingle('Scene.hoverCircle.Renderable.Fade', 0.0);");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[5];
|
||||
CHECK(e.timestamp == 26.381116207921878);
|
||||
CHECK(e.simulationTime == 779267348.8676736);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(
|
||||
62165003943664156672.0,
|
||||
482784744565750824960.0,
|
||||
1117492338753629847552.0
|
||||
)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(0.7107617f, -0.16281216f, -0.117395274f, -0.6741872f)
|
||||
);
|
||||
CHECK(camera.scale == 1.7638751e-17f);
|
||||
CHECK(!camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Venus");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Ascii Linux Roundtrip", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0200_ascii_linux.osrectxt"));
|
||||
saveSessionRecording(absPath("${TEMPORARY}/ascii"), rec, DataMode::Ascii);
|
||||
saveSessionRecording(absPath("${TEMPORARY}/binary"), rec, DataMode::Binary);
|
||||
SessionRecording a = loadSessionRecording(absPath("${TEMPORARY}/ascii"));
|
||||
SessionRecording b = loadSessionRecording(absPath("${TEMPORARY}/binary"));
|
||||
|
||||
CHECK(rec == a);
|
||||
CHECK(rec == b);
|
||||
CHECK(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Binary Linux", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0200_binary_linux.osrec"));
|
||||
REQUIRE(rec.entries.size() == 6);
|
||||
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[0];
|
||||
CHECK(e.timestamp == 0.0);
|
||||
CHECK(e.simulationTime == 0.0);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.time.setPause(false);openspace.time.setDeltaTime(1);");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[1];
|
||||
CHECK(e.timestamp == 0.0029818089678883553);
|
||||
CHECK(e.simulationTime == 779267268.772417);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(2560146.332327013, -5779694.5689156465, -852442.7158538934)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(
|
||||
0.6254600882530212f,
|
||||
0.5630295276641846f,
|
||||
0.502471387386322f,
|
||||
-0.19829261302947998f
|
||||
)
|
||||
);
|
||||
CHECK(camera.scale == 0.06643116474151611f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Venus");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[2];
|
||||
CHECK(e.timestamp == 0.005884706974029541);
|
||||
CHECK(e.simulationTime == 779267268.7753198);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(2560146.3323334977, -5779694.568918271, -852442.7158528116)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(
|
||||
0.6254600882530212f,
|
||||
0.5630295276641846f,
|
||||
0.502471387386322f,
|
||||
-0.19829261302947998f
|
||||
)
|
||||
);
|
||||
CHECK(camera.scale == 0.06643116474151611f);
|
||||
CHECK(camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Venus");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[3];
|
||||
CHECK(e.timestamp == 10.153814885416068);
|
||||
CHECK(e.simulationTime == 779267278.9232514);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
|
||||
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
|
||||
CHECK(script == "openspace.setPropertyValueSingle(\"Scene.Venus.Renderable.Layers.ColorLayers.Clouds_Magellan_Combo_Utah.Fade\",0)");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[4];
|
||||
CHECK(e.timestamp == 10.155818674364127);
|
||||
CHECK(e.simulationTime == 779267278.9252552);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-57303121.02243042, 8346999.591819763, -128851858.36139679)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(
|
||||
0.1360674947500229f,
|
||||
0.658237636089325f,
|
||||
-0.7229072451591492f,
|
||||
-0.16004367172718048f
|
||||
)
|
||||
);
|
||||
CHECK(camera.scale == 0.0001590096508152783f);
|
||||
CHECK(!camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Venus");
|
||||
}
|
||||
{
|
||||
const SessionRecording::Entry& e = rec.entries[5];
|
||||
CHECK(e.timestamp == 27.533842067583464);
|
||||
CHECK(e.simulationTime == 779267296.303281);
|
||||
REQUIRE(std::holds_alternative<SessionRecording::Entry::Camera>(e.value));
|
||||
const auto& camera = std::get<SessionRecording::Entry::Camera>(e.value);
|
||||
CHECK(
|
||||
camera.position ==
|
||||
glm::dvec3(-4573226225.966583, 667900806.931778, -10309469556.229485)
|
||||
);
|
||||
CHECK(
|
||||
camera.rotation ==
|
||||
glm::quat(
|
||||
0.13572217524051666f,
|
||||
0.6582902073860168f,
|
||||
-0.7229912281036377f,
|
||||
-0.15974101424217224f
|
||||
)
|
||||
);
|
||||
CHECK(camera.scale == 0.0000019040056713492959f);
|
||||
CHECK(!camera.followFocusNodeRotation);
|
||||
CHECK(camera.focusNode == "Venus");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SessionRecording: 02.00 Binary Linux Roundtrip", "[sessionrecording]") {
|
||||
SessionRecording rec = loadSessionRecording(test("0200_binary_linux.osrec"));
|
||||
saveSessionRecording(absPath("${TEMPORARY}/ascii"), rec, DataMode::Ascii);
|
||||
saveSessionRecording(absPath("${TEMPORARY}/binary"), rec, DataMode::Binary);
|
||||
SessionRecording a = loadSessionRecording(absPath("${TEMPORARY}/ascii"));
|
||||
SessionRecording b = loadSessionRecording(absPath("${TEMPORARY}/binary"));
|
||||
|
||||
CHECK(rec == a);
|
||||
CHECK(rec == b);
|
||||
CHECK(a == b);
|
||||
}
|
||||
Reference in New Issue
Block a user