From 9ed418f7b612030cf6d2b825f043456319b99af1 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 17 Aug 2015 19:19:11 -0700 Subject: [PATCH 01/55] Simple frequencey decoupling. Still same thread. --- interface/src/Application.cpp | 23 +++++++++++++++++------ interface/src/Application.h | 3 +++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 621210cc4e..b89fc35f2c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2289,6 +2289,7 @@ QVector Application::pasteEntities(float x, float y, float z) { void Application::initDisplay() { } +static QTimer* avatarTimer = NULL; void Application::init() { // Make sure Login state is up to date DependencyManager::get()->toggleLoginDialog(); @@ -2351,6 +2352,11 @@ void Application::init() { // Make sure any new sounds are loaded as soon as know about them. connect(tree, &EntityTree::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); connect(_myAvatar, &MyAvatar::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); + + const qint64 AVATAR_UPDATE_INTERVAL_MSECS = 1000 / 55; + avatarTimer = new QTimer(this); + connect(avatarTimer, &QTimer::timeout, this, &Application::avatarUpdate); + avatarTimer->start(AVATAR_UPDATE_INTERVAL_MSECS); } void Application::closeMirrorView() { @@ -2415,6 +2421,16 @@ void Application::updateMouseRay() { } } +void Application::avatarUpdate() { + PerformanceTimer perfTimer("myAvatar"); + qint64 now = usecTimestampNow(); + float deltaTime = (now - _lastAvatarUpdate) / (1000.0f * 1000.0f); + _lastAvatarUpdate = now; + updateMyAvatarLookAtPosition(); + // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes + DependencyManager::get()->updateMyAvatar(deltaTime); +} + // Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone. // (Maybe this code should be moved there?) // The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition(). @@ -2787,12 +2803,7 @@ void Application::update(float deltaTime) { _overlays.update(deltaTime); } - { - PerformanceTimer perfTimer("myAvatar"); - updateMyAvatarLookAtPosition(); - // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes - DependencyManager::get()->updateMyAvatar(deltaTime); - } + avatarUpdate(); { PerformanceTimer perfTimer("emitSimulating"); diff --git a/interface/src/Application.h b/interface/src/Application.h index e0d4fa559d..9e1e5e8239 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -681,6 +681,9 @@ private: EntityItemID _keyboardFocusedItem; quint64 _lastAcceptedKeyPress = 0; + + void avatarUpdate(); + quint64 _lastAvatarUpdate = 0; }; #endif // hifi_Application_h From 2819f3ac1bfae2a2e97327663b7e60a78233b178 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 18 Aug 2015 16:39:30 -0700 Subject: [PATCH 02/55] constant --- interface/src/avatar/AvatarUpdate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index c14fcdd6f5..c997a76132 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -84,7 +84,7 @@ void AvatarUpdate::avatarUpdateIfSynchronous() { void AvatarUpdate::avatarUpdate() { PerformanceTimer perfTimer("AvatarUpdate"); quint64 now = usecTimestampNow(); - float deltaTime = (now - _lastAvatarUpdate) / (1000.0f * 1000.0f); + float deltaTime = (now - _lastAvatarUpdate) / (float) USECS_PER_SECOND; Application::getInstance()->setAvatarSimrateSample(1.0f / deltaTime); _lastAvatarUpdate = now; From b8ca604bf8b76b976a1c90bf22d9ba81c0cfb280 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 25 Aug 2015 16:17:50 -0700 Subject: [PATCH 03/55] Checkpoint before GenericThread. --- interface/src/Application.cpp | 5 +- interface/src/avatar/AvatarUpdate.cpp | 109 ++++++++++++++------------ interface/src/avatar/AvatarUpdate.h | 43 ++++++++-- 3 files changed, 98 insertions(+), 59 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9bc395e168..919816a643 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -771,7 +771,7 @@ void Application::cleanupBeforeQuit() { // first stop all timers directly or by invokeMethod // depending on what thread they run in - _avatarUpdate->stop(); + _avatarUpdate->terminate(); locationUpdateTimer->stop(); balanceUpdateTimer->stop(); identityPacketTimer->stop(); @@ -2453,6 +2453,7 @@ void Application::init() { connect(_myAvatar, &MyAvatar::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); _avatarUpdate = new AvatarUpdate(); + _avatarUpdate->initialize(_avatarUpdate->isToBeThreaded()); } void Application::closeMirrorView() { @@ -2884,7 +2885,7 @@ void Application::update(float deltaTime) { _overlays.update(deltaTime); } - _avatarUpdate->avatarUpdateIfSynchronous(); + _avatarUpdate->synchronousProcess(); { PerformanceTimer perfTimer("emitSimulating"); diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index c997a76132..acab5a46ad 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -15,73 +15,69 @@ #include "AvatarUpdate.h" #include "InterfaceLogging.h" -enum UpdateType { - Synchronous = 1, - Timer, - Thread -}; - -AvatarUpdate::AvatarUpdate() : _lastAvatarUpdate(0) { +// GenericThread accepts an optional "parent" argument, defaulting to nullptr. +// This is odd, because the moveToThread() in GenericThread::initialize() will +// become a no-op if the instance ever inits QObject(someNonNullParentQObject). +// (The only clue will be a log message "QObject::moveToThread: Cannot move objects with a parent", +// and things will end up in the same thread that created this instance. Hillarity ensues.) +// As it turns out, all the other subclasses of GenericThread (at this time) do not init +// GenericThread(parent), so things work as expected. Here we explicitly init GenericThread(nullptr) +// so that there is no confusion and no chance for a hillarious thread debugging session. +AvatarUpdate::AvatarUpdate() : QObject(nullptr), _timer(nullptr), _lastAvatarUpdate(0), _thread(nullptr)/*FIXME remove*/ { + setObjectName("Avatar Update"); // GenericThread::initialize uses this to set the thread name. Settings settings; - int type = settings.value("AvatarUpdateType", UpdateType::Synchronous).toInt(); - _targetSimrate = settings.value("AvatarUpdateTargetSimrate", 60).toInt(); - qCDebug(interfaceapp) << "AvatarUpdate using" << type << "at" << _targetSimrate << "sims/second"; - switch (type) { - case UpdateType::Synchronous: - _timer = nullptr; - break; - case UpdateType::Timer: - initTimer(); - break; - default: - initThread(); - break; - } + const int DEFAULT_TARGET_AVATAR_SIMRATE = 60; + _targetSimrate = settings.value("AvatarUpdateTargetSimrate", DEFAULT_TARGET_AVATAR_SIMRATE).toInt(); + _type = settings.value("AvatarUpdateType", UpdateType::Synchronous).toInt(); + qCDebug(interfaceapp) << "AvatarUpdate using" << _type << "at" << _targetSimrate << "sims/second"; } -void AvatarUpdate::stop() { - if (!_timer) { - return; +// We could have the constructor call initialize(), but GenericThread::initialize can take parameters. +// Keeping it separately called by the client allows that client to pass those without our +// constructor needing to know about them. + +// GenericThread::terimate() calls terminating() only when _isThreaded, so it's not good enough +// to just override terminating(). Instead, we extend terminate(); +void AvatarUpdate::terminate() { + if (_timer) { + _timer->stop(); } - _timer->stop(); - if (_avatarThread) { - return; + // FIXME: GenericThread::terminate(); + if (_thread) { + _thread->quit(); } - _avatarThread->quit(); } +// QTimer runs in the thread that starts it. +// threadRoutine() is called once within the separate thread (if any), +// or on each main loop update via synchronousProcess(); +void AvatarUpdate::threadRoutine() { + if (!_timer && (_type != UpdateType::Synchronous)) { + initTimer(); + } + if (!_timer) { + process(); + } +} void AvatarUpdate::initTimer() { const qint64 AVATAR_UPDATE_INTERVAL_MSECS = 1000 / _targetSimrate; _timer = new QTimer(this); - connect(_timer, &QTimer::timeout, this, &AvatarUpdate::avatarUpdate); + connect(_timer, &QTimer::timeout, this, &AvatarUpdate::process); _timer->start(AVATAR_UPDATE_INTERVAL_MSECS); } -void AvatarUpdate::initThread() { - _avatarThread = new QThread(); - _avatarThread->setObjectName("Avatar Update Thread"); - this->moveToThread(_avatarThread); - connect(_avatarThread, &QThread::started, this, &AvatarUpdate::initTimer); - _avatarThread->start(); -} -// There are a couple of ways we could do this. -// Right now, the goals are: -// 1. allow development to switch between UpdateType -// 2. minimize changes everwhere, particularly outside of Avatars. -// As an example of the latter, we could make Application::isHMDMode() thread safe, but in this case -// we just made AvatarUpdate::isHMDMode() thread safe. -void AvatarUpdate::avatarUpdateIfSynchronous() { +void AvatarUpdate::synchronousProcess() { + // Keep our own updated value, so that our asynchronous code can consult it. _isHMDMode = Application::getInstance()->isHMDMode(); + if (_updateBillboard) { Application::getInstance()->getMyAvatar()->doUpdateBillboard(); } - if (_timer) { - return; - } - avatarUpdate(); + + threadRoutine(); } -void AvatarUpdate::avatarUpdate() { + bool AvatarUpdate::process() { PerformanceTimer perfTimer("AvatarUpdate"); quint64 now = usecTimestampNow(); float deltaTime = (now - _lastAvatarUpdate) / (float) USECS_PER_SECOND; @@ -95,4 +91,19 @@ void AvatarUpdate::avatarUpdate() { Application::getInstance()->updateMyAvatarLookAtPosition(); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes DependencyManager::get()->updateMyAvatar(deltaTime); -} \ No newline at end of file + return true; +} + +// To be removed with GenericThread +void AvatarUpdate::initialize(bool isThreaded) { //fixme remove + if (isThreaded) { + initThread(); + } +} +void AvatarUpdate::initThread() { + _thread = new QThread(); + _thread->setObjectName(objectName()); + this->moveToThread(_thread); + connect(_thread, &QThread::started, this, &AvatarUpdate::threadRoutine); + _thread->start(); +} diff --git a/interface/src/avatar/AvatarUpdate.h b/interface/src/avatar/AvatarUpdate.h index af17ffdeab..6a70aeb1f9 100644 --- a/interface/src/avatar/AvatarUpdate.h +++ b/interface/src/avatar/AvatarUpdate.h @@ -15,27 +15,54 @@ #include #include +// There are a couple of ways we could do this. +// Right now, the goals are: +// 1. allow development to switch between UpdateType +// e.g.: see uses of UpdateType. +// 2. minimize changes everwhere, particularly outside of Avatars. +// e.g.: we could make Application::isHMDMode() thread safe, but for now we just made AvatarUpdate::isHMDMode() thread safe. + + // Home for the avatarUpdate operations (e.g., whether on a separate thread, pipelined in various ways, etc.) -// TODO: become GenericThread // This might get folded into AvatarManager. class AvatarUpdate : public QObject { Q_OBJECT public: AvatarUpdate(); - void avatarUpdateIfSynchronous(); - bool isHMDMode() { return _isHMDMode; } + void synchronousProcess(); void setRequestBillboardUpdate(bool needsUpdate) { _updateBillboard = needsUpdate; } - void stop(); + void terminate(); // Extends GenericThread::terminate to also kill timer. + private: + virtual bool process(); // No reason for other classes to invoke this. void initTimer(); - void initThread(); - void avatarUpdate(); int _targetSimrate; - bool _isHMDMode; bool _updateBillboard; QTimer* _timer; - QThread* _avatarThread; quint64 _lastAvatarUpdate; + + // Goes away when we get rid of the ability to switch back and forth in settings: + enum UpdateType { + Synchronous = 1, + Timer, + Thread + }; + int _type; +public: + bool isToBeThreaded() const { return _type == UpdateType::Thread; } // Currently set by constructor from settings. + void threadRoutine(); + + // Goes away when using GenericThread: + void initialize(bool isThreaded); +private: + QThread* _thread; + void initThread(); + + // Goes away if Applicaiton::isHMDMode() is made thread safe: +public: + bool isHMDMode() { return _isHMDMode; } +private: + bool _isHMDMode; }; From 38bc8634de226b4534e4801fceaf2a2d466a8f21 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 25 Aug 2015 21:26:46 -0700 Subject: [PATCH 04/55] displayPlugin is not thread safe, and switch from simulate rate to interval in prep for sleep. --- interface/src/Application.cpp | 2 +- interface/src/avatar/AvatarUpdate.cpp | 9 +++++---- interface/src/avatar/AvatarUpdate.h | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 919816a643..50f39c87bb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2588,7 +2588,7 @@ void Application::updateMyAvatarLookAtPosition() { } else { // I am not looking at anyone else, so just look forward if (isHMD) { - glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); + glm::mat4 headPose = _avatarUpdate->getHeadPose(); glm::quat headRotation = glm::quat_cast(headPose); lookAtSpot = _myCamera.getPosition() + _myAvatar->getOrientation() * (headRotation * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index acab5a46ad..4335cbd16d 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -13,6 +13,7 @@ #include "Application.h" #include "AvatarManager.h" #include "AvatarUpdate.h" +#include #include "InterfaceLogging.h" // GenericThread accepts an optional "parent" argument, defaulting to nullptr. @@ -27,9 +28,9 @@ AvatarUpdate::AvatarUpdate() : QObject(nullptr), _timer(nullptr), _lastAvatarUp setObjectName("Avatar Update"); // GenericThread::initialize uses this to set the thread name. Settings settings; const int DEFAULT_TARGET_AVATAR_SIMRATE = 60; - _targetSimrate = settings.value("AvatarUpdateTargetSimrate", DEFAULT_TARGET_AVATAR_SIMRATE).toInt(); + _targetInterval = USECS_PER_SECOND / settings.value("AvatarUpdateTargetSimrate", DEFAULT_TARGET_AVATAR_SIMRATE).toInt(); _type = settings.value("AvatarUpdateType", UpdateType::Synchronous).toInt(); - qCDebug(interfaceapp) << "AvatarUpdate using" << _type << "at" << _targetSimrate << "sims/second"; + qCDebug(interfaceapp) << "AvatarUpdate using" << _type << "at" << _targetInterval << "microseconds"; } // We could have the constructor call initialize(), but GenericThread::initialize can take parameters. // Keeping it separately called by the client allows that client to pass those without our @@ -59,16 +60,16 @@ void AvatarUpdate::threadRoutine() { } } void AvatarUpdate::initTimer() { - const qint64 AVATAR_UPDATE_INTERVAL_MSECS = 1000 / _targetSimrate; _timer = new QTimer(this); connect(_timer, &QTimer::timeout, this, &AvatarUpdate::process); - _timer->start(AVATAR_UPDATE_INTERVAL_MSECS); + _timer->start(_targetInterval / USECS_PER_MSEC); } void AvatarUpdate::synchronousProcess() { // Keep our own updated value, so that our asynchronous code can consult it. _isHMDMode = Application::getInstance()->isHMDMode(); + _headPose = Application::getInstance()->getActiveDisplayPlugin()->getHeadPose(); if (_updateBillboard) { Application::getInstance()->getMyAvatar()->doUpdateBillboard(); diff --git a/interface/src/avatar/AvatarUpdate.h b/interface/src/avatar/AvatarUpdate.h index 6a70aeb1f9..93192ae50d 100644 --- a/interface/src/avatar/AvatarUpdate.h +++ b/interface/src/avatar/AvatarUpdate.h @@ -36,7 +36,7 @@ public: private: virtual bool process(); // No reason for other classes to invoke this. void initTimer(); - int _targetSimrate; + quint64 _targetInterval; bool _updateBillboard; QTimer* _timer; quint64 _lastAvatarUpdate; @@ -58,11 +58,13 @@ private: QThread* _thread; void initThread(); - // Goes away if Applicaiton::isHMDMode() is made thread safe: + // Goes away if Applicaiton::isHMDMode() and friends are made thread safe: public: bool isHMDMode() { return _isHMDMode; } + glm::mat4 getHeadPose() { return _headPose; } private: bool _isHMDMode; + glm::mat4 _headPose; }; From b0734a06f3b81fbf1649f06cae09261ed102f179 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 26 Aug 2015 11:14:09 -0700 Subject: [PATCH 05/55] snapshot --- interface/src/avatar/AvatarUpdate.cpp | 36 +++++++++++++++++++-------- interface/src/avatar/AvatarUpdate.h | 6 ++--- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 4335cbd16d..7f5131f94b 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -24,7 +24,7 @@ // As it turns out, all the other subclasses of GenericThread (at this time) do not init // GenericThread(parent), so things work as expected. Here we explicitly init GenericThread(nullptr) // so that there is no confusion and no chance for a hillarious thread debugging session. -AvatarUpdate::AvatarUpdate() : QObject(nullptr), _timer(nullptr), _lastAvatarUpdate(0), _thread(nullptr)/*FIXME remove*/ { +AvatarUpdate::AvatarUpdate() : QObject(nullptr), _lastAvatarUpdate(0), _timer(nullptr), _thread(nullptr)/*FIXME remove*/ { setObjectName("Avatar Update"); // GenericThread::initialize uses this to set the thread name. Settings settings; const int DEFAULT_TARGET_AVATAR_SIMRATE = 60; @@ -61,8 +61,11 @@ void AvatarUpdate::threadRoutine() { } void AvatarUpdate::initTimer() { _timer = new QTimer(this); - connect(_timer, &QTimer::timeout, this, &AvatarUpdate::process); - _timer->start(_targetInterval / USECS_PER_MSEC); + /*FIXME connect(_timer, &QTimer::timeout, this, &AvatarUpdate::process); + _timer->start(_targetInterval / USECS_PER_MSEC);*/ + while (true) { + process(); + } } void AvatarUpdate::synchronousProcess() { @@ -75,23 +78,34 @@ void AvatarUpdate::synchronousProcess() { Application::getInstance()->getMyAvatar()->doUpdateBillboard(); } - threadRoutine(); + //threadRoutine(); + //process(); //fixme } - bool AvatarUpdate::process() { +bool AvatarUpdate::process() { PerformanceTimer perfTimer("AvatarUpdate"); - quint64 now = usecTimestampNow(); - float deltaTime = (now - _lastAvatarUpdate) / (float) USECS_PER_SECOND; - Application::getInstance()->setAvatarSimrateSample(1.0f / deltaTime); - _lastAvatarUpdate = now; + quint64 start = usecTimestampNow(); + quint64 deltaMicroseconds = start - _lastAvatarUpdate; + float deltaSeconds = deltaMicroseconds / (float) USECS_PER_SECOND; + Application::getInstance()->setAvatarSimrateSample(1.0f / deltaSeconds); //loop through all the other avatars and simulate them... //gets current lookat data, removes missing avatars, etc. - DependencyManager::get()->updateOtherAvatars(deltaTime); + DependencyManager::get()->updateOtherAvatars(deltaSeconds); Application::getInstance()->updateMyAvatarLookAtPosition(); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes - DependencyManager::get()->updateMyAvatar(deltaTime); + DependencyManager::get()->updateMyAvatar(deltaSeconds); + + if (isToBeThreaded()) { + int elapsed = (usecTimestampNow() - start); + int usecToSleep = _targetInterval - elapsed; + if (usecToSleep < 0) { + usecToSleep = 1; // always yield + } + usleep(usecToSleep); + } + _lastAvatarUpdate = start; return true; } diff --git a/interface/src/avatar/AvatarUpdate.h b/interface/src/avatar/AvatarUpdate.h index 93192ae50d..fc2f540111 100644 --- a/interface/src/avatar/AvatarUpdate.h +++ b/interface/src/avatar/AvatarUpdate.h @@ -36,10 +36,9 @@ public: private: virtual bool process(); // No reason for other classes to invoke this. void initTimer(); - quint64 _targetInterval; + quint64 _targetInterval; // microseconds bool _updateBillboard; - QTimer* _timer; - quint64 _lastAvatarUpdate; + quint64 _lastAvatarUpdate; // microsoeconds // Goes away when we get rid of the ability to switch back and forth in settings: enum UpdateType { @@ -55,6 +54,7 @@ public: // Goes away when using GenericThread: void initialize(bool isThreaded); private: + QTimer* _timer; QThread* _thread; void initThread(); From 54877cdd47d837950bd0b65f4abb225c4d6f9a65 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 26 Aug 2015 11:59:12 -0700 Subject: [PATCH 06/55] Use GenericThread, and use usleep instead of QTimer (which is incompatible with GenericThread). --- interface/src/avatar/AvatarUpdate.cpp | 83 +++++++-------------------- interface/src/avatar/AvatarUpdate.h | 40 +++---------- 2 files changed, 29 insertions(+), 94 deletions(-) diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 7f5131f94b..9c6f4a6f25 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -24,50 +24,22 @@ // As it turns out, all the other subclasses of GenericThread (at this time) do not init // GenericThread(parent), so things work as expected. Here we explicitly init GenericThread(nullptr) // so that there is no confusion and no chance for a hillarious thread debugging session. -AvatarUpdate::AvatarUpdate() : QObject(nullptr), _lastAvatarUpdate(0), _timer(nullptr), _thread(nullptr)/*FIXME remove*/ { +AvatarUpdate::AvatarUpdate() : GenericThread(nullptr), _lastAvatarUpdate(0)/*, _timer(nullptr), _thread(nullptr)FIXME remove*/ { setObjectName("Avatar Update"); // GenericThread::initialize uses this to set the thread name. Settings settings; const int DEFAULT_TARGET_AVATAR_SIMRATE = 60; _targetInterval = USECS_PER_SECOND / settings.value("AvatarUpdateTargetSimrate", DEFAULT_TARGET_AVATAR_SIMRATE).toInt(); - _type = settings.value("AvatarUpdateType", UpdateType::Synchronous).toInt(); - qCDebug(interfaceapp) << "AvatarUpdate using" << _type << "at" << _targetInterval << "microseconds"; + _isToBeThreaded = settings.value("AvatarUpdateIsThreaded", true).toBool(); + if (_isToBeThreaded) { + qCDebug(interfaceapp) << "AvatarUpdate threaded at" << _targetInterval << "microsecond interval."; + } else { + qCDebug(interfaceapp) << "AvatarUpdate unthreaded."; + } } // We could have the constructor call initialize(), but GenericThread::initialize can take parameters. // Keeping it separately called by the client allows that client to pass those without our // constructor needing to know about them. -// GenericThread::terimate() calls terminating() only when _isThreaded, so it's not good enough -// to just override terminating(). Instead, we extend terminate(); -void AvatarUpdate::terminate() { - if (_timer) { - _timer->stop(); - } - // FIXME: GenericThread::terminate(); - if (_thread) { - _thread->quit(); - } -} - -// QTimer runs in the thread that starts it. -// threadRoutine() is called once within the separate thread (if any), -// or on each main loop update via synchronousProcess(); -void AvatarUpdate::threadRoutine() { - if (!_timer && (_type != UpdateType::Synchronous)) { - initTimer(); - } - if (!_timer) { - process(); - } -} -void AvatarUpdate::initTimer() { - _timer = new QTimer(this); - /*FIXME connect(_timer, &QTimer::timeout, this, &AvatarUpdate::process); - _timer->start(_targetInterval / USECS_PER_MSEC);*/ - while (true) { - process(); - } -} - void AvatarUpdate::synchronousProcess() { // Keep our own updated value, so that our asynchronous code can consult it. @@ -78,14 +50,17 @@ void AvatarUpdate::synchronousProcess() { Application::getInstance()->getMyAvatar()->doUpdateBillboard(); } - //threadRoutine(); - //process(); //fixme + if (isThreaded()) { + return; + } + process(); } bool AvatarUpdate::process() { PerformanceTimer perfTimer("AvatarUpdate"); quint64 start = usecTimestampNow(); quint64 deltaMicroseconds = start - _lastAvatarUpdate; + _lastAvatarUpdate = start; float deltaSeconds = deltaMicroseconds / (float) USECS_PER_SECOND; Application::getInstance()->setAvatarSimrateSample(1.0f / deltaSeconds); @@ -96,29 +71,15 @@ bool AvatarUpdate::process() { Application::getInstance()->updateMyAvatarLookAtPosition(); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes DependencyManager::get()->updateMyAvatar(deltaSeconds); - - if (isToBeThreaded()) { - int elapsed = (usecTimestampNow() - start); - int usecToSleep = _targetInterval - elapsed; - if (usecToSleep < 0) { - usecToSleep = 1; // always yield - } - usleep(usecToSleep); - } - _lastAvatarUpdate = start; + + if (!isThreaded()) { + return true; + } + int elapsed = (usecTimestampNow() - start); + int usecToSleep = _targetInterval - elapsed; + if (usecToSleep < 0) { + usecToSleep = 1; // always yield + } + usleep(usecToSleep); return true; } - -// To be removed with GenericThread -void AvatarUpdate::initialize(bool isThreaded) { //fixme remove - if (isThreaded) { - initThread(); - } -} -void AvatarUpdate::initThread() { - _thread = new QThread(); - _thread->setObjectName(objectName()); - this->moveToThread(_thread); - connect(_thread, &QThread::started, this, &AvatarUpdate::threadRoutine); - _thread->start(); -} diff --git a/interface/src/avatar/AvatarUpdate.h b/interface/src/avatar/AvatarUpdate.h index fc2f540111..8e3448c533 100644 --- a/interface/src/avatar/AvatarUpdate.h +++ b/interface/src/avatar/AvatarUpdate.h @@ -15,50 +15,24 @@ #include #include -// There are a couple of ways we could do this. -// Right now, the goals are: -// 1. allow development to switch between UpdateType -// e.g.: see uses of UpdateType. -// 2. minimize changes everwhere, particularly outside of Avatars. -// e.g.: we could make Application::isHMDMode() thread safe, but for now we just made AvatarUpdate::isHMDMode() thread safe. - - // Home for the avatarUpdate operations (e.g., whether on a separate thread, pipelined in various ways, etc.) // This might get folded into AvatarManager. -class AvatarUpdate : public QObject { +class AvatarUpdate : public GenericThread { Q_OBJECT public: AvatarUpdate(); void synchronousProcess(); void setRequestBillboardUpdate(bool needsUpdate) { _updateBillboard = needsUpdate; } - void terminate(); // Extends GenericThread::terminate to also kill timer. + bool isToBeThreaded() { return _isToBeThreaded; } private: virtual bool process(); // No reason for other classes to invoke this. - void initTimer(); - quint64 _targetInterval; // microseconds - bool _updateBillboard; quint64 _lastAvatarUpdate; // microsoeconds - - // Goes away when we get rid of the ability to switch back and forth in settings: - enum UpdateType { - Synchronous = 1, - Timer, - Thread - }; - int _type; -public: - bool isToBeThreaded() const { return _type == UpdateType::Thread; } // Currently set by constructor from settings. - void threadRoutine(); - - // Goes away when using GenericThread: - void initialize(bool isThreaded); -private: - QTimer* _timer; - QThread* _thread; - void initThread(); - - // Goes away if Applicaiton::isHMDMode() and friends are made thread safe: + quint64 _targetInterval; // microseconds + bool _isToBeThreaded; + bool _updateBillboard; + + // Goes away if Application::getActiveDisplayPlugin() and friends are made thread safe: public: bool isHMDMode() { return _isHMDMode; } glm::mat4 getHeadPose() { return _headPose; } From 475331a97df3a41ddeaf5358f561c0928136243a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 26 Aug 2015 16:08:07 -0700 Subject: [PATCH 07/55] Sprinkle locks everywhere. --- interface/src/avatar/Avatar.cpp | 5 +++++ interface/src/avatar/AvatarManager.cpp | 6 +++++ interface/src/avatar/SkeletonModel.cpp | 7 ++++++ interface/src/avatar/SkeletonModel.h | 2 ++ libraries/avatars/src/AvatarData.h | 2 ++ .../src/DynamicCharacterController.cpp | 2 ++ libraries/render-utils/src/Model.cpp | 22 +++++++++++++++---- libraries/render-utils/src/Model.h | 3 +++ 8 files changed, 45 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e7423336b1..7b40b5cc5b 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -318,6 +318,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrupdate(); } @@ -392,6 +393,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) { + avatarLock.unlock(); return; } @@ -542,6 +544,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { if (!isMyAvatar() || cameraMode != CAMERA_MODE_FIRST_PERSON) { renderDisplayName(batch, *renderArgs->_viewFrustum, renderArgs->_viewport); } + avatarLock.unlock(); } glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { @@ -1019,6 +1022,7 @@ void Avatar::setBillboard(const QByteArray& billboard) { } int Avatar::parseDataFromBuffer(const QByteArray& buffer) { + avatarLock.lockForWrite(); if (!_initialized) { // now that we have data for this Avatar we are go for init init(); @@ -1034,6 +1038,7 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) { if (_moving && _motionState) { _motionState->addDirtyFlags(EntityItem::DIRTY_POSITION); } + avatarLock.unlock(); return bytesRead; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1644f22b09..fefb774d66 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -93,7 +93,9 @@ void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); + _myAvatar->avatarLock.lockForWrite(); _myAvatar->update(deltaTime); + _myAvatar->avatarLock.unlock(); quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; @@ -129,7 +131,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { _avatarFades.push_back(avatarIterator.value()); avatarIterator = _avatarHash.erase(avatarIterator); } else { + avatar->avatarLock.lockForWrite(); avatar->simulate(deltaTime); + avatar->avatarLock.unlock(); ++avatarIterator; } } @@ -148,6 +152,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { render::PendingChanges pendingChanges; while (fadingIterator != _avatarFades.end()) { auto avatar = std::static_pointer_cast(*fadingIterator); + avatar->avatarLock.lockForWrite(); avatar->setTargetScale(avatar->getScale() * SHRINK_RATE, true); if (avatar->getTargetScale() < MIN_FADE_SCALE) { avatar->removeFromScene(*fadingIterator, scene, pendingChanges); @@ -156,6 +161,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { avatar->simulate(deltaTime); ++fadingIterator; } + avatar->avatarLock.unlock(); } scene->enqueuePendingChanges(pendingChanges); } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index bd50215de6..973a9ea8c7 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -39,6 +39,13 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer r SkeletonModel::~SkeletonModel() { } +void SkeletonModel::avatarLockForWriteIfApplicable() { + _owningAvatar->avatarLock.lockForWrite(); +} +void SkeletonModel::avatarLockReleaseIfApplicable() { + _owningAvatar->avatarLock.unlock(); +} + void SkeletonModel::initJointStates(QVector states) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 4ae615eadd..f18895d674 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -105,6 +105,8 @@ public: float getHeadClipDistance() const { return _headClipDistance; } virtual void onInvalidate() override; + virtual void avatarLockForWriteIfApplicable() override; + virtual void avatarLockReleaseIfApplicable() override; signals: diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 8bb874bc71..7356bc863b 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -305,6 +305,8 @@ public: bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } + QReadWriteLock avatarLock; // Name is redundant, but it aids searches. + public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); diff --git a/libraries/physics/src/DynamicCharacterController.cpp b/libraries/physics/src/DynamicCharacterController.cpp index 22f84eccec..7c1455c69e 100644 --- a/libraries/physics/src/DynamicCharacterController.cpp +++ b/libraries/physics/src/DynamicCharacterController.cpp @@ -405,6 +405,7 @@ void DynamicCharacterController::preSimulation(btScalar timeStep) { void DynamicCharacterController::postSimulation() { if (_enabled && _rigidBody) { + _avatarData->avatarLock.lockForWrite(); const btTransform& avatarTransform = _rigidBody->getWorldTransform(); glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); glm::vec3 position = bulletToGLM(avatarTransform.getOrigin()); @@ -412,5 +413,6 @@ void DynamicCharacterController::postSimulation() { _avatarData->setOrientation(rotation); _avatarData->setPosition(position - rotation * _shapeLocalOffset); _avatarData->setVelocity(bulletToGLM(_rigidBody->getLinearVelocity())); + _avatarData->avatarLock.unlock(); } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c2d723a323..039cd803c7 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1285,7 +1285,9 @@ void Model::simulateInternal(float deltaTime) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; updateRig(deltaTime, parentTransform); - +} +void Model::updateClusterMatrices() { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 zeroScale(glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), @@ -1419,15 +1421,17 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { return AABox(); } + avatarLockForWriteIfApplicable(); if (meshIndex < _meshStates.size()) { const MeshState& state = _meshStates.at(meshIndex); bool isSkinned = state.clusterMatrices.size() > 1; if (isSkinned) { // if we're skinned return the entire mesh extents because we can't know for sure our clusters don't move us - return calculateScaledOffsetAABox(_geometry->getFBXGeometry().meshExtents); + AABox box = calculateScaledOffsetAABox(_geometry->getFBXGeometry().meshExtents); + avatarLockReleaseIfApplicable(); + return box; } } - if (_geometry->getFBXGeometry().meshes.size() > meshIndex) { // FIX ME! - This is currently a hack because for some mesh parts our efforts to calculate the bounding @@ -1443,8 +1447,11 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { // // If we not skinned use the bounds of the subMesh for all it's parts const FBXMesh& mesh = _geometry->getFBXGeometry().meshes.at(meshIndex); - return calculateScaledOffsetExtents(mesh.meshExtents); + AABox box = calculateScaledOffsetExtents(mesh.meshExtents); + avatarLockReleaseIfApplicable(); + return box; } + avatarLockReleaseIfApplicable(); return AABox(); } @@ -1482,6 +1489,9 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran return; } + avatarLockForWriteIfApplicable(); + updateClusterMatrices(); + const NetworkMesh& networkMesh = *(networkMeshes.at(meshIndex).get()); const FBXMesh& mesh = geometry.meshes.at(meshIndex); const MeshState& state = _meshStates.at(meshIndex); @@ -1536,6 +1546,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran _meshGroupsKnown = false; // regenerate these lists next time around. _readyWhenAdded = false; // in case any of our users are using scenes invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid + avatarLockReleaseIfApplicable(); return; // FIXME! } @@ -1543,6 +1554,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran int vertexCount = mesh.vertices.size(); if (vertexCount == 0) { // sanity check + avatarLockReleaseIfApplicable(); return; // FIXME! } @@ -1587,6 +1599,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran // guard against partially loaded meshes if (partIndex >= (int)networkMesh._parts.size() || partIndex >= mesh.parts.size()) { + avatarLockReleaseIfApplicable(); return; } @@ -1703,6 +1716,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran args->_details._trianglesRendered += part.triangleIndices.size() / INDICES_PER_TRIANGLE; args->_details._quadsRendered += part.quadIndices.size() / INDICES_PER_QUAD; } + avatarLockReleaseIfApplicable(); } void Model::segregateMeshGroups() { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index e55bff6aca..ca291450f7 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -113,6 +113,9 @@ public: bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); + void updateClusterMatrices(); + virtual void avatarLockForWriteIfApplicable() {}; + virtual void avatarLockReleaseIfApplicable() {}; /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } From 2429134b37d76a9e2a84eb5d8071c94ae0cfdf71 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 27 Aug 2015 13:44:19 -0700 Subject: [PATCH 08/55] Lock both myavatar activities, not one. --- interface/src/avatar/AvatarManager.cpp | 2 -- interface/src/avatar/AvatarUpdate.cpp | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index fefb774d66..d4885d651e 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -93,9 +93,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); - _myAvatar->avatarLock.lockForWrite(); _myAvatar->update(deltaTime); - _myAvatar->avatarLock.unlock(); quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 9c6f4a6f25..5f7999d61f 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -68,9 +68,11 @@ bool AvatarUpdate::process() { //gets current lookat data, removes missing avatars, etc. DependencyManager::get()->updateOtherAvatars(deltaSeconds); + Application::getInstance()->getMyAvatar()->avatarLock.lockForWrite(); Application::getInstance()->updateMyAvatarLookAtPosition(); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes DependencyManager::get()->updateMyAvatar(deltaSeconds); + Application::getInstance()->getMyAvatar()->avatarLock.unlock(); if (!isThreaded()) { return true; From c0fec44f3253b8770725b02bd439a085485120ae Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 3 Sep 2015 19:34:25 -0700 Subject: [PATCH 09/55] Menu item to switch between threaded and not (without restart!). --- interface/src/Application.cpp | 14 +++++++++++--- interface/src/Application.h | 3 ++- interface/src/Menu.cpp | 2 ++ interface/src/Menu.h | 1 + interface/src/avatar/AvatarUpdate.cpp | 13 +++---------- interface/src/avatar/AvatarUpdate.h | 2 -- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 74788224ef..578a637198 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2445,10 +2445,18 @@ void Application::init() { connect(tree, &EntityTree::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); connect(_myAvatar, &MyAvatar::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); - _avatarUpdate = new AvatarUpdate(); - _avatarUpdate->initialize(_avatarUpdate->isToBeThreaded()); + setAvatarUpdateThreading(Menu::getInstance()->isOptionChecked(MenuOption::EnableAvatarUpdateThreading)); } +void Application::setAvatarUpdateThreading(bool isThreaded) { + if (_avatarUpdate) { + _avatarUpdate->terminate(); + } + _avatarUpdate = new AvatarUpdate(); + _avatarUpdate->initialize(isThreaded); +} + + void Application::closeMirrorView() { if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { Menu::getInstance()->triggerOption(MenuOption::Mirror); @@ -2581,7 +2589,7 @@ void Application::updateMyAvatarLookAtPosition() { } else { // I am not looking at anyone else, so just look forward if (isHMD) { - glm::mat4 headPose = _avatarUpdate->getHeadPose(); + glm::mat4 headPose = _avatarUpdate->getHeadPose() ; glm::quat headRotation = glm::quat_cast(headPose); lookAtSpot = _myCamera.getPosition() + _myAvatar->getOrientation() * (headRotation * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); diff --git a/interface/src/Application.h b/interface/src/Application.h index 739ad8fee1..b6f807dec3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -412,6 +412,7 @@ public slots: void openUrl(const QUrl& url); void updateMyAvatarTransform(); + void setAvatarUpdateThreading(bool isThreaded); void domainSettingsReceived(const QJsonObject& domainSettingsObject); @@ -557,7 +558,7 @@ private: KeyboardMouseDevice* _keyboardMouseDevice{ nullptr }; // Default input device, the good old keyboard mouse and maybe touchpad MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be) - AvatarUpdate* _avatarUpdate; + AvatarUpdate* _avatarUpdate = nullptr; SimpleMovingAverage _avatarSimsPerSecond{10}; int _avatarSimsPerSecondReport = 0; quint64 _lastAvatarSimsPerSecondUpdate = 0; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 18015804f8..b012057757 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -431,6 +431,8 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAvatarUpdateThreading, 0, false, + qApp, SLOT(setAvatarUpdateThreading(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, false, avatar, SLOT(setEnableRigAnimations(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index f1fbb17895..476d6d20ae 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -185,6 +185,7 @@ namespace MenuOption { const QString EchoServerAudio = "Echo Server Audio"; const QString EditEntitiesHelp = "Edit Entities Help..."; const QString Enable3DTVMode = "Enable 3DTV Mode"; + const QString EnableAvatarUpdateThreading = "Enable Avatar Update Threading"; const QString EnableCharacterController = "Enable avatar collisions"; const QString EnableRigAnimations = "Enable Rig Animations"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 5f7999d61f..4121b95c82 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -24,17 +24,11 @@ // As it turns out, all the other subclasses of GenericThread (at this time) do not init // GenericThread(parent), so things work as expected. Here we explicitly init GenericThread(nullptr) // so that there is no confusion and no chance for a hillarious thread debugging session. -AvatarUpdate::AvatarUpdate() : GenericThread(nullptr), _lastAvatarUpdate(0)/*, _timer(nullptr), _thread(nullptr)FIXME remove*/ { +AvatarUpdate::AvatarUpdate() : GenericThread(nullptr), _lastAvatarUpdate(0) { setObjectName("Avatar Update"); // GenericThread::initialize uses this to set the thread name. Settings settings; const int DEFAULT_TARGET_AVATAR_SIMRATE = 60; _targetInterval = USECS_PER_SECOND / settings.value("AvatarUpdateTargetSimrate", DEFAULT_TARGET_AVATAR_SIMRATE).toInt(); - _isToBeThreaded = settings.value("AvatarUpdateIsThreaded", true).toBool(); - if (_isToBeThreaded) { - qCDebug(interfaceapp) << "AvatarUpdate threaded at" << _targetInterval << "microsecond interval."; - } else { - qCDebug(interfaceapp) << "AvatarUpdate unthreaded."; - } } // We could have the constructor call initialize(), but GenericThread::initialize can take parameters. // Keeping it separately called by the client allows that client to pass those without our @@ -50,10 +44,9 @@ void AvatarUpdate::synchronousProcess() { Application::getInstance()->getMyAvatar()->doUpdateBillboard(); } - if (isThreaded()) { - return; + if (!isThreaded()) { + process(); } - process(); } bool AvatarUpdate::process() { diff --git a/interface/src/avatar/AvatarUpdate.h b/interface/src/avatar/AvatarUpdate.h index 8e3448c533..08c90c0701 100644 --- a/interface/src/avatar/AvatarUpdate.h +++ b/interface/src/avatar/AvatarUpdate.h @@ -23,13 +23,11 @@ public: AvatarUpdate(); void synchronousProcess(); void setRequestBillboardUpdate(bool needsUpdate) { _updateBillboard = needsUpdate; } - bool isToBeThreaded() { return _isToBeThreaded; } private: virtual bool process(); // No reason for other classes to invoke this. quint64 _lastAvatarUpdate; // microsoeconds quint64 _targetInterval; // microseconds - bool _isToBeThreaded; bool _updateBillboard; // Goes away if Application::getActiveDisplayPlugin() and friends are made thread safe: From 0065c64b3143401de537fb83b9553c43d4a59c74 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 7 Sep 2015 09:57:05 -0700 Subject: [PATCH 10/55] Snapshot of no-judder, before cleanup. --- interface/src/Application.cpp | 7 ++- interface/src/Application.h | 2 +- interface/src/avatar/Avatar.cpp | 6 +-- interface/src/avatar/Avatar.h | 1 + interface/src/avatar/AvatarManager.cpp | 8 ++-- interface/src/avatar/AvatarUpdate.cpp | 17 ++++--- interface/src/avatar/AvatarUpdate.h | 2 +- interface/src/avatar/MyAvatar.cpp | 6 ++- interface/src/avatar/SkeletonModel.cpp | 14 ++++-- interface/src/avatar/SkeletonModel.h | 1 + libraries/avatars/src/AvatarData.cpp | 48 +++++++++++++++++++ libraries/avatars/src/AvatarData.h | 13 +++++ .../src/DynamicCharacterController.cpp | 5 +- libraries/render-utils/src/Model.cpp | 10 ++-- 14 files changed, 108 insertions(+), 32 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dc286dafae..92f64303c7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1019,6 +1019,8 @@ void Application::paintGL() { return; } _inPaint = true; + _myAvatar->captureAttitude(); + _myAvatar->startRender(); //FIXME Finally clearFlagLambda([this] { _inPaint = false; }); auto displayPlugin = getActiveDisplayPlugin(); @@ -1236,6 +1238,7 @@ void Application::paintGL() { gpu::Batch batch; batch.resetStages(); renderArgs._context->render(batch); + _myAvatar->endRender(); } void Application::runTests() { @@ -2152,7 +2155,7 @@ void Application::setAvatarSimrateSample(float sample) { } float Application::getAvatarSimrate() { uint64_t now = usecTimestampNow(); - + if (now - _lastAvatarSimsPerSecondUpdate > USECS_PER_SECOND) { _avatarSimsPerSecondReport = _avatarSimsPerSecond.getAverage(); _lastAvatarSimsPerSecondUpdate = now; @@ -2444,7 +2447,7 @@ void Application::init() { // Make sure any new sounds are loaded as soon as know about them. connect(tree, &EntityTree::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); connect(_myAvatar, &MyAvatar::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); - + setAvatarUpdateThreading(Menu::getInstance()->isOptionChecked(MenuOption::EnableAvatarUpdateThreading)); } diff --git a/interface/src/Application.h b/interface/src/Application.h index b6f807dec3..317832d9f5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -335,7 +335,7 @@ public: gpu::ContextPointer getGPUContext() const { return _gpuContext; } const QRect& getMirrorViewRect() const { return _mirrorViewRect; } - + void updateMyAvatarLookAtPosition(); AvatarUpdate* getAvatarUpdater() { return _avatarUpdate; } MyAvatar* getMyAvatar() { return _myAvatar; } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 67d60073fe..fbc940078e 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -316,7 +316,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrupdate(); } @@ -391,7 +391,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) { - avatarLock.unlock(); + //FIXME endRender(); return; } @@ -542,7 +542,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { if (!isMyAvatar() || cameraMode != CAMERA_MODE_FIRST_PERSON) { renderDisplayName(batch, *renderArgs->_viewFrustum, renderArgs->_viewport); } - avatarLock.unlock(); + //FIXME endRender(); } glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index a7cece2b45..6cfbf939a2 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -156,6 +156,7 @@ public: void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const; void slamPosition(const glm::vec3& position); + virtual void updateAttitude() { _skeletonModel.updateAttitude(); } // Call this when updating Avatar position with a delta. This will allow us to // _accurately_ measure position changes and compute the resulting velocity diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d4885d651e..67668a549d 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -129,9 +129,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { _avatarFades.push_back(avatarIterator.value()); avatarIterator = _avatarHash.erase(avatarIterator); } else { - avatar->avatarLock.lockForWrite(); + avatar->startUpdate(); avatar->simulate(deltaTime); - avatar->avatarLock.unlock(); + avatar->endUpdate(); ++avatarIterator; } } @@ -150,7 +150,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { render::PendingChanges pendingChanges; while (fadingIterator != _avatarFades.end()) { auto avatar = std::static_pointer_cast(*fadingIterator); - avatar->avatarLock.lockForWrite(); + avatar->startUpdate(); avatar->setTargetScale(avatar->getScale() * SHRINK_RATE, true); if (avatar->getTargetScale() < MIN_FADE_SCALE) { avatar->removeFromScene(*fadingIterator, scene, pendingChanges); @@ -159,7 +159,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { avatar->simulate(deltaTime); ++fadingIterator; } - avatar->avatarLock.unlock(); + avatar->endUpdate(); } scene->enqueuePendingChanges(pendingChanges); } diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 4121b95c82..c2240ef123 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -56,17 +56,20 @@ bool AvatarUpdate::process() { _lastAvatarUpdate = start; float deltaSeconds = deltaMicroseconds / (float) USECS_PER_SECOND; Application::getInstance()->setAvatarSimrateSample(1.0f / deltaSeconds); - + + QSharedPointer manager = DependencyManager::get(); + MyAvatar* myAvatar = manager->getMyAvatar(); + //loop through all the other avatars and simulate them... //gets current lookat data, removes missing avatars, etc. - DependencyManager::get()->updateOtherAvatars(deltaSeconds); - - Application::getInstance()->getMyAvatar()->avatarLock.lockForWrite(); + manager->updateOtherAvatars(deltaSeconds); + + myAvatar->startUpdate(); Application::getInstance()->updateMyAvatarLookAtPosition(); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes - DependencyManager::get()->updateMyAvatar(deltaSeconds); - Application::getInstance()->getMyAvatar()->avatarLock.unlock(); - + manager->updateMyAvatar(deltaSeconds); + myAvatar->endUpdate(); + if (!isThreaded()) { return true; } diff --git a/interface/src/avatar/AvatarUpdate.h b/interface/src/avatar/AvatarUpdate.h index 08c90c0701..27c88b6617 100644 --- a/interface/src/avatar/AvatarUpdate.h +++ b/interface/src/avatar/AvatarUpdate.h @@ -29,7 +29,7 @@ private: quint64 _lastAvatarUpdate; // microsoeconds quint64 _targetInterval; // microseconds bool _updateBillboard; - + // Goes away if Application::getActiveDisplayPlugin() and friends are made thread safe: public: bool isHMDMode() { return _isHMDMode; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f75aa5cba4..4e4043b28b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -192,6 +192,9 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("transform"); updateOrientation(deltaTime); updatePosition(deltaTime); + // The 2 updates set position, orientation, and all manner of physics stuff. + // Here we record the results. + nextAttitude(getPosition(), getOrientation()); } { @@ -266,8 +269,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { if (getStandingHMDSensorMode()) { // set the body position/orientation to reflect motion due to the head. auto worldMat = _sensorToWorldMatrix * _bodySensorMatrix; - setPosition(extractTranslation(worldMat)); - setOrientation(glm::quat_cast(worldMat)); + nextAttitude(extractTranslation(worldMat), glm::quat_cast(worldMat)); } } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 9bd6d35075..69aea5bb9d 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -40,10 +40,10 @@ SkeletonModel::~SkeletonModel() { } void SkeletonModel::avatarLockForWriteIfApplicable() { - _owningAvatar->avatarLock.lockForWrite(); + //FIXME _owningAvatar->avatarLock.lockForWrite(); } void SkeletonModel::avatarLockReleaseIfApplicable() { - _owningAvatar->avatarLock.unlock(); + //FIXME _owningAvatar->avatarLock.unlock(); } void SkeletonModel::initJointStates(QVector states) { @@ -154,13 +154,17 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } -// Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed), -// but just before head has been simulated. -void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { +void SkeletonModel::updateAttitude() { setTranslation(_owningAvatar->getSkeletonPosition()); static const glm::quat refOrientation = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); setRotation(_owningAvatar->getOrientation() * refOrientation); setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale()); +} + +// Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed), +// but just before head has been simulated. +void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { + updateAttitude(); setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients()); Model::simulate(deltaTime, fullUpdate); diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index d3bf850c53..1481619a04 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -31,6 +31,7 @@ public: virtual void simulate(float deltaTime, bool fullUpdate = true); virtual void updateRig(float deltaTime, glm::mat4 parentTransform); + void updateAttitude(); void renderIKConstraints(gpu::Batch& batch); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 46a323733a..9d7f974a68 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -110,6 +110,54 @@ void AvatarData::setOrientation(const glm::quat& orientation, bool overideRefere } } +// There are a number of possible strategies, some more optimal than others in terms of using the latest info +// The current one does not update anything until captureAttitude, and then keeps that value until rendered. +void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) { + setPosition(position, true); setOrientation(orientation, true); + _nextPending = 1; // FIXME type bool +} +void AvatarData::captureAttitude() { + if (!_nextAllowed) { // We haven't finished rendering the last one + return; + } + avatarLock.lockForWrite(); + if (_nextPending) { + _nextAllowed = false; + _nextPosition = getPosition(); + _nextOrientation = getOrientation(); + } else { + qCDebug(avatars) << "FIXME capture with nothing pending"; + } + avatarLock.unlock(); +} +void AvatarData::startUpdate() { + avatarLock.lockForWrite(); +} +void AvatarData::endUpdate() { + avatarLock.unlock(); +} +void AvatarData::startRender() { + avatarLock.lockForRead(); + if (!_nextPending) { + return; + } + glm::vec3 pos = getPosition(); + glm::quat rot = getOrientation(); + setPosition(_nextPosition, true); + //setOrientation(_nextOrientation, true); + updateAttitude(); + _nextPosition = pos; + _nextOrientation = rot; +} +void AvatarData::endRender() { + setPosition(_nextPosition, true); + //setOrientation(_nextOrientation, true); + updateAttitude(); + _nextPending = 0; + _nextAllowed = true; + avatarLock.unlock(); +} + float AvatarData::getTargetScale() const { if (_referential) { _referential->update(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index d4da9487f7..41d8cba997 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -198,6 +198,14 @@ public: glm::quat getOrientation() const; virtual void setOrientation(const glm::quat& orientation, bool overideReferential = false); + void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. + void captureAttitude(); // Indicates that the latest values are about to be captured for camera, etc. + void startUpdate(); // start/end of update iteration + void endUpdate(); + void startRender(); // start/end of rendering + void endRender(); + virtual void updateAttitude() {} // Tell skeleton mesh about changes + glm::quat getHeadOrientation() const { return _headData->getOrientation(); } void setHeadOrientation(const glm::quat& orientation) { _headData->setOrientation(orientation); } @@ -358,6 +366,11 @@ protected: float _bodyPitch; // degrees float _bodyRoll; // degrees + glm::vec3 _nextPosition {}; + glm::quat _nextOrientation {}; + int _nextPending = 0; + bool _nextAllowed = true; + // Body scale float _targetScale; diff --git a/libraries/physics/src/DynamicCharacterController.cpp b/libraries/physics/src/DynamicCharacterController.cpp index 7c1455c69e..163cba6aae 100644 --- a/libraries/physics/src/DynamicCharacterController.cpp +++ b/libraries/physics/src/DynamicCharacterController.cpp @@ -405,14 +405,11 @@ void DynamicCharacterController::preSimulation(btScalar timeStep) { void DynamicCharacterController::postSimulation() { if (_enabled && _rigidBody) { - _avatarData->avatarLock.lockForWrite(); const btTransform& avatarTransform = _rigidBody->getWorldTransform(); glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); glm::vec3 position = bulletToGLM(avatarTransform.getOrigin()); - _avatarData->setOrientation(rotation); - _avatarData->setPosition(position - rotation * _shapeLocalOffset); + _avatarData->nextAttitude(position - rotation * _shapeLocalOffset, rotation); _avatarData->setVelocity(bulletToGLM(_rigidBody->getLinearVelocity())); - _avatarData->avatarLock.unlock(); } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 0a785dfa3c..99a0c81d4b 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1310,7 +1310,7 @@ void Model::updateClusterMatrices() { if (_showTrueJointTransforms) { for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); - auto jointMatrix =_rig->getJointTransform(cluster.jointIndex); + auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix; // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. @@ -1324,7 +1324,7 @@ void Model::updateClusterMatrices() { } else { for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); - auto jointMatrix = _rig->getJointVisibleTransform(cluster.jointIndex); + auto jointMatrix = _rig->getJointVisibleTransform(cluster.jointIndex); // differs from above only in using get...VisibleTransform state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix; // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. @@ -1472,6 +1472,10 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran return; // bail asap } + avatarLockForWriteIfApplicable(); + if (!_calculatedMeshPartOffsetValid) + qCDebug(renderutils) << "FIXME surprise!"; + _calculatedMeshPartOffsetValid = false; // FIXME // We need to make sure we have valid offsets calculated before we can render if (!_calculatedMeshPartOffsetValid) { _mutex.lock(); @@ -1496,10 +1500,10 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran // guard against partially loaded meshes if (meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size() ) { + avatarLockReleaseIfApplicable(); return; } - avatarLockForWriteIfApplicable(); updateClusterMatrices(); const NetworkMesh& networkMesh = *(networkMeshes.at(meshIndex).get()); From efeaf213050e0be79e441099cbf167daf7608491 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 7 Sep 2015 16:32:51 -0700 Subject: [PATCH 11/55] Checkpoint smoother. --- interface/src/Application.cpp | 19 +++++-- interface/src/avatar/Avatar.cpp | 10 ++-- interface/src/avatar/MyAvatar.cpp | 3 -- libraries/avatars/src/AvatarData.cpp | 78 ++++++++++++++++++---------- libraries/avatars/src/AvatarData.h | 7 ++- libraries/render-utils/src/Model.cpp | 3 -- 6 files changed, 76 insertions(+), 44 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 92f64303c7..eeb6563bb4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1019,8 +1019,6 @@ void Application::paintGL() { return; } _inPaint = true; - _myAvatar->captureAttitude(); - _myAvatar->startRender(); //FIXME Finally clearFlagLambda([this] { _inPaint = false; }); auto displayPlugin = getActiveDisplayPlugin(); @@ -1028,6 +1026,8 @@ void Application::paintGL() { _offscreenContext->makeCurrent(); // update the avatar with a fresh HMD pose _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose()); + //_myAvatar->captureAttitude(); //FIXME + //_myAvatar->startRender(); //FIXME auto lodManager = DependencyManager::get(); @@ -1046,7 +1046,7 @@ void Application::paintGL() { // Before anything else, let's sync up the gpuContext with the true glcontext used in case anything happened renderArgs._context->syncCache(); - + if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { auto primaryFbo = DependencyManager::get()->getPrimaryFramebufferDepthColor(); @@ -1079,6 +1079,7 @@ void Application::paintGL() { _applicationOverlay.renderOverlay(&renderArgs); } + _myAvatar->startCapture(); if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, _myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN); Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(_myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN)); @@ -1128,7 +1129,7 @@ void Application::paintGL() { if (!isHMDMode()) { _myCamera.update(1.0f / _fps); } - + _myAvatar->endCapture(); // Primary rendering pass auto framebufferCache = DependencyManager::get(); @@ -1237,8 +1238,9 @@ void Application::paintGL() { // Back to the default framebuffer; gpu::Batch batch; batch.resetStages(); + //_myAvatar->startRender(); //FIXME renderArgs._context->render(batch); - _myAvatar->endRender(); + //_myAvatar->endRender(); //FIXME } void Application::runTests() { @@ -3473,7 +3475,10 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se // FIXME: This preRender call is temporary until we create a separate render::scene for the mirror rendering. // Then we can move this logic into the Avatar::simulate call. + _myAvatar->startRender(); //FIXME _myAvatar->preRender(renderArgs); + _myAvatar->endRender(); //FIXME + activeRenderingThread = QThread::currentThread(); PROFILE_RANGE(__FUNCTION__); @@ -3587,7 +3592,9 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se _renderEngine->setRenderContext(renderContext); // Before the deferred pass, let's try to use the render engine + _myAvatar->startRenderRun(); //FIXME _renderEngine->run(); + _myAvatar->endRenderRun(); //FIXME auto engineRC = _renderEngine->getRenderContext(); sceneInterface->setEngineFeedOpaqueItems(engineRC->_numFeedOpaqueItems); @@ -3619,6 +3626,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi float fov = MIRROR_FIELD_OF_VIEW; // bool eyeRelativeCamera = false; + _myAvatar->startRenderCam(); //FIXME if (billboard) { fov = BILLBOARD_FIELD_OF_VIEW; // degees _mirrorCamera.setPosition(_myAvatar->getPosition() + @@ -3664,6 +3672,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi viewport = gpu::Vec4i(0, 0, width, height); } renderArgs->_viewport = viewport; + _myAvatar->endRenderCam(); //FIXME // render rear mirror view displaySide(renderArgs, _mirrorCamera, true, billboard); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index fbc940078e..913afbb2af 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -316,7 +316,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrupdate(); } @@ -391,7 +391,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) { - //FIXME endRender(); + endRenderAv(); //FIXME return; } @@ -542,7 +542,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { if (!isMyAvatar() || cameraMode != CAMERA_MODE_FIRST_PERSON) { renderDisplayName(batch, *renderArgs->_viewFrustum, renderArgs->_viewport); } - //FIXME endRender(); + endRenderAv(); //FIXME } glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { @@ -1022,7 +1022,7 @@ void Avatar::setBillboard(const QByteArray& billboard) { } int Avatar::parseDataFromBuffer(const QByteArray& buffer) { - avatarLock.lockForWrite(); + startUpdate(); if (!_initialized) { // now that we have data for this Avatar we are go for init init(); @@ -1038,7 +1038,7 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) { if (_moving && _motionState) { _motionState->addDirtyFlags(EntityItem::DIRTY_POSITION); } - avatarLock.unlock(); + endUpdate(); return bytesRead; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4e4043b28b..cc850eab9e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -192,9 +192,6 @@ void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("transform"); updateOrientation(deltaTime); updatePosition(deltaTime); - // The 2 updates set position, orientation, and all manner of physics stuff. - // Here we record the results. - nextAttitude(getPosition(), getOrientation()); } { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9d7f974a68..db8837fb79 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -110,52 +110,78 @@ void AvatarData::setOrientation(const glm::quat& orientation, bool overideRefere } } -// There are a number of possible strategies, some more optimal than others in terms of using the latest info -// The current one does not update anything until captureAttitude, and then keeps that value until rendered. +// There are a number of possible strategies for this set of tools through endRender, below. void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) { - setPosition(position, true); setOrientation(orientation, true); - _nextPending = 1; // FIXME type bool -} -void AvatarData::captureAttitude() { - if (!_nextAllowed) { // We haven't finished rendering the last one - return; - } - avatarLock.lockForWrite(); - if (_nextPending) { - _nextAllowed = false; - _nextPosition = getPosition(); - _nextOrientation = getOrientation(); - } else { - qCDebug(avatars) << "FIXME capture with nothing pending"; - } + avatarLock.lock(); + setPosition(position, true); + setOrientation(orientation, true); avatarLock.unlock(); } +void AvatarData::captureAttitude() { + avatarLock.lock(); + assert(_nextAllowed); + _nextAllowed = false; + _nextPosition = getPosition(); + _nextOrientation = getOrientation(); + avatarLock.unlock(); +} +void AvatarData::startCapture() { + avatarLock.lock(); + assert(_nextAllowed); + _nextAllowed = false; + _nextPosition = getPosition(); + _nextOrientation = getOrientation(); +} +void AvatarData::endCapture() { + avatarLock.unlock(); +} + void AvatarData::startUpdate() { - avatarLock.lockForWrite(); + avatarLock.lock(); } void AvatarData::endUpdate() { avatarLock.unlock(); } +void AvatarData::startRenderRun() { + _nextPending = true; // FIXME remove here and in .h + //startRender(); // when on: smooth when startRenderCam off; mini-mirror judder (only, both axes) when startRenderCam on + avatarLock.lock(); +} +void AvatarData::endRenderRun() { + _nextPending = false; // FIXME remove here and in .h + //endRender(); + avatarLock.unlock(); +} +void AvatarData::startRenderAv() { + startRender(); // when on: small rotate judder in all views when starRenderCam off; big rotate judder in all views (and mini-mirror forward judder) when startRenderCam on +} +void AvatarData::endRenderAv() { + endRender(); +} +void AvatarData::startRenderCam() { + //startRender(); +} +void AvatarData::endRenderCam() { + //endRender(); +} void AvatarData::startRender() { - avatarLock.lockForRead(); - if (!_nextPending) { - return; - } + //avatarLock.lock(); + _nextPending = true; // FIXME remove here and in .h glm::vec3 pos = getPosition(); glm::quat rot = getOrientation(); setPosition(_nextPosition, true); - //setOrientation(_nextOrientation, true); + setOrientation(_nextOrientation, true); updateAttitude(); _nextPosition = pos; _nextOrientation = rot; } void AvatarData::endRender() { + _nextPending = false; // FIXME remove here and in .h setPosition(_nextPosition, true); - //setOrientation(_nextOrientation, true); + setOrientation(_nextOrientation, true); updateAttitude(); - _nextPending = 0; _nextAllowed = true; - avatarLock.unlock(); + //avatarLock.unlock(); } float AvatarData::getTargetScale() const { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 41d8cba997..2b557e79b1 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -200,9 +200,12 @@ public: void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. void captureAttitude(); // Indicates that the latest values are about to be captured for camera, etc. + void startCapture(); // start/end of the period in which the latest values are about to be captured for camera, etc. + void endCapture(); void startUpdate(); // start/end of update iteration void endUpdate(); void startRender(); // start/end of rendering + void startRenderRun(); void endRenderRun(); void startRenderAv(); void endRenderAv(); void startRenderCam(); void endRenderCam(); void endRender(); virtual void updateAttitude() {} // Tell skeleton mesh about changes @@ -319,7 +322,7 @@ public: bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } - QReadWriteLock avatarLock; // Name is redundant, but it aids searches. + QMutex avatarLock; // Name is redundant, but it aids searches. public slots: void sendAvatarDataPacket(); @@ -368,7 +371,7 @@ protected: glm::vec3 _nextPosition {}; glm::quat _nextOrientation {}; - int _nextPending = 0; + bool _nextPending = false; bool _nextAllowed = true; // Body scale diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 99a0c81d4b..438bed437a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1473,9 +1473,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran } avatarLockForWriteIfApplicable(); - if (!_calculatedMeshPartOffsetValid) - qCDebug(renderutils) << "FIXME surprise!"; - _calculatedMeshPartOffsetValid = false; // FIXME // We need to make sure we have valid offsets calculated before we can render if (!_calculatedMeshPartOffsetValid) { _mutex.lock(); From d472fd66ffb5a95b1b8cb121c69e9c02d223389d Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 7 Sep 2015 17:26:22 -0700 Subject: [PATCH 12/55] Cleanup. --- interface/src/Application.cpp | 2 -- interface/src/avatar/Avatar.cpp | 6 +++--- libraries/avatars/src/AvatarData.cpp | 23 ++--------------------- libraries/avatars/src/AvatarData.h | 6 +++--- 4 files changed, 8 insertions(+), 29 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index eeb6563bb4..ada1260023 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3626,7 +3626,6 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi float fov = MIRROR_FIELD_OF_VIEW; // bool eyeRelativeCamera = false; - _myAvatar->startRenderCam(); //FIXME if (billboard) { fov = BILLBOARD_FIELD_OF_VIEW; // degees _mirrorCamera.setPosition(_myAvatar->getPosition() + @@ -3672,7 +3671,6 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi viewport = gpu::Vec4i(0, 0, width, height); } renderArgs->_viewport = viewport; - _myAvatar->endRenderCam(); //FIXME // render rear mirror view displaySide(renderArgs, _mirrorCamera, true, billboard); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 913afbb2af..086178ec6c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -316,7 +316,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrupdate(); } @@ -391,7 +391,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) { - endRenderAv(); //FIXME + endRender(); return; } @@ -542,7 +542,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { if (!isMyAvatar() || cameraMode != CAMERA_MODE_FIRST_PERSON) { renderDisplayName(batch, *renderArgs->_viewFrustum, renderArgs->_viewport); } - endRenderAv(); //FIXME + endRender(); } glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index db8837fb79..2bde964ff2 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -135,7 +135,6 @@ void AvatarData::startCapture() { void AvatarData::endCapture() { avatarLock.unlock(); } - void AvatarData::startUpdate() { avatarLock.lock(); } @@ -143,30 +142,14 @@ void AvatarData::endUpdate() { avatarLock.unlock(); } void AvatarData::startRenderRun() { - _nextPending = true; // FIXME remove here and in .h - //startRender(); // when on: smooth when startRenderCam off; mini-mirror judder (only, both axes) when startRenderCam on + // I'd like to get rid of this and just (un)lock at (end-)startRender. + // But somehow that causes judder in rotations. avatarLock.lock(); } void AvatarData::endRenderRun() { - _nextPending = false; // FIXME remove here and in .h - //endRender(); avatarLock.unlock(); } -void AvatarData::startRenderAv() { - startRender(); // when on: small rotate judder in all views when starRenderCam off; big rotate judder in all views (and mini-mirror forward judder) when startRenderCam on -} -void AvatarData::endRenderAv() { - endRender(); -} -void AvatarData::startRenderCam() { - //startRender(); -} -void AvatarData::endRenderCam() { - //endRender(); -} void AvatarData::startRender() { - //avatarLock.lock(); - _nextPending = true; // FIXME remove here and in .h glm::vec3 pos = getPosition(); glm::quat rot = getOrientation(); setPosition(_nextPosition, true); @@ -176,12 +159,10 @@ void AvatarData::startRender() { _nextOrientation = rot; } void AvatarData::endRender() { - _nextPending = false; // FIXME remove here and in .h setPosition(_nextPosition, true); setOrientation(_nextOrientation, true); updateAttitude(); _nextAllowed = true; - //avatarLock.unlock(); } float AvatarData::getTargetScale() const { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2b557e79b1..15be97c22e 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -204,8 +204,9 @@ public: void endCapture(); void startUpdate(); // start/end of update iteration void endUpdate(); - void startRender(); // start/end of rendering - void startRenderRun(); void endRenderRun(); void startRenderAv(); void endRenderAv(); void startRenderCam(); void endRenderCam(); + void startRender(); // start/end of rendering of this object + void startRenderRun(); // start/end of entire scene. + void endRenderRun(); void endRender(); virtual void updateAttitude() {} // Tell skeleton mesh about changes @@ -371,7 +372,6 @@ protected: glm::vec3 _nextPosition {}; glm::quat _nextOrientation {}; - bool _nextPending = false; bool _nextAllowed = true; // Body scale From 27f4bca0a483316e72fdc3a4915455fc860c8921 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 8 Sep 2015 14:49:58 -0700 Subject: [PATCH 13/55] Cleanup: This appears to be complete and working -- EXCEPT for loading animations on the update thread! Until I figure that out, the answer is to turn off Developer->Avatar->"Enable Avatar Update Threading", run through forward/back/left/right/strafeLeft/strafeRight, and then turn "Enable Avatar Update Threading" back on. --- interface/src/Application.cpp | 12 ++++-------- interface/src/avatar/SkeletonModel.cpp | 7 ------- interface/src/avatar/SkeletonModel.h | 2 -- libraries/avatars/src/AvatarData.cpp | 8 -------- libraries/avatars/src/AvatarData.h | 1 - libraries/render-utils/src/Model.cpp | 16 ++-------------- libraries/render-utils/src/Model.h | 2 -- 7 files changed, 6 insertions(+), 42 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ada1260023..7da07ff64c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1026,8 +1026,6 @@ void Application::paintGL() { _offscreenContext->makeCurrent(); // update the avatar with a fresh HMD pose _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose()); - //_myAvatar->captureAttitude(); //FIXME - //_myAvatar->startRender(); //FIXME auto lodManager = DependencyManager::get(); @@ -1238,9 +1236,7 @@ void Application::paintGL() { // Back to the default framebuffer; gpu::Batch batch; batch.resetStages(); - //_myAvatar->startRender(); //FIXME renderArgs._context->render(batch); - //_myAvatar->endRender(); //FIXME } void Application::runTests() { @@ -3475,9 +3471,9 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se // FIXME: This preRender call is temporary until we create a separate render::scene for the mirror rendering. // Then we can move this logic into the Avatar::simulate call. - _myAvatar->startRender(); //FIXME + _myAvatar->startRender(); _myAvatar->preRender(renderArgs); - _myAvatar->endRender(); //FIXME + _myAvatar->endRender(); activeRenderingThread = QThread::currentThread(); @@ -3592,9 +3588,9 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se _renderEngine->setRenderContext(renderContext); // Before the deferred pass, let's try to use the render engine - _myAvatar->startRenderRun(); //FIXME + _myAvatar->startRenderRun(); _renderEngine->run(); - _myAvatar->endRenderRun(); //FIXME + _myAvatar->endRenderRun(); auto engineRC = _renderEngine->getRenderContext(); sceneInterface->setEngineFeedOpaqueItems(engineRC->_numFeedOpaqueItems); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 69aea5bb9d..7b5979e62f 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -39,13 +39,6 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer r SkeletonModel::~SkeletonModel() { } -void SkeletonModel::avatarLockForWriteIfApplicable() { - //FIXME _owningAvatar->avatarLock.lockForWrite(); -} -void SkeletonModel::avatarLockReleaseIfApplicable() { - //FIXME _owningAvatar->avatarLock.unlock(); -} - void SkeletonModel::initJointStates(QVector states) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 rootTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset; diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 1481619a04..fc99f22ee9 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -105,8 +105,6 @@ public: float getHeadClipDistance() const { return _headClipDistance; } virtual void onInvalidate() override; - virtual void avatarLockForWriteIfApplicable() override; - virtual void avatarLockReleaseIfApplicable() override; void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2bde964ff2..190214017b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -117,14 +117,6 @@ void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) { setOrientation(orientation, true); avatarLock.unlock(); } -void AvatarData::captureAttitude() { - avatarLock.lock(); - assert(_nextAllowed); - _nextAllowed = false; - _nextPosition = getPosition(); - _nextOrientation = getOrientation(); - avatarLock.unlock(); -} void AvatarData::startCapture() { avatarLock.lock(); assert(_nextAllowed); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 15be97c22e..c4adae29e2 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -199,7 +199,6 @@ public: virtual void setOrientation(const glm::quat& orientation, bool overideReferential = false); void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. - void captureAttitude(); // Indicates that the latest values are about to be captured for camera, etc. void startCapture(); // start/end of the period in which the latest values are about to be captured for camera, etc. void endCapture(); void startUpdate(); // start/end of update iteration diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 438bed437a..4c56556118 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1431,15 +1431,12 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { return AABox(); } - avatarLockForWriteIfApplicable(); if (meshIndex < _meshStates.size()) { const MeshState& state = _meshStates.at(meshIndex); bool isSkinned = state.clusterMatrices.size() > 1; if (isSkinned) { // if we're skinned return the entire mesh extents because we can't know for sure our clusters don't move us - AABox box = calculateScaledOffsetAABox(_geometry->getFBXGeometry().meshExtents); - avatarLockReleaseIfApplicable(); - return box; + return calculateScaledOffsetAABox(_geometry->getFBXGeometry().meshExtents); } } if (_geometry->getFBXGeometry().meshes.size() > meshIndex) { @@ -1457,11 +1454,8 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { // // If we not skinned use the bounds of the subMesh for all it's parts const FBXMesh& mesh = _geometry->getFBXGeometry().meshes.at(meshIndex); - AABox box = calculateScaledOffsetExtents(mesh.meshExtents); - avatarLockReleaseIfApplicable(); - return box; + return calculateScaledOffsetExtents(mesh.meshExtents); } - avatarLockReleaseIfApplicable(); return AABox(); } @@ -1472,7 +1466,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran return; // bail asap } - avatarLockForWriteIfApplicable(); // We need to make sure we have valid offsets calculated before we can render if (!_calculatedMeshPartOffsetValid) { _mutex.lock(); @@ -1497,7 +1490,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran // guard against partially loaded meshes if (meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size() ) { - avatarLockReleaseIfApplicable(); return; } @@ -1557,7 +1549,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran _meshGroupsKnown = false; // regenerate these lists next time around. _readyWhenAdded = false; // in case any of our users are using scenes invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid - avatarLockReleaseIfApplicable(); return; // FIXME! } @@ -1565,7 +1556,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran int vertexCount = mesh.vertices.size(); if (vertexCount == 0) { // sanity check - avatarLockReleaseIfApplicable(); return; // FIXME! } @@ -1610,7 +1600,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran // guard against partially loaded meshes if (partIndex >= (int)networkMesh._parts.size() || partIndex >= mesh.parts.size()) { - avatarLockReleaseIfApplicable(); return; } @@ -1727,7 +1716,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran args->_details._trianglesRendered += part.triangleIndices.size() / INDICES_PER_TRIANGLE; args->_details._quadsRendered += part.quadIndices.size() / INDICES_PER_QUAD; } - avatarLockReleaseIfApplicable(); } void Model::segregateMeshGroups() { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index d736cc972f..91b6b4c83b 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -113,8 +113,6 @@ public: virtual void simulate(float deltaTime, bool fullUpdate = true); void updateClusterMatrices(); - virtual void avatarLockForWriteIfApplicable() {}; - virtual void avatarLockReleaseIfApplicable() {}; /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } From 90fe54e6d69af236b7eee13e0e5a14d2018e100c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 15:01:33 -0700 Subject: [PATCH 14/55] remove cruft and minor cleanup --- libraries/animation/src/Rig.cpp | 11 ++--------- libraries/animation/src/Rig.h | 1 - 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index f2ea922ab7..69334f9310 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -477,7 +477,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } if (glm::length(localVel) > moveThresh) { - if (fabs(forwardSpeed) > 0.5f * fabs(lateralSpeed)) { + if (fabsf(forwardSpeed) > 0.5f * fabsf(lateralSpeed)) { if (forwardSpeed > 0.0f) { // forward _animVars.set("isMovingForward", true); @@ -501,7 +501,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } _state = RigRole::Move; } else { - if (fabs(turningSpeed) > turnThresh) { + if (fabsf(turningSpeed) > turnThresh) { if (turningSpeed > 0.0f) { // turning right _animVars.set("isTurningRight", true); @@ -907,13 +907,6 @@ void Rig::updateVisibleJointStates() { } } -void Rig::setJointTransform(int jointIndex, glm::mat4 newTransform) { - if (jointIndex == -1 || jointIndex >= _jointStates.size()) { - return; - } - _jointStates[jointIndex].setTransform(newTransform); -} - void Rig::setJointVisibleTransform(int jointIndex, glm::mat4 newTransform) { if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 0bf0645b4d..387a31c626 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -133,7 +133,6 @@ public: glm::vec3 translation, glm::quat rotation) const; bool getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& result, glm::quat rotation) const; glm::mat4 getJointTransform(int jointIndex) const; - void setJointTransform(int jointIndex, glm::mat4 newTransform); glm::mat4 getJointVisibleTransform(int jointIndex) const; void setJointVisibleTransform(int jointIndex, glm::mat4 newTransform); // Start or stop animations as needed. From 281e4f21fc33a67e784bf5b2fccb0706c5296b33 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 15:01:51 -0700 Subject: [PATCH 15/55] fix avatar bounding capsule calculations --- interface/src/avatar/SkeletonModel.cpp | 18 ++- libraries/animation/src/JointState.cpp | 4 +- libraries/animation/src/JointState.h | 4 +- libraries/fbx/src/FBXReader.cpp | 168 +++++++++++-------------- libraries/fbx/src/FBXReader.h | 10 +- libraries/fbx/src/OBJReader.cpp | 2 - 6 files changed, 98 insertions(+), 108 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 5ffd0f8dec..6590b5936a 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -544,14 +544,20 @@ void SkeletonModel::computeBoundingShape() { totalExtents.addPoint(glm::vec3(0.0f)); int numStates = _rig->getJointStateCount(); for (int i = 0; i < numStates; i++) { - // compute the default transform of this joint const JointState& state = _rig->getJointState(i); - // Each joint contributes a sphere at its position - glm::vec3 axis(state.getBoneRadius()); - glm::vec3 jointPosition = state.getPosition(); - totalExtents.addPoint(jointPosition + axis); - totalExtents.addPoint(jointPosition - axis); + const glm::mat4& jointTransform = state.getTransform(); + float scale = extractUniformScale(jointTransform); + + // Each joint contributes a capsule defined by FBXJoint.shapeInfo. + // For totalExtents we use the capsule endpoints expanded by the radius. + const FBXJointShapeInfo& shapeInfo = geometry.joints.at(i).shapeInfo; + for (int j = 0; j < shapeInfo.points.size(); ++j) { + glm::vec3 transformedPoint = extractTranslation(jointTransform * glm::translate(shapeInfo.points[j])); + vec3 radius(scale * shapeInfo.radius); + totalExtents.addPoint(transformedPoint + radius); + totalExtents.addPoint(transformedPoint - radius); + } } // compute bounding shape parameters diff --git a/libraries/animation/src/JointState.cpp b/libraries/animation/src/JointState.cpp index edea3b462d..7493e95084 100644 --- a/libraries/animation/src/JointState.cpp +++ b/libraries/animation/src/JointState.cpp @@ -41,7 +41,7 @@ void JointState::copyState(const JointState& other) { // DO NOT copy _constraint _name = other._name; _isFree = other._isFree; - _boneRadius = other._boneRadius; +// _boneRadius = other._boneRadius; _parentIndex = other._parentIndex; _defaultRotation = other._defaultRotation; _inverseDefaultRotation = other._inverseDefaultRotation; @@ -58,7 +58,7 @@ JointState::JointState(const FBXJoint& joint) { _rotationInConstrainedFrame = joint.rotation; _name = joint.name; _isFree = joint.isFree; - _boneRadius = joint.boneRadius; +// _boneRadius = joint.boneRadius; _parentIndex = joint.parentIndex; _translation = joint.translation; _defaultRotation = joint.rotation; diff --git a/libraries/animation/src/JointState.h b/libraries/animation/src/JointState.h index 4f45661eb2..426603bde1 100644 --- a/libraries/animation/src/JointState.h +++ b/libraries/animation/src/JointState.h @@ -118,7 +118,7 @@ public: const glm::quat& getDefaultRotation() const { return _defaultRotation; } const glm::quat& getInverseDefaultRotation() const { return _inverseDefaultRotation; } const QString& getName() const { return _name; } - float getBoneRadius() const { return _boneRadius; } +// float getBoneRadius() const { return _boneRadius; } bool getIsFree() const { return _isFree; } float getAnimationPriority() const { return _animationPriority; } void setAnimationPriority(float priority) { _animationPriority = priority; } @@ -149,7 +149,7 @@ private: QString _name; int _parentIndex; bool _isFree; - float _boneRadius; +// float _boneRadius; glm::vec3 _rotationMin; glm::vec3 _rotationMax; glm::quat _preRotation; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 35390a8e44..5410859340 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1257,21 +1257,7 @@ QString getString(const QVariant& value) { return list.isEmpty() ? value.toString() : list.at(0).toString(); } -class JointShapeInfo { -public: - JointShapeInfo() : numVertices(0), - sumVertexWeights(0.0f), sumWeightedRadii(0.0f), numVertexWeights(0), - boneBegin(0.0f), averageRadius(0.0f) { - } - - // NOTE: the points here are in the "joint frame" which has the "jointEnd" at the origin - int numVertices; // num vertices from contributing meshes - float sumVertexWeights; // sum of all vertex weights - float sumWeightedRadii; // sum of weighted vertices - int numVertexWeights; // num vertices that contributed to sums - glm::vec3 boneBegin; // parent joint location (in joint frame) - float averageRadius; -}; +typedef std::vector ShapeVertices; class AnimationCurve { public: @@ -2282,22 +2268,21 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping joint.postTransform = model.postTransform; joint.rotationMin = model.rotationMin; joint.rotationMax = model.rotationMax; - glm::quat combinedRotation = model.preRotation * model.rotation * model.postRotation; + glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; if (joint.parentIndex == -1) { - joint.transform = geometry.offset * glm::translate(model.translation) * model.preTransform * - glm::mat4_cast(combinedRotation) * model.postTransform; + joint.transform = geometry.offset * glm::translate(joint.translation) * joint.preTransform * + glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation); - joint.distanceToParent = 0.0f; + joint.distanceToParent = 0.0f; } else { const FBXJoint& parentJoint = geometry.joints.at(joint.parentIndex); - joint.transform = parentJoint.transform * glm::translate(model.translation) * - model.preTransform * glm::mat4_cast(combinedRotation) * model.postTransform; + joint.transform = parentJoint.transform * glm::translate(joint.translation) * + joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation) * parentJoint.inverseDefaultRotation; joint.distanceToParent = glm::distance(extractTranslation(parentJoint.transform), extractTranslation(joint.transform)); } - joint.boneRadius = 0.0f; joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = model.name; @@ -2326,9 +2311,10 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size())))); } } - // for each joint we allocate a JointShapeInfo in which we'll store collision shape info - QVector jointShapeInfos; - jointShapeInfos.resize(geometry.joints.size()); + + // NOTE: shapeVertices are in joint-frame + QVector shapeVertices; + shapeVertices.resize(geometry.joints.size()); // find our special joints geometry.leftEyeJointIndex = modelIDs.indexOf(jointEyeLeftID); @@ -2585,8 +2571,10 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping boneDirection /= boneLength; } } - float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix); - JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex]; + + float clusterScale = extractUniformScale(fbxCluster.inverseBindMatrix); + glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; + ShapeVertices& points = shapeVertices[jointIndex]; float totalWeight = 0.0f; for (int j = 0; j < cluster.indices.size(); j++) { @@ -2595,18 +2583,13 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping totalWeight += weight; for (QMultiHash::const_iterator it = extracted.newIndices.constFind(oldIndex); it != extracted.newIndices.end() && it.key() == oldIndex; it++) { - // expand the bone radius for vertices with at least 1/4 weight + + // remember vertices with at least 1/4 weight const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; if (weight > EXPANSION_WEIGHT_THRESHOLD) { - const glm::vec3& vertex = extracted.mesh.vertices.at(it.value()); - float proj = glm::dot(boneDirection, boneEnd - vertex); - float radiusWeight = (proj < 0.0f || proj > boneLength) ? 0.5f * weight : weight; - - jointShapeInfo.sumVertexWeights += radiusWeight; - jointShapeInfo.sumWeightedRadii += radiusWeight * radiusScale * glm::distance(vertex, boneEnd - boneDirection * proj); - ++jointShapeInfo.numVertexWeights; - - ++jointShapeInfo.numVertices; + // transform to joint-frame and save for later + const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(it.value())); + points.push_back(extractTranslation(vertexTransform) * clusterScale); } // look for an unused slot in the weights vector @@ -2649,54 +2632,16 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping // this is a single-mesh joint int jointIndex = maxJointIndex; FBXJoint& joint = geometry.joints[jointIndex]; - JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex]; - glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform; - glm::vec3 boneEnd = extractTranslation(transformJointToMesh); - glm::vec3 boneBegin = boneEnd; - - glm::vec3 boneDirection; - float boneLength = 0.0f; - if (joint.parentIndex != -1) { - boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform); - boneDirection = boneEnd - boneBegin; - boneLength = glm::length(boneDirection); - if (boneLength > EPSILON) { - boneDirection /= boneLength; - } - } - float radiusScale = extractUniformScale(joint.transform * firstFBXCluster.inverseBindMatrix); - - // compute average vertex - glm::vec3 averageVertex(0.0f); + // transform cluster vertices to joint-frame and save for later + float clusterScale = extractUniformScale(firstFBXCluster.inverseBindMatrix); + glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; + ShapeVertices& points = shapeVertices[jointIndex]; foreach (const glm::vec3& vertex, extracted.mesh.vertices) { - float proj = glm::dot(boneDirection, boneEnd - vertex); - float radiusWeight = (proj < 0.0f || proj > boneLength) ? 0.5f : 1.0f; - jointShapeInfo.sumVertexWeights += radiusWeight; - jointShapeInfo.sumWeightedRadii += radiusWeight * radiusScale * glm::distance(vertex, boneEnd - boneDirection * proj); - ++jointShapeInfo.numVertexWeights; - averageVertex += vertex; + const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertex); + points.push_back(extractTranslation(vertexTransform) * clusterScale); } - // compute joint's radius - int numVertices = extracted.mesh.vertices.size(); - jointShapeInfo.numVertices = numVertices; - if (numVertices > 0) { - // compute average radius - averageVertex /= (float)jointShapeInfo.numVertices; - float averageRadius = 0.0f; - foreach (const glm::vec3& vertex, extracted.mesh.vertices) { - averageRadius += glm::distance(vertex, averageVertex); - } - averageRadius *= radiusScale / (float)jointShapeInfo.numVertices; - - // final radius is minimum of average and weighted - float weightedRadius = jointShapeInfo.sumWeightedRadii / jointShapeInfo.sumVertexWeights; - jointShapeInfo.averageRadius = glm::min(weightedRadius, averageRadius); - } - - // clear sumVertexWeights (this flags it as a single-mesh joint for later) - jointShapeInfo.sumVertexWeights = 0.0f; } extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex); @@ -2721,24 +2666,59 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping glm::vec3 defaultCapsuleAxis(0.0f, 1.0f, 0.0f); for (int i = 0; i < geometry.joints.size(); ++i) { FBXJoint& joint = geometry.joints[i]; - JointShapeInfo& jointShapeInfo = jointShapeInfos[i]; - if (joint.parentIndex == -1) { - jointShapeInfo.boneBegin = glm::vec3(0.0f); + // NOTE: points are in joint-frame + // compute average point + ShapeVertices& points = shapeVertices[i]; + glm::vec3 avgPoint = glm::vec3(0.0f); + for (uint32_t j = 0; j < points.size(); ++j) { + avgPoint += points[j]; + } + avgPoint /= (float)points.size(); + + // compute axis from begin to avgPoint + glm::vec3 begin(0.0f); + glm::vec3 end = avgPoint; + glm::vec3 axis = end - begin; + float axisLength = glm::length(axis); + if (axisLength > EPSILON) { + axis /= axisLength; } else { - const FBXJoint& parentJoint = geometry.joints[joint.parentIndex]; - glm::quat inverseRotation = glm::inverse(extractRotation(joint.transform)); - jointShapeInfo.boneBegin = inverseRotation * (extractTranslation(parentJoint.transform) - extractTranslation(joint.transform)); + axis = glm::vec3(0.0f); } - if (jointShapeInfo.sumVertexWeights > 0.0f) { - // mutiple meshes contributed to the bone radius and now that all - // contributing meshes are done we can finally compute the boneRadius - joint.boneRadius = jointShapeInfo.sumWeightedRadii / jointShapeInfo.sumVertexWeights; - } else { - // single-mesh joint - joint.boneRadius = jointShapeInfo.averageRadius; + // measure average cylindrical radius + float avgRadius = 0.0f; + if (points.size() > 0) { + float minProjection = FLT_MAX; + float maxProjection = -FLT_MIN; + for (uint32_t j = 0; j < points.size(); ++j) { + glm::vec3 offset = points[j] - avgPoint; + float projection = glm::dot(offset, axis); + maxProjection = glm::max(maxProjection, projection); + minProjection = glm::min(minProjection, projection); + avgRadius += glm::length(offset - projection * axis); + } + avgRadius /= (float)points.size(); + + // compute endpoints of capsule in joint-frame + glm::vec3 capsuleBegin = avgPoint; + glm::vec3 capsuleEnd = avgPoint; + if (maxProjection - minProjection < 2.0f * avgRadius) { + // the mesh-as-cylinder approximation is too short to collide as a capsule + // so we'll collapse it to a sphere (although that isn't a very good approximation) + capsuleBegin = avgPoint + 0.5f * (maxProjection + minProjection) * axis; + capsuleEnd = capsuleBegin; + } else { + capsuleBegin = avgPoint + (minProjection + avgRadius) * axis; + capsuleEnd = avgPoint + (maxProjection - avgRadius) * axis; + } + + // save points for later + joint.shapeInfo.points.push_back(capsuleBegin); + joint.shapeInfo.points.push_back(capsuleEnd); } + joint.shapeInfo.radius = avgRadius; } geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 158b5581c6..cff22676c8 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -55,15 +55,21 @@ public: QVector normals; }; +struct FBXJointShapeInfo { + // same units and frame as FBXJoint.translation + QVector points; + float radius; +}; + /// A single joint (transformation node) extracted from an FBX document. class FBXJoint { public: - bool isFree; + FBXJointShapeInfo shapeInfo; QVector freeLineage; + bool isFree; int parentIndex; float distanceToParent; - float boneRadius; // http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/SDKRef/a00209.html diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 3eff3bdec5..b4fa000d47 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -444,7 +444,6 @@ FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, geometry.joints[0].isFree = false; geometry.joints[0].parentIndex = -1; geometry.joints[0].distanceToParent = 0; - geometry.joints[0].boneRadius = 0; geometry.joints[0].translation = glm::vec3(0, 0, 0); geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); @@ -620,7 +619,6 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << " freeLineage" << joint.freeLineage; qCDebug(modelformat) << " parentIndex" << joint.parentIndex; qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; - qCDebug(modelformat) << " boneRadius" << joint.boneRadius; qCDebug(modelformat) << " translation" << joint.translation; qCDebug(modelformat) << " preTransform" << joint.preTransform; qCDebug(modelformat) << " preRotation" << joint.preRotation; From 4a40781e2f961666d8f1b3b80deea552d29c59ae Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 15:11:19 -0700 Subject: [PATCH 16/55] remove some commented out cruft --- libraries/animation/src/JointState.cpp | 2 -- libraries/animation/src/JointState.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/libraries/animation/src/JointState.cpp b/libraries/animation/src/JointState.cpp index 7493e95084..9597a46726 100644 --- a/libraries/animation/src/JointState.cpp +++ b/libraries/animation/src/JointState.cpp @@ -41,7 +41,6 @@ void JointState::copyState(const JointState& other) { // DO NOT copy _constraint _name = other._name; _isFree = other._isFree; -// _boneRadius = other._boneRadius; _parentIndex = other._parentIndex; _defaultRotation = other._defaultRotation; _inverseDefaultRotation = other._inverseDefaultRotation; @@ -58,7 +57,6 @@ JointState::JointState(const FBXJoint& joint) { _rotationInConstrainedFrame = joint.rotation; _name = joint.name; _isFree = joint.isFree; -// _boneRadius = joint.boneRadius; _parentIndex = joint.parentIndex; _translation = joint.translation; _defaultRotation = joint.rotation; diff --git a/libraries/animation/src/JointState.h b/libraries/animation/src/JointState.h index 426603bde1..07ed010104 100644 --- a/libraries/animation/src/JointState.h +++ b/libraries/animation/src/JointState.h @@ -118,7 +118,6 @@ public: const glm::quat& getDefaultRotation() const { return _defaultRotation; } const glm::quat& getInverseDefaultRotation() const { return _inverseDefaultRotation; } const QString& getName() const { return _name; } -// float getBoneRadius() const { return _boneRadius; } bool getIsFree() const { return _isFree; } float getAnimationPriority() const { return _animationPriority; } void setAnimationPriority(float priority) { _animationPriority = priority; } @@ -149,7 +148,6 @@ private: QString _name; int _parentIndex; bool _isFree; -// float _boneRadius; glm::vec3 _rotationMin; glm::vec3 _rotationMax; glm::quat _preRotation; From 2c856e4b08b103cfb3e4529d96a000d2686d155a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 8 Sep 2015 16:22:06 -0700 Subject: [PATCH 17/55] Work around animation cache misbehavior wrt threads. --- interface/src/avatar/MyAvatar.cpp | 27 +++++++++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 28 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index cc850eab9e..adfde2936b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -704,19 +704,46 @@ float loadSetting(QSettings& settings, const char* name, float defaultValue) { return value; } +// Resource loading is not yet thread safe. If an animation is not loaded when requested by other than tha main thread, +// we block in AnimationHandle::setURL => AnimationCache::getAnimation. +// Meanwhile, the main thread will also eventually lock as it tries to render us. +// If we demand the animation from the update thread while we're locked, we'll deadlock. +// Until we untangle this, code puts the updates back on the main thread temporarilly and starts all the loading. +void MyAvatar::safelyLoadAnimations() { + qApp->setAvatarUpdateThreading(false); + _rig->addAnimationByRole("idle"); + _rig->addAnimationByRole("walk"); + _rig->addAnimationByRole("backup"); + _rig->addAnimationByRole("leftTurn"); + _rig->addAnimationByRole("rightTurn"); + _rig->addAnimationByRole("leftStrafe"); + _rig->addAnimationByRole("rightStrafe"); +} + void MyAvatar::setEnableRigAnimations(bool isEnabled) { + if (isEnabled) { + safelyLoadAnimations(); + } _rig->setEnableRig(isEnabled); if (!isEnabled) { _rig->deleteAnimations(); + } else if (Menu::getInstance()->isOptionChecked(MenuOption::EnableAvatarUpdateThreading)) { + qApp->setAvatarUpdateThreading(true); } } void MyAvatar::setEnableAnimGraph(bool isEnabled) { + if (isEnabled) { + safelyLoadAnimations(); + } _rig->setEnableAnimGraph(isEnabled); if (isEnabled) { if (_skeletonModel.readyToAddToScene()) { initAnimGraph(); } + if (Menu::getInstance()->isOptionChecked(MenuOption::EnableAvatarUpdateThreading)) { + qApp->setAvatarUpdateThreading(true); + } } else { destroyAnimGraph(); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ffa93ed13c..5fdfe000e3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -292,6 +292,7 @@ private: void initHeadBones(); void initAnimGraph(); void destroyAnimGraph(); + void safelyLoadAnimations(); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; From 387d50c21744e8c846e70aa19d65fd4b757c3090 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 8 Sep 2015 16:35:06 -0700 Subject: [PATCH 18/55] Added a flag to enable and disable lean. This should help improve our idle and walk animations, because animation on the "lean" joint was being lost, even when we did not require procedural leaning. --- interface/src/avatar/SkeletonModel.cpp | 5 +++-- libraries/animation/src/Rig.cpp | 4 +++- libraries/animation/src/Rig.h | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 5ffd0f8dec..7b0c1e8cbb 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -103,13 +103,15 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation()); } Model::updateRig(deltaTime, parentTransform); + Head* head = _owningAvatar->getHead(); if (_owningAvatar->isMyAvatar()) { + MyAvatar* myAvatar = static_cast(_owningAvatar); const FBXGeometry& geometry = _geometry->getFBXGeometry(); - Head* head = _owningAvatar->getHead(); Rig::HeadParameters params; params.modelRotation = getRotation(); params.modelTranslation = getTranslation(); + params.enableLean = qApp->isHMDMode() && !myAvatar->getStandingHMDSensorMode(); params.leanSideways = head->getFinalLeanSideways(); params.leanForward = head->getFinalLeanForward(); params.torsoTwist = head->getTorsoTwist(); @@ -133,7 +135,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // However, in the !isLookingAtMe case, the eyes aren't rotating the way they should right now. // We will revisit that as priorities allow, and particularly after the new rig/animation/joints. const FBXGeometry& geometry = _geometry->getFBXGeometry(); - Head* head = _owningAvatar->getHead(); // If the head is not positioned, updateEyeJoints won't get the math right glm::quat headOrientation; _rig->getJointRotation(geometry.headJointIndex, headOrientation); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index f2ea922ab7..ad24ed0c30 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -936,7 +936,9 @@ glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) { } void Rig::updateFromHeadParameters(const HeadParameters& params) { - updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist); + if (params.enableLean) { + updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist); + } updateNeckJoint(params.neckJointIndex, params.localHeadOrientation, params.leanSideways, params.leanForward, params.torsoTwist); updateEyeJoints(params.leftEyeJointIndex, params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 0bf0645b4d..39c2540ee3 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -57,6 +57,7 @@ public: float leanSideways = 0.0f; // degrees float leanForward = 0.0f; // degrees float torsoTwist = 0.0f; // degrees + bool enableLean = false; glm::quat modelRotation = glm::quat(); glm::quat localHeadOrientation = glm::quat(); glm::quat worldHeadOrientation = glm::quat(); From 614c0be59a0078d50cebcc5f04b241c3c80db0d1 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 8 Sep 2015 17:09:37 -0700 Subject: [PATCH 19/55] HACK so legless avatars don't drag their knuckles --- interface/src/avatar/SkeletonModel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 6590b5936a..df2d9818d1 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -558,6 +558,10 @@ void SkeletonModel::computeBoundingShape() { totalExtents.addPoint(transformedPoint + radius); totalExtents.addPoint(transformedPoint - radius); } + // HACK so that default legless robot doesn't knuckle-drag + if (shapeInfo.points.size() == 0 && (state.getName() == "LeftFoot" || state.getName() == "RightFoot")) { + totalExtents.addPoint(extractTranslation(jointTransform)); + } } // compute bounding shape parameters From 75a05ff15dca45ba3ebea7ca635d135a13530846 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 8 Sep 2015 20:52:18 -0700 Subject: [PATCH 20/55] Reduce logging spam in omniTools/magBalls --- examples/libraries/utils.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index 10694b11f5..1275975fd8 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -96,20 +96,44 @@ mergeObjects = function(proto, custom) { return result; } +LOG_WARN = 1; + logWarn = function(str) { - print(str); + if (LOG_WARN) { + print(str); + } } +LOG_ERROR = 1; + logError = function(str) { - print(str); + if (LOG_ERROR) { + print(str); + } } +LOG_INFO = 1; + logInfo = function(str) { - print(str); + if (LOG_INFO) { + print(str); + } } +LOG_DEBUG = 0; + logDebug = function(str) { - print(str); + if (LOG_DEBUG) { + print(str); + } +} + +LOG_TRACE = 0; + +logTrace = function(str) { + if (LOG_TRACE) { + print(str); + } } // Computes the penetration between a point and a sphere (centered at the origin) From b0329a58a650c032bf416edd96c91f9eb205f214 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 9 Sep 2015 09:29:31 -0700 Subject: [PATCH 21/55] lower volume --- examples/entityScripts/boombox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/entityScripts/boombox.js b/examples/entityScripts/boombox.js index d1d18ec615..cc0dc23a23 100644 --- a/examples/entityScripts/boombox.js +++ b/examples/entityScripts/boombox.js @@ -71,7 +71,7 @@ if (_this.injector == null) { _this.injector = Audio.playSound(_this.song, { position: props.position, // position of boombox entity - volume: 0.5, + volume: 0.1, loop: true }); } else { From 24f1387579cb5d61a8f9d4cec915229bdef328a8 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 10:34:08 -0700 Subject: [PATCH 22/55] Consistently use brace initialization in class headers. --- interface/src/Application.h | 8 ++++---- libraries/avatars/src/AvatarData.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index 10901b4644..6056323aa9 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -558,10 +558,10 @@ private: KeyboardMouseDevice* _keyboardMouseDevice{ nullptr }; // Default input device, the good old keyboard mouse and maybe touchpad MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be) - AvatarUpdate* _avatarUpdate = nullptr; - SimpleMovingAverage _avatarSimsPerSecond{10}; - int _avatarSimsPerSecondReport = 0; - quint64 _lastAvatarSimsPerSecondUpdate = 0; + AvatarUpdate* _avatarUpdate {nullptr}; + SimpleMovingAverage _avatarSimsPerSecond {10}; + int _avatarSimsPerSecondReport {0}; + quint64 _lastAvatarSimsPerSecondUpdate {0}; Camera _myCamera; // My view onto the world Camera _mirrorCamera; // Cammera for mirror view QRect _mirrorViewRect; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 53818cacab..1809704ce5 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -373,7 +373,7 @@ protected: glm::vec3 _nextPosition {}; glm::quat _nextOrientation {}; - bool _nextAllowed = true; + bool _nextAllowed {true}; // Body scale float _targetScale; From e7e1f5fe5c7c4fe4137f06ce9e0e46d8079a6846 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 9 Sep 2015 10:35:31 -0700 Subject: [PATCH 23/55] make all of AssetClient packets reliable --- libraries/networking/src/AssetClient.cpp | 7 ++----- libraries/networking/src/AssetUpload.cpp | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 959c1562f2..4c87da1b33 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -96,7 +96,7 @@ bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOf SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { - auto packet = NLPacket::create(PacketType::AssetGet); + auto packet = NLPacket::create(PacketType::AssetGet, -1, true); auto messageID = ++_currentID; @@ -127,7 +127,7 @@ bool AssetClient::getAssetInfo(const QString& hash, const QString& extension, Ge SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { - auto packet = NLPacket::create(PacketType::AssetGetInfo); + auto packet = NLPacket::create(PacketType::AssetGetInfo, -1, true); auto messageID = ++_currentID; packet->writePrimitive(messageID); @@ -207,9 +207,6 @@ bool AssetClient::uploadAsset(const QByteArray& data, const QString& extension, packetList->writePrimitive(static_cast(extension.length())); packetList->write(extension.toLatin1().constData(), extension.length()); - qDebug() << "Extension length: " << extension.length(); - qDebug() << "Extension: " << extension; - uint64_t size = data.length(); packetList->writePrimitive(size); packetList->write(data.constData(), size); diff --git a/libraries/networking/src/AssetUpload.cpp b/libraries/networking/src/AssetUpload.cpp index 14e5354f0b..03c8707517 100644 --- a/libraries/networking/src/AssetUpload.cpp +++ b/libraries/networking/src/AssetUpload.cpp @@ -41,6 +41,8 @@ void AssetUpload::start() { // ask the AssetClient to upload the asset and emit the proper signals from the passed callback auto assetClient = DependencyManager::get(); + qDebug() << "Attempting to upload" << _filename << "to asset-server."; + assetClient->uploadAsset(data, _extension, [this](AssetServerError error, const QString& hash){ switch (error) { case AssetServerError::NoError: From f081631075f2b4cef7c6690ca3e28cec88234913 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 10:36:52 -0700 Subject: [PATCH 24/55] Explicit cast. --- interface/src/avatar/AvatarUpdate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index c2240ef123..12836bb129 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -54,7 +54,7 @@ bool AvatarUpdate::process() { quint64 start = usecTimestampNow(); quint64 deltaMicroseconds = start - _lastAvatarUpdate; _lastAvatarUpdate = start; - float deltaSeconds = deltaMicroseconds / (float) USECS_PER_SECOND; + float deltaSeconds = (float) deltaMicroseconds / (float) USECS_PER_SECOND; Application::getInstance()->setAvatarSimrateSample(1.0f / deltaSeconds); QSharedPointer manager = DependencyManager::get(); From 5572840133ba75ef706abcc24fcd008be1b08cf4 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 10:39:36 -0700 Subject: [PATCH 25/55] Convert isHMD reference. --- interface/src/avatar/SkeletonModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index d3b3c19c08..ffe15a6d04 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -111,7 +111,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Rig::HeadParameters params; params.modelRotation = getRotation(); params.modelTranslation = getTranslation(); - params.enableLean = qApp->isHMDMode() && !myAvatar->getStandingHMDSensorMode(); + params.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode(); params.leanSideways = head->getFinalLeanSideways(); params.leanForward = head->getFinalLeanForward(); params.torsoTwist = head->getTorsoTwist(); From 2849d80274d02e2b155866c8c9a7269cab5dc0e3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 9 Sep 2015 10:39:53 -0700 Subject: [PATCH 26/55] use the exact payload size where possible --- libraries/networking/src/AssetClient.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 4c87da1b33..9931bab5ed 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -57,7 +57,8 @@ AssetRequest* AssetClient::createRequest(const QString& hash, const QString& ext SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (!assetServer) { - qDebug().nospace() << "Could not request " << hash << "." << extension << " since you are not currently connected to an asset-server."; + qDebug().nospace() << "Could not request " << hash << "." << extension + << " since you are not currently connected to an asset-server."; return nullptr; } @@ -96,10 +97,13 @@ bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOf SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { - auto packet = NLPacket::create(PacketType::AssetGet, -1, true); - + auto messageID = ++_currentID; + auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH + sizeof(uint8_t) + extension.length() + + sizeof(start) + sizeof(end); + auto packet = NLPacket::create(PacketType::AssetGet, payloadSize, true); + qDebug() << "Requesting data from" << start << "to" << end << "of" << hash << "from asset-server."; packet->writePrimitive(messageID); @@ -127,9 +131,11 @@ bool AssetClient::getAssetInfo(const QString& hash, const QString& extension, Ge SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { - auto packet = NLPacket::create(PacketType::AssetGetInfo, -1, true); - auto messageID = ++_currentID; + + auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH + sizeof(uint8_t) + extension.length(); + auto packet = NLPacket::create(PacketType::AssetGetInfo, payloadSize, true); + packet->writePrimitive(messageID); packet->write(QByteArray::fromHex(hash.toLatin1())); packet->writePrimitive(uint8_t(extension.length())); From 3f5744712f764c92a65b9b0886b22293484af8c7 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 11:04:17 -0700 Subject: [PATCH 27/55] Remove optional "parent" argument. GenericThread used to accept an optional "parent" argument, defaulting to nullptr. This was odd, because the moveToThread() in GenericThread::initialize() would become a no-op if the instance ever inits QObject(someNonNullParentQObject). (The only clue would be a log message "QObject::moveToThread: Cannot move objects with a parent", and things would end up in the same thread that created the instance.) As it turns out, all the subclasses of GenericThread do not init GenericThread(parent), so things worked as expected. --- interface/src/avatar/AvatarUpdate.cpp | 10 +--------- libraries/shared/src/GenericQueueThread.h | 2 +- libraries/shared/src/GenericThread.cpp | 4 ++-- libraries/shared/src/GenericThread.h | 2 +- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/AvatarUpdate.cpp b/interface/src/avatar/AvatarUpdate.cpp index 12836bb129..a4391172a7 100644 --- a/interface/src/avatar/AvatarUpdate.cpp +++ b/interface/src/avatar/AvatarUpdate.cpp @@ -16,15 +16,7 @@ #include #include "InterfaceLogging.h" -// GenericThread accepts an optional "parent" argument, defaulting to nullptr. -// This is odd, because the moveToThread() in GenericThread::initialize() will -// become a no-op if the instance ever inits QObject(someNonNullParentQObject). -// (The only clue will be a log message "QObject::moveToThread: Cannot move objects with a parent", -// and things will end up in the same thread that created this instance. Hillarity ensues.) -// As it turns out, all the other subclasses of GenericThread (at this time) do not init -// GenericThread(parent), so things work as expected. Here we explicitly init GenericThread(nullptr) -// so that there is no confusion and no chance for a hillarious thread debugging session. -AvatarUpdate::AvatarUpdate() : GenericThread(nullptr), _lastAvatarUpdate(0) { +AvatarUpdate::AvatarUpdate() : GenericThread(), _lastAvatarUpdate(0) { setObjectName("Avatar Update"); // GenericThread::initialize uses this to set the thread name. Settings settings; const int DEFAULT_TARGET_AVATAR_SIMRATE = 60; diff --git a/libraries/shared/src/GenericQueueThread.h b/libraries/shared/src/GenericQueueThread.h index 2a48ff7418..067d7a7989 100644 --- a/libraries/shared/src/GenericQueueThread.h +++ b/libraries/shared/src/GenericQueueThread.h @@ -24,7 +24,7 @@ class GenericQueueThread : public GenericThread { public: using Queue = QQueue; GenericQueueThread(QObject* parent = nullptr) - : GenericThread(parent) {} + : GenericThread() {} virtual ~GenericQueueThread() {} diff --git a/libraries/shared/src/GenericThread.cpp b/libraries/shared/src/GenericThread.cpp index 18f5224229..66af2e01c8 100644 --- a/libraries/shared/src/GenericThread.cpp +++ b/libraries/shared/src/GenericThread.cpp @@ -14,8 +14,8 @@ #include "GenericThread.h" -GenericThread::GenericThread(QObject* parent) : - QObject(parent), +GenericThread::GenericThread() : + QObject(), _stopThread(false), _isThreaded(false) // assume non-threaded, must call initialize() { diff --git a/libraries/shared/src/GenericThread.h b/libraries/shared/src/GenericThread.h index f261dc5b37..8362d3ba57 100644 --- a/libraries/shared/src/GenericThread.h +++ b/libraries/shared/src/GenericThread.h @@ -23,7 +23,7 @@ class GenericThread : public QObject { Q_OBJECT public: - GenericThread(QObject* parent = nullptr); + GenericThread(); virtual ~GenericThread(); /// Call to start the thread. From 83d14d33809a0702d50b938a9d1ab8e496eb6291 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 11:11:06 -0700 Subject: [PATCH 28/55] Protect avatarLock. --- libraries/avatars/src/AvatarData.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 1809704ce5..81d252c622 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -324,8 +324,6 @@ public: bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } - QMutex avatarLock; // Name is redundant, but it aids searches. - public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); @@ -424,6 +422,8 @@ protected: SimpleMovingAverage _averageBytesReceived; + QMutex avatarLock; // Name is redundant, but it aids searches. + private: static QUrl _defaultFullAvatarModelUrl; // privatize the copy constructor and assignment operator so they cannot be called From 01d7e68dbb8d7689c0d31e76e9ea57d0604f7fdc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Sep 2015 11:29:57 -0700 Subject: [PATCH 29/55] Terminate DDE earlier during shut-down and always at start-up --- interface/src/Application.cpp | 15 ++++++++------- interface/src/devices/DdeFaceTracker.cpp | 5 ++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 08a8af89f0..167d760cf5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -786,6 +786,14 @@ void Application::aboutToQuit() { } void Application::cleanupBeforeQuit() { + // Terminate third party processes so that they're not left running in the event of a subsequent shutdown crash +#ifdef HAVE_DDE + DependencyManager::destroy(); +#endif +#ifdef HAVE_IVIEWHMD + DependencyManager::destroy(); +#endif + if (_keyboardFocusHighlightID > 0) { getOverlays().deleteOverlay(_keyboardFocusHighlightID); _keyboardFocusHighlightID = -1; @@ -833,13 +841,6 @@ void Application::cleanupBeforeQuit() { // destroy the AudioClient so it and its thread have a chance to go down safely DependencyManager::destroy(); - -#ifdef HAVE_DDE - DependencyManager::destroy(); -#endif -#ifdef HAVE_IVIEWHMD - DependencyManager::destroy(); -#endif } void Application::emptyLocalCache() { diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index 396539c3cf..02cdb4bad8 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -254,9 +254,8 @@ void DdeFaceTracker::setEnabled(bool enabled) { _ddeProcess = new QProcess(qApp); connect(_ddeProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); _ddeProcess->start(QCoreApplication::applicationDirPath() + DDE_PROGRAM_PATH, DDE_ARGUMENTS); - } - - if (!enabled && _ddeProcess) { + } else { + // Send a command to stop face tracker evening if no _ddeProcess in case DDE has been left running after a crash _ddeStopping = true; _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); qCDebug(interfaceapp) << "DDE Face Tracker: Stopping"; From ed2ed525b2958b8507071001c0c59efd4eb05c8f Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 12:15:48 -0700 Subject: [PATCH 30/55] Tear down animGraph when turning off the avatar update thread. Otherwise, big time deltas accumulate. --- interface/src/Application.cpp | 1 + interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8ddf1c4c2..eb4fbe9607 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2477,6 +2477,7 @@ void Application::init() { void Application::setAvatarUpdateThreading(bool isThreaded) { if (_avatarUpdate) { + getMyAvatar()->destroyAnimGraph(); _avatarUpdate->terminate(); } _avatarUpdate = new AvatarUpdate(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index cc26be9012..a3a7514778 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -152,6 +152,7 @@ public: bool getStandingHMDSensorMode() const { return _standingHMDSensorMode; } void doUpdateBillboard(); + void destroyAnimGraph(); public slots: void increaseSize(); @@ -291,7 +292,6 @@ private: void maybeUpdateBillboard(); void initHeadBones(); void initAnimGraph(); - void destroyAnimGraph(); void safelyLoadAnimations(); // Avatar Preferences From 427ed8807b1fbc2e127d5a1854bcea138291517e Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 9 Sep 2015 12:41:59 -0700 Subject: [PATCH 31/55] fix breakdance toy --- examples/toys/breakdanceCore.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/toys/breakdanceCore.js b/examples/toys/breakdanceCore.js index 57a679ff51..6a69e8d35a 100644 --- a/examples/toys/breakdanceCore.js +++ b/examples/toys/breakdanceCore.js @@ -10,8 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - -function getPuppetPosition(props) { +getPuppetPosition = function(props) { var MAX_DISTANCE = 10; var searchPosition = MyAvatar.position; From 382de2b4aa2e02e77fd56728d6962bc0666fccc7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 9 Sep 2015 14:09:01 -0700 Subject: [PATCH 32/55] Fix possible hang when switching out of Oculus mode --- interface/src/Application.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 08a8af89f0..714a2593b6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4761,9 +4761,11 @@ void Application::updateDisplayMode() { qDebug() << "Deferring plugin switch until out of painting"; // Have the old plugin stop requesting renders oldDisplayPlugin->stop(); - QCoreApplication::postEvent(this, new LambdaEvent([this] { + QTimer* timer = new QTimer(); + timer->singleShot(500, [this, timer] { + timer->deleteLater(); updateDisplayMode(); - })); + }); return; } From 502503072679b0f7a9362bba88df8c3862d87c7e Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Wed, 9 Sep 2015 14:24:30 -0700 Subject: [PATCH 33/55] added lifetime to pointer, default grab behavior now honors objects initial position and rotation --- examples/controllers/handControllerGrab.js | 56 +++++++++++++++++----- examples/entityScripts/sprayPaintCan.js | 18 ++++--- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 2ebea75abc..24df71df9e 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -11,8 +11,7 @@ // -Script.include("../libraries/utils.js"); - +Script.include("https://hifi-public.s3.amazonaws.com/scripts/libraries/utils.js"); var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); var rightTriggerAction = RIGHT_HAND_CLICK; @@ -22,6 +21,10 @@ var GRAB_USER_DATA_KEY = "grabKey"; var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); var leftTriggerAction = LEFT_HAND_CLICK; +var LIFETIME = 10; +var currentLife = 0; +var POINTER_CHECK_TIME = 5000; + var ZERO_VEC = { x: 0, y: 0, @@ -42,7 +45,7 @@ var INTERSECT_COLOR = { blue: 10 }; -var GRAB_RADIUS = 1.0; +var GRAB_RADIUS = 1.5; var GRAB_COLOR = { red: 250, @@ -59,8 +62,15 @@ var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5; var RIGHT = 1; var LEFT = 0; -var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right") -var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left") +var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right"); +var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left"); + + +//Need to wait before calling these methods for some reason... +Script.setTimeout(function() { + rightController.checkPointer(); + leftController.checkPointer(); +}, 100) function controller(side, triggerAction, pullAction, hand) { this.hand = hand; @@ -92,7 +102,9 @@ function controller(side, triggerAction, pullAction, hand) { z: 1000 }, visible: false, + lifetime: LIFETIME }); + } @@ -127,6 +139,18 @@ controller.prototype.updateLine = function() { } +controller.prototype.checkPointer = function() { + var self = this; + Script.setTimeout(function() { + var props = Entities.getEntityProperties(self.pointer); + var currentLife = LIFETIME + POINTER_CHECK_TIME + currentLife; + //dimensions are set to .1, .1, .1 when lifetime expires + Entities.editEntity(self.pointer, { + lifetime: currentLife + }); + self.checkPointer(); + }, POINTER_CHECK_TIME); +} controller.prototype.checkForIntersections = function(origin, direction) { var pickRay = { @@ -241,14 +265,22 @@ controller.prototype.grabEntity = function() { this.closeGrabbing = true; //check if our entity has instructions on how to be grabbed, otherwise, just use default relative position and rotation var userData = getEntityUserData(this.grabbedEntity); - var relativePosition = ZERO_VEC; - var relativeRotation = Quat.fromPitchYawRollDegrees(0, 0, 0); - if(userData.spatialKey) { - if(userData.spatialKey.relativePosition) { - relativePosition = userData.spatialKey.relativePosition; + + var objectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation; + var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + + var objectPosition = Entities.getEntityProperties(this.grabbedEntity).position; + var offset = Vec3.subtract(objectPosition, handPosition); + var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset); + + var relativePosition = offsetPosition; + var relativeRotation = offsetRotation; + if (userData.grabFrame) { + if (userData.grabFrame.relativePosition) { + relativePosition = userData.grabFrame.relativePosition; } - if(userData.spatialKey.relativeRotation) { - relativeRotation = userData.spatialKey.relativeRotation; + if (userData.grabFrame.relativeRotation) { + relativeRotation = userData.grabFrame.relativeRotation; } } this.actionID = Entities.addAction("hold", this.grabbedEntity, { diff --git a/examples/entityScripts/sprayPaintCan.js b/examples/entityScripts/sprayPaintCan.js index 914e855349..c42a9bd659 100644 --- a/examples/entityScripts/sprayPaintCan.js +++ b/examples/entityScripts/sprayPaintCan.js @@ -1,6 +1,6 @@ (function() { Script.include("../libraries/utils.js"); - SPATIAL_USER_DATA_KEY = "spatialKey"; + GRAB_FRAME_USER_DATA_KEY = "grabFrame"; this.userData = {}; var TIP_OFFSET_Z = 0.14; @@ -62,13 +62,14 @@ this.sprayStream = function() { var forwardVec = Quat.getFront(self.properties.rotation); forwardVec = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 90, 0), forwardVec); + forwardVec = Vec3.normalize(forwardVec); var upVec = Quat.getUp(self.properties.rotation); var position = Vec3.sum(self.properties.position, Vec3.multiply(forwardVec, TIP_OFFSET_Z)); position = Vec3.sum(position, Vec3.multiply(upVec, TIP_OFFSET_Y)) Entities.editEntity(self.paintStream, { position: position, - emitVelocity: forwardVec + emitVelocity: Vec3.multiply(forwardVec, 4) }); //Now check for an intersection with an entity @@ -155,12 +156,16 @@ if (this.userData.grabKey && this.userData.grabKey.activated) { this.activated = true; } - if(!this.userData.spatialKey) { + if (!this.userData.grabFrame) { var data = { - relativePosition: {x: 0, y: 0, z: 0}, - relativeRotation: Quat.fromPitchYawRollDegrees(0, 0,0) + relativePosition: { + x: 0, + y: 0, + z: 0 + }, + relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, 0) } - setEntityCustomData(SPATIAL_USER_DATA_KEY, this.entityId, data); + setEntityCustomData(GRAB_FRAME_USER_DATA_KEY, this.entityId, data); } this.initialize(); } @@ -174,7 +179,6 @@ running: false }); - this.paintStream = Entities.addEntity({ type: "ParticleEffect", animationSettings: animationSettings, From ec93fc1b05c1e49da3e01381110f7f61e519bc08 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 9 Sep 2015 14:24:52 -0700 Subject: [PATCH 34/55] added some debugging to breakdance toy --- examples/entityScripts/breakdanceEntity.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/entityScripts/breakdanceEntity.js b/examples/entityScripts/breakdanceEntity.js index ece3e57062..332c381e39 100644 --- a/examples/entityScripts/breakdanceEntity.js +++ b/examples/entityScripts/breakdanceEntity.js @@ -45,14 +45,15 @@ // if the grabData says we're being grabbed, and the owner ID is our session, then we are being grabbed by this interface if (grabData.activated && grabData.avatarId == MyAvatar.sessionUUID) { - + //print("BreakdanceEntity.update() [I'm being grabbed] _this.entityID:" + _this.entityID); if (!_this.beingGrabbed) { + print("I'm was grabbed... _this.entityID:" + _this.entityID); + // remember we're being grabbed so we can detect being released _this.beingGrabbed = true; var props = Entities.getEntityProperties(entityID); var puppetPosition = getPuppetPosition(props); breakdanceStart(props.modelURL, puppetPosition); - print("I'm was grabbed..."); } else { breakdanceUpdate(); } @@ -62,7 +63,7 @@ // if we are not being grabbed, and we previously were, then we were just released, remember that // and print out a message _this.beingGrabbed = false; - print("I'm was released..."); + print("I'm was released... _this.entityID:" + _this.entityID); breakdanceEnd(); } }, @@ -73,6 +74,7 @@ // * connecting to the update signal so we can check our grabbed state preload: function(entityID) { this.entityID = entityID; + print("BreakdanceEntity.preload() this.entityID:" + this.entityID); Script.update.connect(this.update); }, @@ -80,6 +82,7 @@ // or because we've left the domain or quit the application. In all cases we want to unhook our connection // to the update signal unload: function(entityID) { + print("BreakdanceEntity.unload() this.entityID:" + this.entityID); Script.update.disconnect(this.update); }, From 62bf6c7c6fea7443ec5b121ba9a74818aa59bc19 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 9 Sep 2015 14:55:39 -0700 Subject: [PATCH 35/55] added some debugging to breakdance toy --- examples/entityScripts/breakdanceEntity.js | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/entityScripts/breakdanceEntity.js b/examples/entityScripts/breakdanceEntity.js index 332c381e39..5d4d418fec 100644 --- a/examples/entityScripts/breakdanceEntity.js +++ b/examples/entityScripts/breakdanceEntity.js @@ -31,6 +31,7 @@ // if we're currently being grabbed and if the person grabbing us is the current interfaces avatar. // we will watch this for state changes and print out if we're being grabbed or released when it changes. update: function() { + //print("BreakdanceEntity.update() _this.entityID:" + _this.entityID); var GRAB_USER_DATA_KEY = "grabKey"; // because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID From c1d68e058e23a4a5342e5fe0b6f3289ce6254a9d Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 15:02:11 -0700 Subject: [PATCH 36/55] Reduce interconnection between AnimGraph and SkeletonModel by removing init and having MyAvatar:initAnimGraph go directly to rig (just like destroyAnimGraph does). --- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/avatar/SkeletonModel.cpp | 3 --- interface/src/avatar/SkeletonModel.h | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 89ebe650d0..615a1c582b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1243,7 +1243,7 @@ void MyAvatar::initAnimGraph() { // python2 -m SimpleHTTPServer& //auto graphUrl = QUrl("http://localhost:8000/avatar.json"); auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/e2cb37aee601b6fba31d60eac3f6ae3ef72d4a66/avatar.json"); - _skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); + _rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); } void MyAvatar::destroyAnimGraph() { diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 165aa873fa..6297399354 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -618,6 +618,3 @@ bool SkeletonModel::hasSkeleton() { void SkeletonModel::onInvalidate() { } -void SkeletonModel::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) { - _rig->initAnimGraph(url, fbxGeometry); -} diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 389b1e7d36..75ad728d46 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -105,8 +105,6 @@ public: virtual void onInvalidate() override; - void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry); - signals: void skeletonLoaded(); From 5e8fe2967405d8e73ec554fcea9fd1de32a50a95 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 15:09:06 -0700 Subject: [PATCH 37/55] Remove unused declaration. --- interface/src/avatar/MyAvatar.h | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index e743dea860..d6f9e2c397 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -19,7 +19,6 @@ #include "Avatar.h" class ModelItemID; -class AnimNode; enum eyeContactTarget { LEFT_EYE, From 738da0116a58f126b0ec7ea1b62f5f8e72c1704e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 15:19:01 -0700 Subject: [PATCH 38/55] Simplify timer use. --- tests/animation/src/AnimTests.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 806560d96f..c2bb5a9350 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -131,17 +131,13 @@ void AnimTests::testLoader() { const int timeout = 1000; QEventLoop loop; - QTimer timer; - timer.setInterval(timeout); - timer.setSingleShot(true); AnimNode::Pointer node = nullptr; connect(&loader, &AnimNodeLoader::success, [&](AnimNode::Pointer nodeIn) { node = nodeIn; }); loop.connect(&loader, SIGNAL(success(AnimNode::Pointer)), SLOT(quit())); loop.connect(&loader, SIGNAL(error(int, QString)), SLOT(quit())); - loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); - timer.start(); + QTimer::singleShot(timeout, &loader, SLOT(quit())); loop.exec(); QVERIFY((bool)node); From f06e4eaae9e8b19f66b8e62d65ef40ef87a5ea07 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 9 Sep 2015 15:20:35 -0700 Subject: [PATCH 39/55] fix suppression of packet from unknown node --- libraries/networking/src/LimitedNodeList.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 38c87418c8..b6c3db3562 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -242,8 +242,9 @@ bool LimitedNodeList::packetSourceAndHashMatch(const udt::Packet& packet) { return true; } else { + static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with UUID"; static QString repeatedMessage - = LogHandler::getInstance().addRepeatedMessageRegex("Packet of type \\d+ \\([\\sa-zA-Z]+\\) received from unknown node with UUID"); + = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); qCDebug(networking) << "Packet of type" << headerType << "received from unknown node with UUID" << qPrintable(uuidStringWithoutCurlyBraces(sourceID)); From 745872f914bcb45903f1a6ee88538697891576ff Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 15:45:23 -0700 Subject: [PATCH 40/55] More variants tests. --- tests/animation/src/AnimTests.cpp | 49 +++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index c2bb5a9350..a08288000e 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -127,6 +127,8 @@ void AnimTests::testClipEvaulateWithVars() { void AnimTests::testLoader() { auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/8ba57a8f0a76f88b39a11f77f8d9df04af9cec95/test.json"); + // NOTE: This will warn about missing "test01.fbx", "test02.fbx", etc. if the resource loading code doesn't handle relative pathnames! + // However, the test will proceed. AnimNodeLoader loader(url); const int timeout = 1000; @@ -137,7 +139,8 @@ void AnimTests::testLoader() { loop.connect(&loader, SIGNAL(success(AnimNode::Pointer)), SLOT(quit())); loop.connect(&loader, SIGNAL(error(int, QString)), SLOT(quit())); - QTimer::singleShot(timeout, &loader, SLOT(quit())); + QTimer::singleShot(timeout, &loop, SLOT(quit())); + loop.exec(); QVERIFY((bool)node); @@ -180,42 +183,58 @@ void AnimTests::testLoader() { void AnimTests::testVariant() { auto defaultVar = AnimVariant(); - auto boolVar = AnimVariant(true); - auto intVar = AnimVariant(1); - auto floatVar = AnimVariant(1.0f); - auto vec3Var = AnimVariant(glm::vec3(1.0f, 2.0f, 3.0f)); - auto quatVar = AnimVariant(glm::quat(1.0f, 2.0f, 3.0f, 4.0f)); + auto boolVarTrue = AnimVariant(true); + auto boolVarFalse = AnimVariant(false); + auto intVarZero = AnimVariant(0); + auto intVarOne = AnimVariant(1); + auto intVarNegative = AnimVariant(-1); + auto floatVarZero = AnimVariant(0.0f); + auto floatVarOne = AnimVariant(1.0f); + auto floatVarNegative = AnimVariant(-1.0f); + auto vec3Var = AnimVariant(glm::vec3(1.0f, -2.0f, 3.0f)); + auto quatVar = AnimVariant(glm::quat(1.0f, 2.0f, -3.0f, 4.0f)); auto mat4Var = AnimVariant(glm::mat4(glm::vec4(1.0f, 2.0f, 3.0f, 4.0f), - glm::vec4(5.0f, 6.0f, 7.0f, 8.0f), + glm::vec4(5.0f, 6.0f, -7.0f, 8.0f), glm::vec4(9.0f, 10.0f, 11.0f, 12.0f), glm::vec4(13.0f, 14.0f, 15.0f, 16.0f))); QVERIFY(defaultVar.isBool()); QVERIFY(defaultVar.getBool() == false); - QVERIFY(boolVar.isBool()); - QVERIFY(boolVar.getBool() == true); + QVERIFY(boolVarTrue.isBool()); + QVERIFY(boolVarTrue.getBool() == true); + QVERIFY(boolVarFalse.isBool()); + QVERIFY(boolVarFalse.getBool() == false); - QVERIFY(intVar.isInt()); - QVERIFY(intVar.getInt() == 1); + QVERIFY(intVarZero.isInt()); + QVERIFY(intVarZero.getInt() == 0); + QVERIFY(intVarOne.isInt()); + QVERIFY(intVarOne.getInt() == 1); + QVERIFY(intVarNegative.isInt()); + QVERIFY(intVarNegative.getInt() == -1); - QVERIFY(floatVar.isFloat()); - QVERIFY(floatVar.getFloat() == 1.0f); + QVERIFY(floatVarZero.isFloat()); + QVERIFY(floatVarZero.getFloat() == 0.0f); + QVERIFY(floatVarOne.isFloat()); + QVERIFY(floatVarOne.getFloat() == 1.0f); + QVERIFY(floatVarNegative.isFloat()); + QVERIFY(floatVarNegative.getFloat() == -1.0f); QVERIFY(vec3Var.isVec3()); auto v = vec3Var.getVec3(); QVERIFY(v.x == 1.0f); - QVERIFY(v.y == 2.0f); + QVERIFY(v.y == -2.0f); QVERIFY(v.z == 3.0f); QVERIFY(quatVar.isQuat()); auto q = quatVar.getQuat(); QVERIFY(q.w == 1.0f); QVERIFY(q.x == 2.0f); - QVERIFY(q.y == 3.0f); + QVERIFY(q.y == -3.0f); QVERIFY(q.z == 4.0f); QVERIFY(mat4Var.isMat4()); auto m = mat4Var.getMat4(); QVERIFY(m[0].x == 1.0f); + QVERIFY(m[1].z == -7.0f); QVERIFY(m[3].w == 16.0f); } From da6444a776a3c2aa0b05418e0a6a75ac92b50da5 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 9 Sep 2015 17:02:55 -0700 Subject: [PATCH 41/55] Start anim graph in the right saved state on startup. --- interface/src/avatar/MyAvatar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index cb79ed7b57..d477db936e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -830,6 +830,9 @@ void MyAvatar::loadData() { settings.endGroup(); _rig->setEnableRig(Menu::getInstance()->isOptionChecked(MenuOption::EnableRigAnimations)); + setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible)); + setEnableDebugDrawBindPose(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawBindPose)); + setEnableDebugDrawAnimPose(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawAnimPose)); } void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const { From d62945c6bf72ed8483a4f238937ea50824bdc78c Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 9 Sep 2015 17:13:16 -0700 Subject: [PATCH 42/55] fix degenerative url scheme case --- libraries/networking/src/ResourceManager.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index c721419c7e..e89e608aef 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -25,6 +25,15 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q return new HTTPResourceRequest(parent, url); } else if (scheme == URL_SCHEME_ATP) { return new AssetResourceRequest(parent, url); + } else { + // check the degenerative file case: on windows we can often have urls of the form c:/filename + // this checks for and works around that case. + QString urlWithFileSchemeName = URL_SCHEME_FILE + ":///" + url.toString(); + QUrl urlWithFileScheme = urlWithFileSchemeName; + QString fileName = urlWithFileScheme.toLocalFile(); + if (!fileName.isEmpty()) { + return new FileResourceRequest(parent, urlWithFileScheme); + } } qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url(); From 6aebda86f4a343b6aeab6a12f07c65f33c255a15 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 9 Sep 2015 17:24:20 -0700 Subject: [PATCH 43/55] Creating the flashlight script files --- examples/toys/flashlight/createFlashlight.js | 45 ++++++ examples/toys/flashlight/flashlight.js | 155 +++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 examples/toys/flashlight/createFlashlight.js create mode 100644 examples/toys/flashlight/flashlight.js diff --git a/examples/toys/flashlight/createFlashlight.js b/examples/toys/flashlight/createFlashlight.js new file mode 100644 index 0000000000..6923c61d7d --- /dev/null +++ b/examples/toys/flashlight/createFlashlight.js @@ -0,0 +1,45 @@ +// +// createFlashligh.js +// examples/entityScripts +// +// Created by Sam Gateau on 9/9/15. +// Copyright 2015 High Fidelity, Inc. +// +// This is a toy script that create a flashlight entity that lit when grabbed +// This can be run from an interface and the flashlight will get deleted from the domain when quitting +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js"); + + +var scriptURL = "file:///C:/GitHub/hifi/examples/toys/flashlight/flashlight.js?"+randInt(0,1000); +//var scriptURL = "https://hifi-public.s3.amazonaws.com/scripts/toys/flashlight/flashlight.js?"+randInt(0,1000); +var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; + + +var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); + +var flashlight = Entities.addEntity({ + type: "Model", + modelURL: modelURL, + position: center, + dimensions: { + x: 0.04, + y: 0.15, + z: 0.04 + }, + collisionsWillMove: true, + shapeType: 'box', + script: scriptURL +}); + + +function cleanup() { + Entities.deleteEntity(flashlight); +} + + +Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js new file mode 100644 index 0000000000..9adbd81ac5 --- /dev/null +++ b/examples/toys/flashlight/flashlight.js @@ -0,0 +1,155 @@ +// +// flashligh.js +// examples/entityScripts +// +// Created by Sam Gateau on 9/9/15. +// Copyright 2015 High Fidelity, Inc. +// +// This is a toy script that can be added to the Flashlight model entity: +// "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx" +// that creates a spotlight attached with the flashlight model while the entity is grabbed +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + function debugPrint(message) { + // print(message); + } + + Script.include("https://hifi-public.s3.amazonaws.com/scripts/libraries/utils.js"); + + var _this; + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + Flashlight = function() { + _this = this; + _this._hasSpotlight = false; + _this._spotlight = null; + }; + + // These constants define the Spotlight position and orientation relative to the model + var MODEL_LIGHT_POSITION = {x: 0, y: 0, z: 0}; + var MODEL_LIGHT_ROTATION = Quat.angleAxis (-90, {x: 1, y: 0, z: 0}); + + // Evaluate the world light entity position and orientation from the model ones + function evalLightWorldTransform(modelPos, modelRot) { + return {p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)}; + }; + + Flashlight.prototype = { + + // update() will be called regulary, because we've hooked the update signal in our preload() function + // we will check out userData for the grabData. In the case of the hydraGrab script, it will tell us + // if we're currently being grabbed and if the person grabbing us is the current interfaces avatar. + // we will watch this for state changes and print out if we're being grabbed or released when it changes. + update: function() { + var GRAB_USER_DATA_KEY = "grabKey"; + + // because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID + var entityID = _this.entityID; + + // we want to assume that if there is no grab data, then we are not being grabbed + var defaultGrabData = { activated: false, avatarId: null }; + + // this handy function getEntityCustomData() is available in utils.js and it will return just the specific section + // of user data we asked for. If it's not available it returns our default data. + var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, defaultGrabData); + + + // if the grabData says we're being grabbed, and the owner ID is our session, then we are being grabbed by this interface + if (grabData.activated && grabData.avatarId == MyAvatar.sessionUUID) { + + // remember we're being grabbed so we can detect being released + _this.beingGrabbed = true; + + var modelProperties = Entities.getEntityProperties(entityID); + var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation); + + // Create the spot light driven by this model if we don;t have one yet + // Or make sure to keep it's position in sync + if (!_this._hasSpotlight) { + + _this._spotlight = Entities.addEntity({ + type: "Light", + position: lightTransform.p, + rotation: lightTransform.q, + isSpotlight: true, + dimensions: { x: 2, y: 2, z: 20 }, + color: { red: 255, green: 255, blue: 255 }, + intensity: 2, + exponent: 0.5, + cutoff: 20 + }); + _this._hasSpotlight = true; + + // _this._startModelPosition = modelProperties.position; + // _this._startModelRotation = modelProperties.rotation; + + debugPrint("Flashlight:: creating a spotlight"); + } else { + // Updating the spotlight + Entities.editEntity(_this._spotlight, {position: lightTransform.p, rotation: lightTransform.q}); + + debugPrint("Flashlight:: updating the spotlight"); + } + + debugPrint("I'm being grabbed..."); + + } else if (_this.beingGrabbed) { + + if (_this._hasSpotlight) { + Entities.deleteEntity(_this._spotlight); + debugPrint("Destroying flashlight spotlight..."); + } + _this._hasSpotlight = false; + _this._spotlight = null; + + // Reset model to initial position + Entities.editEntity(_this.entityID, {position: _this._startModelPosition, rotation: _this._startModelRotation, + velocity: {x: 0, y: 0, z: 0}, angularVelocity: {x: 0, y: 0, z: 0}}); + + // if we are not being grabbed, and we previously were, then we were just released, remember that + // and print out a message + _this.beingGrabbed = false; + debugPrint("I'm was released..."); + } + }, + + // preload() will be called when the entity has become visible (or known) to the interface + // it gives us a chance to set our local JavaScript object up. In this case it means: + // * remembering our entityID, so we can access it in cases where we're called without an entityID + // * connecting to the update signal so we can check our grabbed state + preload: function(entityID) { + _this.entityID = entityID; + + var modelProperties = Entities.getEntityProperties(entityID); + _this._startModelPosition = modelProperties.position; + _this._startModelRotation = modelProperties.rotation; + + Script.update.connect(this.update); + }, + + // unload() will be called when our entity is no longer available. It may be because we were deleted, + // or because we've left the domain or quit the application. In all cases we want to unhook our connection + // to the update signal + unload: function(entityID) { + + if (_this._hasSpotlight) { + Entities.deleteEntity(_this._spotlight); + //print("Destroying flashlight spotlight..."); + } + _this._hasSpotlight = false; + _this._spotlight = null; + + Script.update.disconnect(this.update); + }, + }; + + // entity scripts always need to return a newly constructed object of our type + return new Flashlight(); +}) From f710afbfd1a3ed6bca47f90aa79972cbb0e8151c Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 9 Sep 2015 17:26:01 -0700 Subject: [PATCH 44/55] CR feedback --- libraries/networking/src/ResourceManager.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index e89e608aef..a06c89fe0a 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -28,10 +28,8 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q } else { // check the degenerative file case: on windows we can often have urls of the form c:/filename // this checks for and works around that case. - QString urlWithFileSchemeName = URL_SCHEME_FILE + ":///" + url.toString(); - QUrl urlWithFileScheme = urlWithFileSchemeName; - QString fileName = urlWithFileScheme.toLocalFile(); - if (!fileName.isEmpty()) { + QUrl urlWithFileScheme { QString(URL_SCHEME_FILE + ":///" + url.toString()) }; + if (!urlWithFileScheme.toLocalFile().isEmpty()) { return new FileResourceRequest(parent, urlWithFileScheme); } } From 626fcb867b88cf8e102eb78aa0354209e87375a4 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 9 Sep 2015 17:27:39 -0700 Subject: [PATCH 45/55] CR feedback --- libraries/networking/src/ResourceManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index a06c89fe0a..8af33c5463 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -28,7 +28,7 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q } else { // check the degenerative file case: on windows we can often have urls of the form c:/filename // this checks for and works around that case. - QUrl urlWithFileScheme { QString(URL_SCHEME_FILE + ":///" + url.toString()) }; + QUrl urlWithFileScheme { URL_SCHEME_FILE + ":///" + url.toString() }; if (!urlWithFileScheme.toLocalFile().isEmpty()) { return new FileResourceRequest(parent, urlWithFileScheme); } From ede0a39b5b8dffd24de50e539b6b9c39c659ea75 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 9 Sep 2015 17:54:09 -0700 Subject: [PATCH 46/55] prevent Script.include() from including the same file multiple times --- libraries/script-engine/src/ScriptEngine.cpp | 8 +++++++- libraries/script-engine/src/ScriptEngine.h | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index eeff6c15f0..a460a1189d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -860,7 +860,12 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac } QList urls; for (QString file : includeFiles) { - urls.append(resolvePath(file)); + QUrl thisURL { resolvePath(file) }; + if (!_includedURLs.contains(thisURL)) { + urls.append(thisURL); + } else { + qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL; + } } BatchLoader* loader = new BatchLoader(urls); @@ -871,6 +876,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac if (contents.isNull()) { qCDebug(scriptengine) << "Error loading file: " << url << "line:" << __LINE__; } else { + _includedURLs << url; QScriptValue result = evaluate(contents, url.toString()); } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 65644dceb9..bb2918fb8e 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -152,6 +153,7 @@ protected: Sound* _avatarSound; int _numAvatarSoundSentBytes; bool _isAgent = false; + QSet _includedURLs; private: void stopAllTimers(); From 8360fceadf8a8f4065651f98661702400cdb2497 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 9 Sep 2015 18:09:52 -0700 Subject: [PATCH 47/55] Fixing the url behavior --- examples/toys/flashlight/createFlashlight.js | 1 + examples/toys/flashlight/flashlight.js | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/toys/flashlight/createFlashlight.js b/examples/toys/flashlight/createFlashlight.js index 6923c61d7d..312d9626ab 100644 --- a/examples/toys/flashlight/createFlashlight.js +++ b/examples/toys/flashlight/createFlashlight.js @@ -17,6 +17,7 @@ Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js"); var scriptURL = "file:///C:/GitHub/hifi/examples/toys/flashlight/flashlight.js?"+randInt(0,1000); //var scriptURL = "https://hifi-public.s3.amazonaws.com/scripts/toys/flashlight/flashlight.js?"+randInt(0,1000); + var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index 9adbd81ac5..4cc777c1b5 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -82,13 +82,13 @@ dimensions: { x: 2, y: 2, z: 20 }, color: { red: 255, green: 255, blue: 255 }, intensity: 2, - exponent: 0.5, + exponent: 0.3, cutoff: 20 }); _this._hasSpotlight = true; - // _this._startModelPosition = modelProperties.position; - // _this._startModelRotation = modelProperties.rotation; + // _this._startModelPosition = modelProperties.position; + // _this._startModelRotation = modelProperties.rotation; debugPrint("Flashlight:: creating a spotlight"); } else { @@ -141,7 +141,6 @@ if (_this._hasSpotlight) { Entities.deleteEntity(_this._spotlight); - //print("Destroying flashlight spotlight..."); } _this._hasSpotlight = false; _this._spotlight = null; From 0a0a9805f63db4a1d9a97900af4ff3f3d4e7fdf7 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 9 Sep 2015 18:11:00 -0700 Subject: [PATCH 48/55] Fixing the url behavior --- examples/toys/flashlight/createFlashlight.js | 3 +-- examples/toys/flashlight/flashlight.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/toys/flashlight/createFlashlight.js b/examples/toys/flashlight/createFlashlight.js index 312d9626ab..bf03de547b 100644 --- a/examples/toys/flashlight/createFlashlight.js +++ b/examples/toys/flashlight/createFlashlight.js @@ -15,8 +15,7 @@ Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js"); -var scriptURL = "file:///C:/GitHub/hifi/examples/toys/flashlight/flashlight.js?"+randInt(0,1000); -//var scriptURL = "https://hifi-public.s3.amazonaws.com/scripts/toys/flashlight/flashlight.js?"+randInt(0,1000); +var scriptURL = "https://hifi-public.s3.amazonaws.com/scripts/toys/flashlight/flashlight.js?"+randInt(0,1000); var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"; diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index 4cc777c1b5..e0b2f06bdd 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -87,8 +87,8 @@ }); _this._hasSpotlight = true; - // _this._startModelPosition = modelProperties.position; - // _this._startModelRotation = modelProperties.rotation; + _this._startModelPosition = modelProperties.position; + _this._startModelRotation = modelProperties.rotation; debugPrint("Flashlight:: creating a spotlight"); } else { From fdcfa9657c2523333d63264c18b072a79ca8a52a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 9 Sep 2015 21:06:50 -0700 Subject: [PATCH 49/55] Rework and clarify stopping an existing DDE process --- interface/src/devices/DdeFaceTracker.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index 02cdb4bad8..2ddd8d9d04 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -236,28 +236,25 @@ void DdeFaceTracker::setEnabled(bool enabled) { cancelCalibration(); } - // isOpen() does not work as one might expect on QUdpSocket; don't test isOpen() before closing socket. _udpSocket.close(); - if (enabled) { - _udpSocket.bind(_host, _serverPort); - } + // Terminate any existing DDE process, perhaps left running after an Interface crash. + // Do this even if !enabled in case user reset their settings after crash. const char* DDE_EXIT_COMMAND = "exit"; + _udpSocket.bind(_host, _serverPort); + _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); if (enabled && !_ddeProcess) { - // Terminate any existing DDE process, perhaps left running after an Interface crash - _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); _ddeStopping = false; - qCDebug(interfaceapp) << "DDE Face Tracker: Starting"; _ddeProcess = new QProcess(qApp); connect(_ddeProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); _ddeProcess->start(QCoreApplication::applicationDirPath() + DDE_PROGRAM_PATH, DDE_ARGUMENTS); - } else { - // Send a command to stop face tracker evening if no _ddeProcess in case DDE has been left running after a crash + } + + if (!enabled && _ddeProcess) { _ddeStopping = true; - _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); qCDebug(interfaceapp) << "DDE Face Tracker: Stopping"; } #endif From e647bfc19b93810ec903854b2689125147684fec Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 9 Sep 2015 22:41:09 -0700 Subject: [PATCH 50/55] Update flashlight.js modify the path to be relative --- examples/toys/flashlight/flashlight.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index e0b2f06bdd..74014781e9 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -19,7 +19,7 @@ // print(message); } - Script.include("https://hifi-public.s3.amazonaws.com/scripts/libraries/utils.js"); + Script.include("../libraries/utils.js"); var _this; From 710e369b70c352e198cd84df54aefec25126ecdb Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 10 Sep 2015 08:52:24 -0700 Subject: [PATCH 51/55] don't have polyvox build bindings --- cmake/externals/polyvox/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/externals/polyvox/CMakeLists.txt b/cmake/externals/polyvox/CMakeLists.txt index 4563bf5918..14712e5537 100644 --- a/cmake/externals/polyvox/CMakeLists.txt +++ b/cmake/externals/polyvox/CMakeLists.txt @@ -5,7 +5,7 @@ ExternalProject_Add( ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/polyvox-master-2015-7-15.zip URL_MD5 9ec6323b87e849ae36e562ae1c7494a9 - CMAKE_ARGS -DENABLE_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX:PATH= + CMAKE_ARGS -DENABLE_EXAMPLES=OFF -DENABLE_BINDINGS=OFF -DCMAKE_INSTALL_PREFIX:PATH= BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build LOG_DOWNLOAD 1 LOG_CONFIGURE 1 From 9ba681dd7fc76c1e5b44b13d074e2628ad4a87a7 Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Thu, 10 Sep 2015 08:59:05 -0700 Subject: [PATCH 52/55] fixed way lifetime on pointer entity is updated --- examples/controllers/handControllerGrab.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 24df71df9e..c980646250 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -11,7 +11,7 @@ // -Script.include("https://hifi-public.s3.amazonaws.com/scripts/libraries/utils.js"); +Script.include("../libraries/utils.js"); var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); var rightTriggerAction = RIGHT_HAND_CLICK; @@ -22,6 +22,7 @@ var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); var leftTriggerAction = LEFT_HAND_CLICK; var LIFETIME = 10; +var EXTRA_TIME = 5; var currentLife = 0; var POINTER_CHECK_TIME = 5000; @@ -143,8 +144,7 @@ controller.prototype.checkPointer = function() { var self = this; Script.setTimeout(function() { var props = Entities.getEntityProperties(self.pointer); - var currentLife = LIFETIME + POINTER_CHECK_TIME + currentLife; - //dimensions are set to .1, .1, .1 when lifetime expires + var currentLife = props.age + EXTRA_TIME Entities.editEntity(self.pointer, { lifetime: currentLife }); From 66a4ff752c1d526c2b53161477dc1592c7c8df2b Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Thu, 10 Sep 2015 09:01:51 -0700 Subject: [PATCH 53/55] cleaning up lifetime calc --- examples/controllers/handControllerGrab.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index c980646250..523cb4eff9 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -23,7 +23,6 @@ var leftTriggerAction = LEFT_HAND_CLICK; var LIFETIME = 10; var EXTRA_TIME = 5; -var currentLife = 0; var POINTER_CHECK_TIME = 5000; var ZERO_VEC = { @@ -144,9 +143,8 @@ controller.prototype.checkPointer = function() { var self = this; Script.setTimeout(function() { var props = Entities.getEntityProperties(self.pointer); - var currentLife = props.age + EXTRA_TIME Entities.editEntity(self.pointer, { - lifetime: currentLife + lifetime: props.age + EXTRA_TIME }); self.checkPointer(); }, POINTER_CHECK_TIME); From f45e182d33331f3ffd49db37ab0a5d24fe6ade6e Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 10 Sep 2015 09:06:56 -0700 Subject: [PATCH 54/55] fix CR feedback, add examples --- examples/example/tests/test-includes/a.js | 7 +++++++ examples/example/tests/test-includes/b.js | 6 ++++++ examples/example/tests/test-includes/start.js | 5 +++++ libraries/script-engine/src/ScriptEngine.cpp | 5 +++-- 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 examples/example/tests/test-includes/a.js create mode 100644 examples/example/tests/test-includes/b.js create mode 100644 examples/example/tests/test-includes/start.js diff --git a/examples/example/tests/test-includes/a.js b/examples/example/tests/test-includes/a.js new file mode 100644 index 0000000000..1d99a08525 --- /dev/null +++ b/examples/example/tests/test-includes/a.js @@ -0,0 +1,7 @@ +// a.js: +Script.include('b.js'); +if (a === undefined) { +a = 0; +} +a++; +print('script a:' + a); diff --git a/examples/example/tests/test-includes/b.js b/examples/example/tests/test-includes/b.js new file mode 100644 index 0000000000..ea3f9664ba --- /dev/null +++ b/examples/example/tests/test-includes/b.js @@ -0,0 +1,6 @@ +// b.js: +if (b === undefined) { +b = 0; +} +b++; +print('script b: ' + b); \ No newline at end of file diff --git a/examples/example/tests/test-includes/start.js b/examples/example/tests/test-includes/start.js new file mode 100644 index 0000000000..8060ffcf71 --- /dev/null +++ b/examples/example/tests/test-includes/start.js @@ -0,0 +1,5 @@ +// start.js: +var a, b; +print('initially: a:' + a + ' b:' + b); +Script.include(['a.js', '../test-includes/a.js', 'b.js', 'a.js']); +print('finally a:' + a + ' b:' + b); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a460a1189d..83a87b718f 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -863,7 +863,9 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac QUrl thisURL { resolvePath(file) }; if (!_includedURLs.contains(thisURL)) { urls.append(thisURL); - } else { + _includedURLs << thisURL; + } + else { qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL; } } @@ -876,7 +878,6 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac if (contents.isNull()) { qCDebug(scriptengine) << "Error loading file: " << url << "line:" << __LINE__; } else { - _includedURLs << url; QScriptValue result = evaluate(contents, url.toString()); } } From 38fcb80135033de178a27da4ea6558ae34b7499a Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 10 Sep 2015 09:52:52 -0700 Subject: [PATCH 55/55] FIx bad relative path --- examples/toys/flashlight/flashlight.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/toys/flashlight/flashlight.js b/examples/toys/flashlight/flashlight.js index 74014781e9..e0106dbd49 100644 --- a/examples/toys/flashlight/flashlight.js +++ b/examples/toys/flashlight/flashlight.js @@ -19,7 +19,7 @@ // print(message); } - Script.include("../libraries/utils.js"); + Script.include("../../libraries/utils.js"); var _this;