SessionRecording and KeyframeRecording redesign (#3399)

This commit is contained in:
Alexander Bock
2024-10-21 15:06:40 +02:00
committed by GitHub
parent 304cbb2032
commit 438d6e3ddd
49 changed files with 3176 additions and 4971 deletions

View File

@@ -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;

View File

@@ -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__

View File

@@ -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__

View File

@@ -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

View 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__

View File

@@ -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__

View File

@@ -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

View File

@@ -57,6 +57,7 @@ public:
CameraPose() = default;
CameraPose(datamessagestructures::CameraKeyframe&& kf);
auto operator<=>(const CameraPose&) const = default;
};
/**

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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;
};

View File

@@ -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));

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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());

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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(), "");
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -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();

View File

@@ -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();

View File

@@ -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

View 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

View 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

Binary file not shown.

Binary file not shown.

View 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

View 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

Binary file not shown.

Binary file not shown.

View 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);
}