mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-05-03 17:30:04 -05:00
Latest changes to session recording with support for new webGUI record/playback controls (#840)
* Added separate directory for session recording files * Changed recording & playback paths to use RECORDINGS dir and prevent absolute or relative paths in filename. * Updated .gitignore so that recordings directory is ignored. * Added session recording topic for synchronization of rec/play state with web gui. * Added support to sessionRecording for providing a list of available playback files to web GUI. * Fixed problem with playback filenames in list * Fixed problem with occasional large jump in camera pos/rotation after playback finishes. * Fixed the remaining post-playback problem that was causing a jump in position and rotation. * Fix path issue on mac * Fixed bug with bad scale values in recordings saved in ascii format.
This commit is contained in:
committed by
Emil Axelsson
parent
c4781b01de
commit
32ebea9e06
@@ -29,6 +29,7 @@ Thumbs.db
|
||||
/documentation/
|
||||
/logs/
|
||||
/screenshots/
|
||||
/recordings/
|
||||
/sync/
|
||||
# Customization is not supposed to be committed
|
||||
customization.lua
|
||||
|
||||
@@ -318,6 +318,12 @@ void mainInitFunc() {
|
||||
);
|
||||
}
|
||||
|
||||
std::string sessionRecordingPath = "${RECORDINGS}";
|
||||
FileSys.registerPathToken(
|
||||
"${RECORDINGS}",
|
||||
absPath(sessionRecordingPath),
|
||||
ghoul::filesystem::FileSystem::Override::Yes
|
||||
);
|
||||
|
||||
for (size_t i = 0; i < nWindows; ++i) {
|
||||
sgct::SGCTWindow* w = SgctEngine->getWindowPtr(i);
|
||||
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
glm::dvec2 localRollVelocity() const;
|
||||
glm::dvec2 globalRollVelocity() const;
|
||||
|
||||
void resetVelocities();
|
||||
|
||||
protected:
|
||||
struct InteractionState {
|
||||
InteractionState(double scaleFactor);
|
||||
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
|
||||
void updateStatesFromInput(const InputState& inputState, double deltaTime);
|
||||
void updateCameraStateFromStates(double deltaTime);
|
||||
void resetVelocities();
|
||||
|
||||
Camera* camera() const;
|
||||
void setCamera(Camera* camera);
|
||||
@@ -66,6 +67,7 @@ public:
|
||||
void startRetargetAim();
|
||||
float retargetInterpolationTime() const;
|
||||
void setRetargetInterpolationTime(float durationInSeconds);
|
||||
void resetNodeMovements();
|
||||
|
||||
JoystickCameraStates& joystickStates();
|
||||
|
||||
@@ -148,7 +150,6 @@ private:
|
||||
glm::dquat _previousAnchorNodeRotation;
|
||||
|
||||
glm::dvec3 _previousAimNodePosition;
|
||||
glm::dquat _previousAimNodeRotation;
|
||||
|
||||
double _currentCameraToSurfaceDistance = 0.0;
|
||||
bool _directlySetStereoDistance = false;
|
||||
|
||||
@@ -47,6 +47,15 @@ public:
|
||||
Binary
|
||||
};
|
||||
|
||||
enum class SessionState {
|
||||
Idle = 0,
|
||||
Recording = 1,
|
||||
Playback = 2
|
||||
};
|
||||
|
||||
using CallbackHandle = int;
|
||||
using StateChangeCallback = std::function<void()>;
|
||||
|
||||
SessionRecording();
|
||||
~SessionRecording();
|
||||
/**
|
||||
@@ -115,6 +124,12 @@ public:
|
||||
*/
|
||||
bool isPlayingBack() const;
|
||||
|
||||
/**
|
||||
* Used to obtain the state of idle/recording/playback.
|
||||
* \returns 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
|
||||
@@ -141,12 +156,26 @@ public:
|
||||
*/
|
||||
static openspace::scripting::LuaLibrary luaLibrary();
|
||||
|
||||
/**
|
||||
* Used to request a callback for notification of playback state change.
|
||||
* \param cb function handle for callback.
|
||||
* \returns 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.
|
||||
* \returns string of newline-delimited filenames in recordings dir.
|
||||
*/
|
||||
std::string playbackList();
|
||||
|
||||
private:
|
||||
enum class SessionState {
|
||||
Idle = 0,
|
||||
Recording,
|
||||
Playback
|
||||
};
|
||||
enum class RecordedType {
|
||||
Camera = 0,
|
||||
Time,
|
||||
@@ -206,6 +235,7 @@ private:
|
||||
bool isDataModeBinary();
|
||||
unsigned int findIndexOfLastCameraKeyframeInTimeline();
|
||||
bool doesTimelineEntryContainCamera(unsigned int index) const;
|
||||
std::vector<std::pair<CallbackHandle, StateChangeCallback>> _stateChangeCallbacks;
|
||||
|
||||
RecordedType getNextKeyframeType();
|
||||
RecordedType getPrevKeyframeType();
|
||||
@@ -222,6 +252,7 @@ private:
|
||||
|
||||
RecordedDataMode _recordingDataMode = RecordedDataMode::Binary;
|
||||
SessionState _state = SessionState::Idle;
|
||||
SessionState _lastState = SessionState::Idle;
|
||||
std::string _playbackFilename;
|
||||
std::ifstream _playbackFile;
|
||||
std::string _playbackLineParsing;
|
||||
@@ -260,6 +291,8 @@ private:
|
||||
|
||||
unsigned int _idxTimeline_cameraFirstInTimeline = 0;
|
||||
double _cameraFirstInTimeline_timestamp = 0;
|
||||
|
||||
int _nextCallbackHandle = 0;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -36,6 +36,7 @@ set(HEADER_FILES
|
||||
include/topics/documentationtopic.h
|
||||
include/topics/getpropertytopic.h
|
||||
include/topics/luascripttopic.h
|
||||
include/topics/sessionrecordingtopic.h
|
||||
include/topics/setpropertytopic.h
|
||||
include/topics/shortcuttopic.h
|
||||
include/topics/subscriptiontopic.h
|
||||
@@ -57,6 +58,7 @@ set(SOURCE_FILES
|
||||
src/topics/documentationtopic.cpp
|
||||
src/topics/getpropertytopic.cpp
|
||||
src/topics/luascripttopic.cpp
|
||||
src/topics/sessionrecordingtopic.cpp
|
||||
src/topics/setpropertytopic.cpp
|
||||
src/topics/shortcuttopic.cpp
|
||||
src/topics/subscriptiontopic.cpp
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2019 *
|
||||
* *
|
||||
* 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_MODULE_SERVER___SESSION_RECORDING_TOPIC___H__
|
||||
#define __OPENSPACE_MODULE_SERVER___SESSION_RECORDING_TOPIC___H__
|
||||
|
||||
#include <modules/server/include/topics/topic.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
|
||||
namespace openspace {
|
||||
|
||||
class SessionRecordingTopic : public Topic {
|
||||
public:
|
||||
SessionRecordingTopic();
|
||||
virtual ~SessionRecordingTopic();
|
||||
|
||||
void handleJson(const nlohmann::json& json) override;
|
||||
bool isDone() const override;
|
||||
|
||||
private:
|
||||
const int UnsetOnChangeHandle = -1;
|
||||
|
||||
//Provides the idle/recording/playback state int value in json message
|
||||
nlohmann::json state();
|
||||
|
||||
int _stateCallbackHandle = UnsetOnChangeHandle;
|
||||
bool _isDone = false;
|
||||
interaction::SessionRecording::SessionState _lastState;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SERVER___SESSION_RECORDING_TOPIC___H__
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <modules/server/include/topics/documentationtopic.h>
|
||||
#include <modules/server/include/topics/getpropertytopic.h>
|
||||
#include <modules/server/include/topics/luascripttopic.h>
|
||||
#include <modules/server/include/topics/sessionrecordingtopic.h>
|
||||
#include <modules/server/include/topics/setpropertytopic.h>
|
||||
#include <modules/server/include/topics/shortcuttopic.h>
|
||||
#include <modules/server/include/topics/subscriptiontopic.h>
|
||||
@@ -56,6 +57,7 @@ namespace {
|
||||
constexpr const char* DocumentationTopicKey = "documentation";
|
||||
constexpr const char* GetPropertyTopicKey = "get";
|
||||
constexpr const char* LuaScriptTopicKey = "luascript";
|
||||
constexpr const char* SessionRecordingTopicKey = "sessionRecording";
|
||||
constexpr const char* SetPropertyTopicKey = "set";
|
||||
constexpr const char* ShortcutTopicKey = "shortcuts";
|
||||
constexpr const char* SubscriptionTopicKey = "subscribe";
|
||||
@@ -86,6 +88,7 @@ Connection::Connection(std::unique_ptr<ghoul::io::Socket> s,
|
||||
_topicFactory.registerClass<DocumentationTopic>(DocumentationTopicKey);
|
||||
_topicFactory.registerClass<GetPropertyTopic>(GetPropertyTopicKey);
|
||||
_topicFactory.registerClass<LuaScriptTopic>(LuaScriptTopicKey);
|
||||
_topicFactory.registerClass<SessionRecordingTopic>(SessionRecordingTopicKey);
|
||||
_topicFactory.registerClass<SetPropertyTopic>(SetPropertyTopicKey);
|
||||
_topicFactory.registerClass<ShortcutTopic>(ShortcutTopicKey);
|
||||
_topicFactory.registerClass<SubscriptionTopic>(SubscriptionTopicKey);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <openspace/engine/virtualpropertymanager.h>
|
||||
#include <openspace/engine/windowdelegate.h>
|
||||
#include <openspace/interaction/navigationhandler.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <openspace/network/parallelpeer.h>
|
||||
#include <openspace/query/query.h>
|
||||
#include <openspace/rendering/luaconsole.h>
|
||||
@@ -48,6 +49,7 @@ const char* AllNodesValue = "__allNodes";
|
||||
const char* AllScreenSpaceRenderablesValue = "__screenSpaceRenderables";
|
||||
const char* PropertyKey = "property";
|
||||
const char* RootPropertyOwner = "__rootOwner";
|
||||
const char* SessionRecordingPlaybackList = "playbackList";
|
||||
}
|
||||
|
||||
namespace openspace {
|
||||
@@ -70,6 +72,11 @@ void GetPropertyTopic::handleJson(const nlohmann::json& json) {
|
||||
else if (requestedKey == RootPropertyOwner) {
|
||||
response = wrappedPayload(global::rootPropertyOwner);
|
||||
}
|
||||
else if (requestedKey == SessionRecordingPlaybackList) {
|
||||
std::string fileList = global::sessionRecording.playbackList();
|
||||
nlohmann::json getJson = { { SessionRecordingPlaybackList, fileList } };
|
||||
response = wrappedPayload(getJson);
|
||||
}
|
||||
else {
|
||||
response = propertyFromKey(requestedKey);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2019 *
|
||||
* *
|
||||
* 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 "modules/server/include/topics/sessionrecordingtopic.h"
|
||||
|
||||
#include <modules/server/include/connection.h>
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/query/query.h>
|
||||
#include <openspace/interaction/sessionrecording.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
|
||||
namespace {
|
||||
constexpr const char* _loggerCat = "SessionRecordingTopic";
|
||||
constexpr const char* PropertyKey = "property";
|
||||
constexpr const char* EventKey = "event";
|
||||
constexpr const char* UnsubscribeEvent = "stop_subscription";
|
||||
constexpr const char* StateKey = "recState";
|
||||
} // namespace
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace openspace {
|
||||
|
||||
SessionRecordingTopic::SessionRecordingTopic()
|
||||
: _lastState(interaction::SessionRecording::SessionState::Idle)
|
||||
{
|
||||
LDEBUG("Starting new SessionRecording state subscription");
|
||||
}
|
||||
|
||||
SessionRecordingTopic::~SessionRecordingTopic() {
|
||||
if (_stateCallbackHandle != UnsetOnChangeHandle) {
|
||||
global::sessionRecording.removeStateChangeCallback(_stateCallbackHandle);
|
||||
}
|
||||
}
|
||||
|
||||
bool SessionRecordingTopic::isDone() const {
|
||||
return _isDone;
|
||||
}
|
||||
|
||||
void SessionRecordingTopic::handleJson(const nlohmann::json& json) {
|
||||
std::string event = json.at(EventKey).get<std::string>();
|
||||
if (event == UnsubscribeEvent) {
|
||||
_isDone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string requestedKey = json.at(PropertyKey).get<std::string>();
|
||||
LDEBUG("Subscribing to " + requestedKey);
|
||||
|
||||
if (requestedKey == StateKey) {
|
||||
_stateCallbackHandle = global::sessionRecording.addStateChangeCallback(
|
||||
[this]() {
|
||||
openspace::interaction::SessionRecording::SessionState nowState =
|
||||
global::sessionRecording.state();
|
||||
if (nowState != _lastState) {
|
||||
_connection->sendJson(state());
|
||||
_lastState = nowState;
|
||||
}
|
||||
}
|
||||
);
|
||||
_connection->sendJson(state());
|
||||
}
|
||||
else {
|
||||
LWARNING("Cannot get " + requestedKey);
|
||||
_isDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
json SessionRecordingTopic::state() {
|
||||
json statJson = { { "state", static_cast<int>(global::sessionRecording.state()) } };
|
||||
return wrappedPayload(statJson);
|
||||
}
|
||||
|
||||
} // namespace openspace
|
||||
@@ -62,6 +62,7 @@ Paths = {
|
||||
SYNC = "${BASE}/sync",
|
||||
SCREENSHOTS = "${BASE}/screenshots",
|
||||
WEB = "${DATA}/web",
|
||||
RECORDINGS = "${BASE}/recordings",
|
||||
|
||||
CACHE = "${BASE}/cache",
|
||||
CONFIG = "${BASE}/config",
|
||||
|
||||
@@ -78,6 +78,14 @@ void CameraInteractionStates::setVelocityScaleFactor(double scaleFactor) {
|
||||
_globalRollState.setVelocityScaleFactor(scaleFactor);
|
||||
}
|
||||
|
||||
void CameraInteractionStates::resetVelocities() {
|
||||
_globalRotationState.velocity.setHard({ 0.0, 0.0 });
|
||||
_localRotationState.velocity.setHard({ 0.0, 0.0 });
|
||||
_truckMovementState.velocity.setHard({ 0.0, 0.0 });
|
||||
_localRollState.velocity.setHard({ 0.0, 0.0 });
|
||||
_globalRollState.velocity.setHard({ 0.0, 0.0 });
|
||||
}
|
||||
|
||||
glm::dvec2 CameraInteractionStates::globalRotationVelocity() const{
|
||||
return _globalRotationState.velocity.get();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#ifdef INTERPOLATION_DEBUG_PRINT
|
||||
namespace {
|
||||
constexpr const char* _loggerCat = "KeyframeNavigator";
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
namespace openspace::interaction {
|
||||
|
||||
bool KeyframeNavigator::updateCamera(Camera& camera, bool ignoreFutureKeyframes) {
|
||||
@@ -134,13 +140,15 @@ bool KeyframeNavigator::updateCamera(Camera* camera, const CameraPose prevPose,
|
||||
|
||||
// Linear interpolation
|
||||
t = std::max(0.0, std::min(1.0, t));
|
||||
camera->setPositionVec3(
|
||||
prevKeyframeCameraPosition * (1 - t) + nextKeyframeCameraPosition * t
|
||||
);
|
||||
camera->setRotation(
|
||||
glm::slerp(prevKeyframeCameraRotation, nextKeyframeCameraRotation, t)
|
||||
glm::dvec3 nowCameraPosition = prevKeyframeCameraPosition * (1 - t) +
|
||||
nextKeyframeCameraPosition * t;
|
||||
glm::dquat nowCameraRotation = glm::slerp(prevKeyframeCameraRotation,
|
||||
nextKeyframeCameraRotation, t
|
||||
);
|
||||
|
||||
camera->setPositionVec3(nowCameraPosition);
|
||||
camera->setRotation(nowCameraRotation);
|
||||
|
||||
// We want to affect view scaling, such that we achieve
|
||||
// logarithmic interpolation of distance to an imagined focus node.
|
||||
// To do this, we interpolate the scale reciprocal logarithmically.
|
||||
@@ -155,42 +163,15 @@ bool KeyframeNavigator::updateCamera(Camera* camera, const CameraPose prevPose,
|
||||
|
||||
#ifdef INTERPOLATION_DEBUG_PRINT
|
||||
LINFO(fmt::format(
|
||||
"Cam pos prev={}, next={}",
|
||||
std::to_string(prevKeyframeCameraPosition),
|
||||
std::to_string(nextKeyframeCameraPosition)
|
||||
"Cam pos = {} {} {} rot = {} {} {} {}",
|
||||
nowCameraPosition.x,
|
||||
nowCameraPosition.y,
|
||||
nowCameraPosition.z,
|
||||
nowCameraRotation.x,
|
||||
nowCameraRotation.y,
|
||||
nowCameraRotation.z,
|
||||
nowCameraRotation.w
|
||||
));
|
||||
LINFO(fmt::format(
|
||||
"Cam rot prev={} {} {} {} next={} {} {} {}",
|
||||
prevKeyframeCameraRotation.x,
|
||||
prevKeyframeCameraRotation.y,
|
||||
prevKeyframeCameraRotation.z,
|
||||
prevKeyframeCameraRotation.w,
|
||||
nextKeyframeCameraRotation.x,
|
||||
nextKeyframeCameraRotation.y,
|
||||
nextKeyframeCameraRotation.z,
|
||||
nextKeyframeCameraRotation.w
|
||||
));
|
||||
LINFO(fmt::format("Cam interp = {}", t));
|
||||
LINFO(fmt::format(
|
||||
"camera {} {} {} {} {} {}",
|
||||
global::windowDelegate.applicationTime(),
|
||||
global::windowDelegate.applicationTime() - _timestampPlaybackStarted_application,
|
||||
global::timeManager.time().j2000Seconds(),
|
||||
interpolatedCamera.x,
|
||||
interpolatedCamera.y,
|
||||
interpolatedCamera.z
|
||||
));
|
||||
// Following is for direct print to save & compare camera positions against recorded
|
||||
// file
|
||||
printf(
|
||||
"camera %8.4f %8.4f %13.3f %16.7f %16.7f %16.7f\n",
|
||||
global::windowDelegate.applicationTime(),
|
||||
global::windowDelegate.applicationTime() - _timestampPlaybackStarted_application,
|
||||
global::timeManager.time().j2000Seconds(),
|
||||
interpolatedCamera.x,
|
||||
interpolatedCamera.y,
|
||||
interpolatedCamera.z
|
||||
);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
|
||||
@@ -124,14 +124,16 @@ void NavigationHandler::updateCamera(double deltaTime) {
|
||||
if (_cameraUpdatedFromScript) {
|
||||
_cameraUpdatedFromScript = false;
|
||||
}
|
||||
else if ( ! _playbackModeEnabled ) {
|
||||
if (_camera) {
|
||||
if (_useKeyFrameInteraction) {
|
||||
_keyframeNavigator->updateCamera(*_camera, _playbackModeEnabled);
|
||||
}
|
||||
else {
|
||||
_orbitalNavigator->updateStatesFromInput(*_inputState, deltaTime);
|
||||
_orbitalNavigator->updateCameraStateFromStates(deltaTime);
|
||||
else {
|
||||
if ( ! _playbackModeEnabled ) {
|
||||
if (_camera) {
|
||||
if (_useKeyFrameInteraction) {
|
||||
_keyframeNavigator->updateCamera(*_camera, _playbackModeEnabled);
|
||||
}
|
||||
else {
|
||||
_orbitalNavigator->updateStatesFromInput(*_inputState, deltaTime);
|
||||
_orbitalNavigator->updateCameraStateFromStates(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,6 +152,8 @@ void NavigationHandler::triggerPlaybackStart() {
|
||||
}
|
||||
|
||||
void NavigationHandler::stopPlayback() {
|
||||
_orbitalNavigator->resetVelocities();
|
||||
_orbitalNavigator->resetNodeMovements();
|
||||
_playbackModeEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -345,6 +345,11 @@ glm::quat OrbitalNavigator::anchorNodeToCameraRotation() const {
|
||||
}
|
||||
|
||||
|
||||
void OrbitalNavigator::resetVelocities() {
|
||||
_mouseStates.resetVelocities();
|
||||
_joystickStates.resetVelocities();
|
||||
}
|
||||
|
||||
void OrbitalNavigator::updateStatesFromInput(const InputState& inputState,
|
||||
double deltaTime)
|
||||
{
|
||||
@@ -580,6 +585,21 @@ void OrbitalNavigator::setAimNode(const std::string& aimNode) {
|
||||
_aim.set(aimNode);
|
||||
}
|
||||
|
||||
void OrbitalNavigator::resetNodeMovements() {
|
||||
if (_anchorNode) {
|
||||
_previousAnchorNodePosition = _anchorNode->worldPosition();
|
||||
_previousAnchorNodeRotation = glm::quat_cast(_anchorNode->worldRotationMatrix());
|
||||
} else {
|
||||
_previousAnchorNodePosition = glm::dvec3(0.0);
|
||||
_previousAnchorNodeRotation = glm::dquat();
|
||||
}
|
||||
if (_aimNode) {
|
||||
_previousAimNodePosition = _aimNode->worldPosition();
|
||||
} else {
|
||||
_previousAimNodePosition = glm::dvec3(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void OrbitalNavigator::startRetargetAnchor() {
|
||||
if (!_anchorNode) {
|
||||
return;
|
||||
|
||||
@@ -74,7 +74,17 @@ void SessionRecording::setRecordDataFormat(RecordedDataMode dataMode) {
|
||||
}
|
||||
|
||||
bool SessionRecording::startRecording(const std::string& filename) {
|
||||
const std::string absFilename = absPath(filename);
|
||||
if (filename.find("/") != std::string::npos) {
|
||||
LERROR("Recording filename must not contain path (/) elements");
|
||||
return false;
|
||||
}
|
||||
if (!FileSys.directoryExists(absPath("${RECORDINGS}"))) {
|
||||
FileSys.createDirectory(
|
||||
absPath("${RECORDINGS}"),
|
||||
ghoul::filesystem::FileSystem::Recursive::Yes
|
||||
);
|
||||
}
|
||||
const std::string absFilename = absPath("${RECORDINGS}/" + filename);
|
||||
|
||||
if (_state == SessionState::Playback) {
|
||||
_playbackFile.close();
|
||||
@@ -123,7 +133,11 @@ void SessionRecording::stopRecording() {
|
||||
bool SessionRecording::startPlayback(const std::string& filename,
|
||||
KeyframeTimeRef timeMode, bool forceSimTimeAtStart)
|
||||
{
|
||||
const std::string absFilename = absPath(filename);
|
||||
if (filename.find("/") != std::string::npos) {
|
||||
LERROR("Playback filename must not contain path (/) elements");
|
||||
return false;
|
||||
}
|
||||
const std::string absFilename = absPath("${RECORDINGS}/" + filename);
|
||||
|
||||
if (_state == SessionState::Recording) {
|
||||
LERROR("Unable to start playback while in session recording mode");
|
||||
@@ -299,7 +313,6 @@ void SessionRecording::cleanUpPlayback() {
|
||||
if (node) {
|
||||
global::navigationHandler.orbitalNavigator().setFocusNode(node->identifier());
|
||||
}
|
||||
|
||||
}
|
||||
global::scriptScheduler.stopPlayback();
|
||||
|
||||
@@ -477,7 +490,7 @@ void SessionRecording::saveCameraKeyframe() {
|
||||
<< std::fixed << std::setprecision(7) << kf._rotation.y << " "
|
||||
<< std::fixed << std::setprecision(7) << kf._rotation.z << " "
|
||||
<< std::fixed << std::setprecision(7) << kf._rotation.w << " ";
|
||||
keyframeLine << std::fixed << std::setprecision(7) << kf._scale << " ";
|
||||
keyframeLine << std::scientific << kf._scale << " ";
|
||||
if (kf._followNodeRotation) {
|
||||
keyframeLine << "F ";
|
||||
}
|
||||
@@ -586,6 +599,16 @@ void SessionRecording::preSynchronization() {
|
||||
else if (_cleanupNeeded) {
|
||||
cleanUpPlayback();
|
||||
}
|
||||
|
||||
//Handle callback(s) for change in idle/record/playback state
|
||||
if (_state != _lastState) {
|
||||
using K = const CallbackHandle;
|
||||
using V = StateChangeCallback;
|
||||
for (const std::pair<K, V>& it : _stateChangeCallbacks) {
|
||||
it.second();
|
||||
}
|
||||
}
|
||||
_lastState = _state;
|
||||
}
|
||||
|
||||
bool SessionRecording::isRecording() const {
|
||||
@@ -596,6 +619,10 @@ bool SessionRecording::isPlayingBack() const {
|
||||
return (_state == SessionState::Playback);
|
||||
}
|
||||
|
||||
SessionRecording::SessionState SessionRecording::state() const {
|
||||
return _state;
|
||||
}
|
||||
|
||||
bool SessionRecording::playbackAddEntriesToTimeline() {
|
||||
bool parsingErrorsFound = false;
|
||||
|
||||
@@ -1255,6 +1282,44 @@ void SessionRecording::saveKeyframeToFile(std::string entry) {
|
||||
_recordFile << std::move(entry) << std::endl;
|
||||
}
|
||||
|
||||
SessionRecording::CallbackHandle SessionRecording::addStateChangeCallback(StateChangeCallback cb) {
|
||||
CallbackHandle handle = _nextCallbackHandle++;
|
||||
_stateChangeCallbacks.emplace_back(handle, std::move(cb));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void SessionRecording::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 handle"
|
||||
);
|
||||
|
||||
_stateChangeCallbacks.erase(it);
|
||||
}
|
||||
|
||||
std::string SessionRecording::playbackList() {
|
||||
std::string fileList;
|
||||
const std::string recordingsPath = absPath("${RECORDINGS}");
|
||||
|
||||
ghoul::filesystem::Directory currentDir(recordingsPath);
|
||||
std::vector<std::string> allInputFiles = currentDir.readFiles();
|
||||
for (std::string f : allInputFiles) {
|
||||
//Remove path and keep only the filename, and add newline after
|
||||
fileList.append(f.substr(recordingsPath.length() + 1, (f.length() - recordingsPath.length()) - 1));
|
||||
fileList.append("\n");
|
||||
}
|
||||
//Remove the final trailing newline from the list and return it.
|
||||
return fileList.substr(0, fileList.size() - 1);
|
||||
}
|
||||
|
||||
scripting::LuaLibrary SessionRecording::luaLibrary() {
|
||||
return {
|
||||
"sessionRecording",
|
||||
|
||||
Reference in New Issue
Block a user