From 805a74c074d6edda6b00216b3c39c1e8c316d1e6 Mon Sep 17 00:00:00 2001 From: trent Date: Thu, 20 Apr 2017 13:21:38 -0400 Subject: [PATCH 001/135] First-pass attempt at remedying the smoothing issue (the avatar is never receiving the proper call). --- interface/src/avatar/Avatar.cpp | 12 ++++++++---- interface/src/avatar/Avatar.h | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 5b996a3cdf..b6095f76d3 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -358,7 +358,9 @@ void Avatar::simulate(float deltaTime, bool inView) { ); updateAttitude(); } - } + } else if (AvatarData::getPosition() != _smoothPositionTarget) { + setPosition(AvatarData::getPosition()); + } if (_smoothOrientationTimer < _smoothOrientationTime) { // Smooth the remote avatar movement. @@ -371,7 +373,9 @@ void Avatar::simulate(float deltaTime, bool inView) { ); updateAttitude(); } - } + } else if (AvatarData::getOrientation() != _smoothOrientationTarget) { + setOrientation(AvatarData::getOrientation()); + } } PerformanceTimer perfTimer("simulate"); @@ -1386,7 +1390,7 @@ void Avatar::setPosition(const glm::vec3& position) { } // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. - _smoothPositionInitial = getPosition(); + _smoothPositionInitial = AvatarData::getPosition(); _smoothPositionTarget = position; _smoothPositionTimer = 0.0f; } @@ -1400,7 +1404,7 @@ void Avatar::setOrientation(const glm::quat& orientation) { } // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. - _smoothOrientationInitial = getOrientation(); + _smoothOrientationInitial = AvatarData::getOrientation(); _smoothOrientationTarget = orientation; _smoothOrientationTimer = 0.0f; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 8c055885fd..fa7ca59d54 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -253,7 +253,7 @@ protected: friend class AvatarManager; const float SMOOTH_TIME_POSITION = 0.125f; - const float SMOOTH_TIME_ORIENTATION = 0.075f; + const float SMOOTH_TIME_ORIENTATION = 0.15f; virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; From b00cd66a2a40047a540b1f368dd399ef8a915fc4 Mon Sep 17 00:00:00 2001 From: trent Date: Fri, 21 Apr 2017 22:36:11 -0400 Subject: [PATCH 002/135] Revisited where smoothing was done (to a much lower-level); still needs testing. --- interface/src/avatar/Avatar.cpp | 68 ++-------------------- interface/src/avatar/Avatar.h | 23 -------- interface/src/ui/overlays/ModelOverlay.cpp | 61 ++++++++++++++++--- interface/src/ui/overlays/ModelOverlay.h | 27 ++++++++- 4 files changed, 86 insertions(+), 93 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b6095f76d3..63d2459390 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -85,14 +85,6 @@ Avatar::Avatar(QThread* thread, RigPointer rig) : _lastOrientation(), _worldUpDirection(DEFAULT_UP_DIRECTION), _moving(false), - _smoothPositionTime(SMOOTH_TIME_POSITION), - _smoothPositionTimer(std::numeric_limits::max()), - _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), - _smoothOrientationTimer(std::numeric_limits::max()), - _smoothPositionInitial(), - _smoothPositionTarget(), - _smoothOrientationInitial(), - _smoothOrientationTarget(), _initialized(false), _voiceSphereID(GeometryCache::UNKNOWN_ID) { @@ -346,38 +338,6 @@ void Avatar::simulate(float deltaTime, bool inView) { _simulationInViewRate.increment(); } - if (!isMyAvatar()) { - if (_smoothPositionTimer < _smoothPositionTime) { - // Smooth the remote avatar movement. - _smoothPositionTimer += deltaTime; - if (_smoothPositionTimer < _smoothPositionTime) { - AvatarData::setPosition( - lerp(_smoothPositionInitial, - _smoothPositionTarget, - easeInOutQuad(glm::clamp(_smoothPositionTimer / _smoothPositionTime, 0.0f, 1.0f))) - ); - updateAttitude(); - } - } else if (AvatarData::getPosition() != _smoothPositionTarget) { - setPosition(AvatarData::getPosition()); - } - - if (_smoothOrientationTimer < _smoothOrientationTime) { - // Smooth the remote avatar movement. - _smoothOrientationTimer += deltaTime; - if (_smoothOrientationTimer < _smoothOrientationTime) { - AvatarData::setOrientation( - slerp(_smoothOrientationInitial, - _smoothOrientationTarget, - easeInOutQuad(glm::clamp(_smoothOrientationTimer / _smoothOrientationTime, 0.0f, 1.0f))) - ); - updateAttitude(); - } - } else if (AvatarData::getOrientation() != _smoothOrientationTarget) { - setOrientation(AvatarData::getOrientation()); - } - } - PerformanceTimer perfTimer("simulate"); { PROFILE_RANGE(simulation, "updateJoints"); @@ -1382,31 +1342,15 @@ glm::quat Avatar::getUncachedRightPalmRotation() const { } void Avatar::setPosition(const glm::vec3& position) { - if (isMyAvatar()) { - // This is the local avatar, no need to handle any position smoothing. - AvatarData::setPosition(position); - updateAttitude(); - return; - } - - // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. - _smoothPositionInitial = AvatarData::getPosition(); - _smoothPositionTarget = position; - _smoothPositionTimer = 0.0f; + // This is the local avatar, no need to handle any position smoothing. + AvatarData::setPosition(position); + updateAttitude(); } void Avatar::setOrientation(const glm::quat& orientation) { - if (isMyAvatar()) { - // This is the local avatar, no need to handle any position smoothing. - AvatarData::setOrientation(orientation); - updateAttitude(); - return; - } - - // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. - _smoothOrientationInitial = AvatarData::getOrientation(); - _smoothOrientationTarget = orientation; - _smoothOrientationTimer = 0.0f; + // This is the local avatar, no need to handle any position smoothing. + AvatarData::setOrientation(orientation); + updateAttitude(); } void Avatar::updatePalms() { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index fa7ca59d54..80aec18119 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -228,16 +228,6 @@ public: bool hasNewJointData() const { return _hasNewJointData; } - inline float easeInOutQuad(float lerpValue) { - assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); - - if (lerpValue < 0.5f) { - return (2.0f * lerpValue * lerpValue); - } - - return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); - } - public slots: // FIXME - these should be migrated to use Pose data instead @@ -252,9 +242,6 @@ public slots: protected: friend class AvatarManager; - const float SMOOTH_TIME_POSITION = 0.125f; - const float SMOOTH_TIME_ORIENTATION = 0.15f; - virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! @@ -324,16 +311,6 @@ protected: RateCounter<> _skeletonModelSimulationRate; RateCounter<> _jointDataSimulationRate; - // Smoothing data for blending from one position/orientation to another on remote agents. - float _smoothPositionTime; - float _smoothPositionTimer; - float _smoothOrientationTime; - float _smoothOrientationTimer; - glm::vec3 _smoothPositionInitial; - glm::vec3 _smoothPositionTarget; - glm::quat _smoothOrientationInitial; - glm::quat _smoothOrientationTarget; - private: class AvatarEntityDataHash { public: diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index ccaa1d4fbc..97ee9f2aef 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -18,8 +18,16 @@ QString const ModelOverlay::TYPE = "model"; ModelOverlay::ModelOverlay() - : _model(std::make_shared(std::make_shared(), nullptr, this)), - _modelTextures(QVariantMap()) + : _smoothPositionTime(SMOOTH_TIME_POSITION), + _smoothPositionTimer(std::numeric_limits::max()), + _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), + _smoothOrientationTimer(std::numeric_limits::max()), + _smoothPositionInitial(), + _smoothPositionTarget(), + _smoothOrientationInitial(), + _smoothOrientationTarget(), + _model(std::make_shared(std::make_shared(), nullptr, this)), + _modelTextures(QVariantMap()) { _model->init(); _isLoaded = false; @@ -40,6 +48,35 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : } void ModelOverlay::update(float deltatime) { + if (_model && _model->isActive()) { + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + + if (_smoothPositionTimer < _smoothPositionTime) { + // Smooth the remote avatar movement. + _smoothPositionTimer+= deltatime; + if (_smoothPositionTimer < _smoothPositionTime) { + _model->setTranslation( + lerp(_smoothPositionInitial, + _smoothPositionTarget, + easeInOutQuad(glm::clamp(_smoothPositionTimer / _smoothPositionTime, 0.0f, 1.0f))) + ); + } + } + + if (_smoothOrientationTimer < _smoothOrientationTime) { + // Smooth the remote avatar movement. + _smoothOrientationTimer+= deltatime; + if (_smoothOrientationTimer < _smoothOrientationTime) { + _model->setRotation( + slerp(_smoothOrientationInitial, + _smoothOrientationTarget, + easeInOutQuad(glm::clamp(_smoothOrientationTimer / _smoothOrientationTime, 0.0f, 1.0f))) + ); + } + } + } + if (_updateModel) { _updateModel = false; _model->setSnapModelToCenter(true); @@ -173,8 +210,18 @@ ModelOverlay* ModelOverlay::createClone() const { void ModelOverlay::locationChanged(bool tellPhysics) { Base3DOverlay::locationChanged(tellPhysics); - if (_model && _model->isActive()) { - _model->setRotation(getRotation()); - _model->setTranslation(getPosition()); - } -} + if (!_model || !_model->isActive()) { + // If it's not active, don't care about it. + return; + } + + // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. + _smoothPositionInitial = _model->getTranslation(); + _smoothPositionTarget = getPosition(); + _smoothPositionTimer = 0.0f; + + // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. + _smoothOrientationInitial = _model->getRotation(); + _smoothOrientationTarget = getRotation(); + _smoothOrientationTimer = 0.0f; +} \ No newline at end of file diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index a3ddeed480..01861497a0 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -39,7 +39,32 @@ public: virtual bool addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) override; virtual void removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) override; - void locationChanged(bool tellPhysics) override; + void locationChanged(bool tellPhysics = true) override; + + // Basic ease-in-ease-out function for smoothing values. + inline float easeInOutQuad(float lerpValue) { + assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); + + if (lerpValue < 0.5f) { + return (2.0f * lerpValue * lerpValue); + } + + return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); + } + +protected: + const float SMOOTH_TIME_POSITION = 0.125f; + const float SMOOTH_TIME_ORIENTATION = 0.15f; + + // Smoothing data for blending from one position/orientation to another on remote agents. + float _smoothPositionTime; + float _smoothPositionTimer; + float _smoothOrientationTime; + float _smoothOrientationTimer; + glm::vec3 _smoothPositionInitial; + glm::vec3 _smoothPositionTarget; + glm::quat _smoothOrientationInitial; + glm::quat _smoothOrientationTarget; private: From 41cd0d79b29f1ac7ff4f7a11f463b960cbe6e598 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 10 Apr 2017 17:24:58 -0700 Subject: [PATCH 003/135] Set max packet per second on the agent --- assignment-client/src/Agent.cpp | 1 + assignment-client/src/scripts/EntityScriptServer.h | 3 --- libraries/script-engine/src/ScriptEngine.h | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 46e826c596..7a25605d6a 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -56,6 +56,7 @@ Agent::Agent(ReceivedMessage& message) : ThreadedAssignment(message), _receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES) { + _entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT); DependencyManager::get()->setPacketSender(&_entityEditSender); ResourceManager::init(); diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index a468e62958..709c47503b 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -24,9 +24,6 @@ #include #include -static const int DEFAULT_MAX_ENTITY_PPS = 9000; -static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900; - class EntityScriptServer : public ThreadedAssignment { Q_OBJECT diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 5ea8d052e9..671a24e000 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -48,6 +48,8 @@ class QScriptEngineDebugger; static const QString NO_SCRIPT(""); static const int SCRIPT_FPS = 60; +static const int DEFAULT_MAX_ENTITY_PPS = 9000; +static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900; class CallbackData { public: From 53f47c132f61715221096c0aa234e9e6dd375ded Mon Sep 17 00:00:00 2001 From: trent Date: Thu, 27 Apr 2017 16:12:25 -0400 Subject: [PATCH 004/135] Avatar snap-turn rotation smoothing (sent out from the local client outward). --- interface/src/avatar/MyAvatar.cpp | 15 ++++++++++++++- libraries/avatars/src/AvatarData.cpp | 17 ++++++++++++++++- libraries/avatars/src/AvatarData.h | 21 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f4f078c9e5..6300df1634 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -375,6 +375,10 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau); + if (_smoothOrientationTimer < _smoothOrientationTime) { + _smoothOrientationTimer = min(_smoothOrientationTimer + deltaTime, _smoothOrientationTime); + } + #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y)); DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f)); @@ -1742,8 +1746,10 @@ void MyAvatar::updateOrientation(float deltaTime) { // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another // snap turn every half second. + bool snapTurn = false; if (getDriveKey(STEP_YAW) != 0.0f) { totalBodyYaw += getDriveKey(STEP_YAW); + snapTurn = true; } // use head/HMD orientation to turn while flying @@ -1776,10 +1782,17 @@ void MyAvatar::updateOrientation(float deltaTime) { totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI)); } - // update body orientation by movement inputs + glm::quat initialOrientation = getOrientation(); setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)))); + if (snapTurn) { + // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. + _smoothOrientationInitial = initialOrientation; + _smoothOrientationTarget = getOrientation(); + _smoothOrientationTimer = 0.0f; + } + getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); if (qApp->isHMDMode()) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 20894104ff..a38c269831 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -67,7 +67,11 @@ AvatarData::AvatarData() : _displayNameAlpha(1.0f), _errorLogExpiry(0), _owningAvatarMixer(), - _targetVelocity(0.0f) + _targetVelocity(0.0f), + _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), + _smoothOrientationTimer(std::numeric_limits::max()), + _smoothOrientationInitial(), + _smoothOrientationTarget() { setBodyPitch(0.0f); setBodyYaw(-90.0f); @@ -1460,6 +1464,17 @@ void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& ide packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.sessionDisplayName >> identityOut.avatarEntityData; } +glm::quat AvatarData::getLocalOrientation() const { + if (!isMyAvatar() || (_smoothOrientationTimer > _smoothOrientationTime)) { + return (SpatiallyNestable::getLocalOrientation()); + } + + // Smooth the remote avatar movement. + float t = _smoothOrientationTimer / _smoothOrientationTime; + float l = easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); + return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, l)); +} + static const QUrl emptyURL(""); QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { // We don't put file urls on the wire, but instead convert to empty. diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 8319eb5249..b68de2397b 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -603,6 +603,9 @@ public: bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_UPDATE_TIMEOUT; } + // Overload the local orientation function for this. + virtual glm::quat getLocalOrientation() const override; + static const float OUT_OF_VIEW_PENALTY; static void sortAvatars( @@ -619,7 +622,16 @@ public: static float _avatarSortCoefficientCenter; static float _avatarSortCoefficientAge; + // Basic ease-in-ease-out function for smoothing values. + static inline float easeInOutQuad(float lerpValue) { + assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); + if (lerpValue < 0.5f) { + return (2.0f * lerpValue * lerpValue); + } + + return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); + } signals: void displayNameChanged(); @@ -778,6 +790,15 @@ protected: quint64 _audioLoudnessChanged { 0 }; float _audioAverageLoudness { 0.0f }; + // Smoothing. + const float SMOOTH_TIME_ORIENTATION = 0.15f; + + // Smoothing data for blending from one position/orientation to another on remote agents. + float _smoothOrientationTime; + float _smoothOrientationTimer; + glm::quat _smoothOrientationInitial; + glm::quat _smoothOrientationTarget; + private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; From d446640bc8803db5d9fd474775ae80ff2ed5ae80 Mon Sep 17 00:00:00 2001 From: trent Date: Mon, 1 May 2017 13:28:26 -0400 Subject: [PATCH 005/135] Fixed tab issues. --- libraries/avatars/src/AvatarData.cpp | 22 +++++++-------- libraries/avatars/src/AvatarData.h | 40 ++++++++++++++-------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a74bcaf189..52a2849ac4 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -68,10 +68,10 @@ AvatarData::AvatarData() : _errorLogExpiry(0), _owningAvatarMixer(), _targetVelocity(0.0f), - _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), - _smoothOrientationTimer(std::numeric_limits::max()), - _smoothOrientationInitial(), - _smoothOrientationTarget() + _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), + _smoothOrientationTimer(std::numeric_limits::max()), + _smoothOrientationInitial(), + _smoothOrientationTarget() { setBodyPitch(0.0f); setBodyYaw(-90.0f); @@ -1496,14 +1496,14 @@ void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& ide } glm::quat AvatarData::getLocalOrientation() const { - if (!isMyAvatar() || (_smoothOrientationTimer > _smoothOrientationTime)) { - return (SpatiallyNestable::getLocalOrientation()); - } + if (!isMyAvatar() || (_smoothOrientationTimer > _smoothOrientationTime)) { + return (SpatiallyNestable::getLocalOrientation()); + } - // Smooth the remote avatar movement. - float t = _smoothOrientationTimer / _smoothOrientationTime; - float l = easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); - return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, l)); + // Smooth the remote avatar movement. + float t = _smoothOrientationTimer / _smoothOrientationTime; + float l = easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); + return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, l)); } static const QUrl emptyURL(""); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a18af4ea38..278d150259 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -602,10 +602,10 @@ public: return _lastSentJointData; } - // Overload the local orientation function for this. - virtual glm::quat getLocalOrientation() const override; + // Overload the local orientation function for this. + virtual glm::quat getLocalOrientation() const override; - static const float OUT_OF_VIEW_PENALTY; + static const float OUT_OF_VIEW_PENALTY; static void sortAvatars( QList avatarList, @@ -621,18 +621,18 @@ public: static float _avatarSortCoefficientCenter; static float _avatarSortCoefficientAge; - // Basic ease-in-ease-out function for smoothing values. - static inline float easeInOutQuad(float lerpValue) { - assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); + // Basic ease-in-ease-out function for smoothing values. + static inline float easeInOutQuad(float lerpValue) { + assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); - if (lerpValue < 0.5f) { - return (2.0f * lerpValue * lerpValue); - } + if (lerpValue < 0.5f) { + return (2.0f * lerpValue * lerpValue); + } - return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); - } + return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); + } - bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called + bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called void markIdentityDataChanged() { _identityDataChanged = true; _identityUpdatedAt = usecTimestampNow(); @@ -795,16 +795,16 @@ protected: quint64 _audioLoudnessChanged { 0 }; float _audioAverageLoudness { 0.0f }; - // Smoothing. - const float SMOOTH_TIME_ORIENTATION = 0.15f; + // Smoothing. + const float SMOOTH_TIME_ORIENTATION = 0.15f; - // Smoothing data for blending from one position/orientation to another on remote agents. - float _smoothOrientationTime; - float _smoothOrientationTimer; - glm::quat _smoothOrientationInitial; - glm::quat _smoothOrientationTarget; + // Smoothing data for blending from one position/orientation to another on remote agents. + float _smoothOrientationTime; + float _smoothOrientationTimer; + glm::quat _smoothOrientationInitial; + glm::quat _smoothOrientationTarget; - bool _identityDataChanged { false }; + bool _identityDataChanged { false }; quint64 _identityUpdatedAt { 0 }; private: From 4d7eeed343a4122d2df885e5fb854eaf8325d164 Mon Sep 17 00:00:00 2001 From: trent Date: Mon, 1 May 2017 15:21:25 -0400 Subject: [PATCH 006/135] Marking MyAvatar's rotation as dirty if smoothing is going down. --- interface/src/avatar/MyAvatar.cpp | 1 + libraries/shared/src/SpatiallyNestable.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d42c153505..e71d3cc920 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -376,6 +376,7 @@ void MyAvatar::update(float deltaTime) { _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau); if (_smoothOrientationTimer < _smoothOrientationTime) { + rotationForceChange(); _smoothOrientationTimer = min(_smoothOrientationTimer + deltaTime, _smoothOrientationTime); } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 820c8685d7..08dd75b0ac 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -183,6 +183,8 @@ public: bool tranlationChangedSince(quint64 time) const { return _translationChanged > time; } bool rotationChangedSince(quint64 time) const { return _rotationChanged > time; } + inline void rotationForceChange() { _rotationChanged = usecTimestampNow(); } + protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; From 8f34ec4c7ff647a26423cc5c9f05627edbd45571 Mon Sep 17 00:00:00 2001 From: trent Date: Tue, 2 May 2017 13:41:34 -0400 Subject: [PATCH 007/135] Iteration on the snap-turn smoothing based on feedback from Andrew. Also additional cleanup. --- interface/src/avatar/Avatar.cpp | 2 - interface/src/avatar/Avatar.h | 1 - interface/src/avatar/MyAvatar.cpp | 6 ++- interface/src/avatar/MyAvatar.h | 20 ++++++++ interface/src/ui/overlays/ModelOverlay.cpp | 55 +--------------------- interface/src/ui/overlays/ModelOverlay.h | 27 ----------- libraries/avatars/src/AvatarData.cpp | 17 ++----- libraries/avatars/src/AvatarData.h | 24 +--------- libraries/shared/src/SpatiallyNestable.h | 2 - 9 files changed, 32 insertions(+), 122 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 032a91dc3e..7483048e6a 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1361,13 +1361,11 @@ glm::quat Avatar::getUncachedRightPalmRotation() const { } void Avatar::setPosition(const glm::vec3& position) { - // This is the local avatar, no need to handle any position smoothing. AvatarData::setPosition(position); updateAttitude(); } void Avatar::setOrientation(const glm::quat& orientation) { - // This is the local avatar, no need to handle any position smoothing. AvatarData::setOrientation(orientation); updateAttitude(); } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 181865adcd..be055c495b 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -34,7 +34,6 @@ namespace render { } static const float SCALING_RATIO = .05f; -static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1 extern const float CHAT_MESSAGE_SCALE; extern const float CHAT_MESSAGE_HEIGHT; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index de8a9e46b4..2043a6b9b6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -117,6 +117,10 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false), + _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), + _smoothOrientationTimer(std::numeric_limits::max()), + _smoothOrientationInitial(), + _smoothOrientationTarget(), _hmdSensorMatrix(), _hmdSensorOrientation(), _hmdSensorPosition(), @@ -388,7 +392,7 @@ void MyAvatar::update(float deltaTime) { _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau); if (_smoothOrientationTimer < _smoothOrientationTime) { - rotationForceChange(); + _rotationChanged = true; _smoothOrientationTimer = min(_smoothOrientationTimer + deltaTime, _smoothOrientationTime); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6a1e457a97..1d26a2f3f9 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -577,6 +577,17 @@ private: void setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visiblity); + // Basic ease-in-ease-out function for smoothing values. + static inline float easeInOutQuad(float lerpValue) { + assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); + + if (lerpValue < 0.5f) { + return (2.0f * lerpValue * lerpValue); + } + + return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); + } + private: virtual void updatePalms() override {} @@ -626,6 +637,15 @@ private: Setting::Handle _realWorldFieldOfView; Setting::Handle _useAdvancedMovementControls; + // Smoothing. + const float SMOOTH_TIME_ORIENTATION = 0.15f; + + // Smoothing data for blending from one position/orientation to another on remote agents. + float _smoothOrientationTime; + float _smoothOrientationTimer; + glm::quat _smoothOrientationInitial; + glm::quat _smoothOrientationTarget; + // private methods void updateOrientation(float deltaTime); void updateActionMotor(float deltaTime); diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 0a05efa3bf..6043e22978 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -18,16 +18,8 @@ QString const ModelOverlay::TYPE = "model"; ModelOverlay::ModelOverlay() - : _smoothPositionTime(SMOOTH_TIME_POSITION), - _smoothPositionTimer(std::numeric_limits::max()), - _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), - _smoothOrientationTimer(std::numeric_limits::max()), - _smoothPositionInitial(), - _smoothPositionTarget(), - _smoothOrientationInitial(), - _smoothOrientationTarget(), - _model(std::make_shared(std::make_shared(), nullptr, this)), - _modelTextures(QVariantMap()) + : _model(std::make_shared(std::make_shared(), nullptr, this)), + _modelTextures(QVariantMap()) { _model->init(); _isLoaded = false; @@ -51,30 +43,6 @@ void ModelOverlay::update(float deltatime) { if (_model && _model->isActive()) { _model->setRotation(getRotation()); _model->setTranslation(getPosition()); - - if (_smoothPositionTimer < _smoothPositionTime) { - // Smooth the remote avatar movement. - _smoothPositionTimer+= deltatime; - if (_smoothPositionTimer < _smoothPositionTime) { - _model->setTranslation( - lerp(_smoothPositionInitial, - _smoothPositionTarget, - easeInOutQuad(glm::clamp(_smoothPositionTimer / _smoothPositionTime, 0.0f, 1.0f))) - ); - } - } - - if (_smoothOrientationTimer < _smoothOrientationTime) { - // Smooth the remote avatar movement. - _smoothOrientationTimer+= deltatime; - if (_smoothOrientationTimer < _smoothOrientationTime) { - _model->setRotation( - slerp(_smoothOrientationInitial, - _smoothOrientationTarget, - easeInOutQuad(glm::clamp(_smoothOrientationTimer / _smoothOrientationTime, 0.0f, 1.0f))) - ); - } - } } if (_updateModel) { @@ -306,23 +274,4 @@ bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const g ModelOverlay* ModelOverlay::createClone() const { return new ModelOverlay(this); -} - -void ModelOverlay::locationChanged(bool tellPhysics) { - Base3DOverlay::locationChanged(tellPhysics); - - if (!_model || !_model->isActive()) { - // If it's not active, don't care about it. - return; - } - - // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. - _smoothPositionInitial = _model->getTranslation(); - _smoothPositionTarget = getPosition(); - _smoothPositionTimer = 0.0f; - - // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. - _smoothOrientationInitial = _model->getRotation(); - _smoothOrientationTarget = getRotation(); - _smoothOrientationTimer = 0.0f; } \ No newline at end of file diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 4a73c42d7e..6991a11bb2 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -39,33 +39,6 @@ public: virtual bool addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) override; virtual void removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) override; - void locationChanged(bool tellPhysics = true) override; - - // Basic ease-in-ease-out function for smoothing values. - inline float easeInOutQuad(float lerpValue) { - assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); - - if (lerpValue < 0.5f) { - return (2.0f * lerpValue * lerpValue); - } - - return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); - } - -protected: - const float SMOOTH_TIME_POSITION = 0.125f; - const float SMOOTH_TIME_ORIENTATION = 0.15f; - - // Smoothing data for blending from one position/orientation to another on remote agents. - float _smoothPositionTime; - float _smoothPositionTimer; - float _smoothOrientationTime; - float _smoothOrientationTimer; - glm::vec3 _smoothPositionInitial; - glm::vec3 _smoothPositionTarget; - glm::quat _smoothOrientationInitial; - glm::quat _smoothOrientationTarget; - protected: // helper to extract metadata from our Model's rigged joints template using mapFunction = std::function; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a8856d2da1..3c88ee0749 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -67,11 +67,7 @@ AvatarData::AvatarData() : _displayNameAlpha(1.0f), _errorLogExpiry(0), _owningAvatarMixer(), - _targetVelocity(0.0f), - _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), - _smoothOrientationTimer(std::numeric_limits::max()), - _smoothOrientationInitial(), - _smoothOrientationTarget() + _targetVelocity(0.0f) { setBodyPitch(0.0f); setBodyYaw(-90.0f); @@ -1495,15 +1491,8 @@ void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& ide } -glm::quat AvatarData::getLocalOrientation() const { - if (!isMyAvatar() || (_smoothOrientationTimer > _smoothOrientationTime)) { - return (SpatiallyNestable::getLocalOrientation()); - } - - // Smooth the remote avatar movement. - float t = _smoothOrientationTimer / _smoothOrientationTime; - float l = easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); - return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, l)); +glm::quat AvatarData::getOrientationOutbound() const { + return (getLocalOrientation()); } static const QUrl emptyURL(""); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 4fbbd84792..2959d47347 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -602,8 +602,8 @@ public: return _lastSentJointData; } - // Overload the local orientation function for this. - virtual glm::quat getLocalOrientation() const override; + // A method intended to be overriden by MyAvatar for polling orientation for network transmission. + virtual glm::quat getOrientationOutbound() const; static const float OUT_OF_VIEW_PENALTY; @@ -621,17 +621,6 @@ public: static float _avatarSortCoefficientCenter; static float _avatarSortCoefficientAge; - // Basic ease-in-ease-out function for smoothing values. - static inline float easeInOutQuad(float lerpValue) { - assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); - - if (lerpValue < 0.5f) { - return (2.0f * lerpValue * lerpValue); - } - - return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); - } - bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called void markIdentityDataChanged() { _identityDataChanged = true; @@ -795,15 +784,6 @@ protected: quint64 _audioLoudnessChanged { 0 }; float _audioAverageLoudness { 0.0f }; - // Smoothing. - const float SMOOTH_TIME_ORIENTATION = 0.15f; - - // Smoothing data for blending from one position/orientation to another on remote agents. - float _smoothOrientationTime; - float _smoothOrientationTimer; - glm::quat _smoothOrientationInitial; - glm::quat _smoothOrientationTarget; - bool _identityDataChanged { false }; quint64 _identityUpdatedAt { 0 }; diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 08dd75b0ac..820c8685d7 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -183,8 +183,6 @@ public: bool tranlationChangedSince(quint64 time) const { return _translationChanged > time; } bool rotationChangedSince(quint64 time) const { return _rotationChanged > time; } - inline void rotationForceChange() { _rotationChanged = usecTimestampNow(); } - protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; From 49881b5ea24f8cae93d60cd4323e00a9d94f6b79 Mon Sep 17 00:00:00 2001 From: trent Date: Tue, 2 May 2017 13:55:32 -0400 Subject: [PATCH 008/135] Actually the full set of changes. Kids: don't forget to pop your stash. --- interface/src/avatar/MyAvatar.cpp | 11 +++++++++++ interface/src/avatar/MyAvatar.h | 2 ++ libraries/avatars/src/AvatarData.cpp | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2043a6b9b6..98afdb56c0 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -268,6 +268,17 @@ QVariant MyAvatar::getOrientationVar() const { return quatToVariant(Avatar::getOrientation()); } +glm::quat MyAvatar::getOrientationOutbound() const { + // Allows MyAvatar to send out smoothed data to remote agents if required. + if (_smoothOrientationTimer > _smoothOrientationTime) { + return (getLocalOrientation()); + } + + // Smooth the remote avatar movement. + float t = _smoothOrientationTimer / _smoothOrientationTime; + float l = easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); + return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, l)); +} // virtual void MyAvatar::simulateAttachments(float deltaTime) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1d26a2f3f9..c69359cc4c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -187,6 +187,8 @@ public: Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar); Q_INVOKABLE QVariant getOrientationVar() const; + // A method intended to be overriden by MyAvatar for polling orientation for network transmission. + virtual glm::quat getOrientationOutbound() const override; // Pass a recent sample of the HMD to the avatar. // This can also update the avatar's position to follow the HMD diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 3c88ee0749..7eedbcd216 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -312,7 +312,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarOrientation) { auto startSection = destinationBuffer; - auto localOrientation = getLocalOrientation(); + auto localOrientation = getOrientationOutbound(); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); int numBytes = destinationBuffer - startSection; From f79e032841bb01e65a1294318ca4a8ce7f24ecc7 Mon Sep 17 00:00:00 2001 From: trent Date: Tue, 2 May 2017 14:48:27 -0400 Subject: [PATCH 009/135] Silly online git merge tool. You are silly. Errors fixed. --- interface/src/avatar/Avatar.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 1adc83a9f4..867ccde68c 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -234,6 +234,18 @@ public: bool hasNewJointData() const { return _hasNewJointData; } + float getBoundingRadius() const; + + void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene); + void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene); + bool isInScene() const { return render::Item::isValidID(_renderItemID); } + bool isMoving() const { return _moving; } + + //void setMotionState(AvatarMotionState* motionState); + void setPhysicsCallback(AvatarPhysicsCallback cb); + void addPhysicsFlags(uint32_t flags); + bool isInPhysicsSimulation() const { return _physicsCallback != nullptr; } + public slots: // FIXME - these should be migrated to use Pose data instead From 0a5dfbb9af15bf2373eff2070903826d074bdf0a Mon Sep 17 00:00:00 2001 From: Trent Polack Date: Tue, 2 May 2017 19:10:33 -0400 Subject: [PATCH 010/135] Changed l to interp. In my defense, if anyone ever looked at it and thought the 'l' was a '1', they should never be programming unless they used a special language where you could reassign numbers. --- interface/src/avatar/MyAvatar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 98afdb56c0..29848da89d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -276,8 +276,8 @@ glm::quat MyAvatar::getOrientationOutbound() const { // Smooth the remote avatar movement. float t = _smoothOrientationTimer / _smoothOrientationTime; - float l = easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); - return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, l)); + float interp = easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); + return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, interp)); } // virtual From 557a8fffd77615655babf9861b5a5142681c4d85 Mon Sep 17 00:00:00 2001 From: trent Date: Tue, 2 May 2017 19:56:36 -0400 Subject: [PATCH 011/135] Addressing some PR feedback; moved easeInEaseOut to Interpolate class. My function is faster than andrew's. :D --- interface/src/avatar/MyAvatar.cpp | 6 +++--- interface/src/avatar/MyAvatar.h | 11 ----------- interface/src/ui/overlays/ModelOverlay.cpp | 18 +++++++++++------- interface/src/ui/overlays/ModelOverlay.h | 2 ++ libraries/shared/src/Interpolate.cpp | 10 ++++++++++ libraries/shared/src/Interpolate.h | 3 +++ 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 29848da89d..970ef51aad 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -276,7 +276,7 @@ glm::quat MyAvatar::getOrientationOutbound() const { // Smooth the remote avatar movement. float t = _smoothOrientationTimer / _smoothOrientationTime; - float interp = easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); + float interp = Interpolate::easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, interp)); } @@ -1863,13 +1863,13 @@ void MyAvatar::updateOrientation(float deltaTime) { } // update body orientation by movement inputs - glm::quat initialOrientation = getOrientation(); + glm::quat initialOrientation = getOrientationOutbound(); setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)))); if (snapTurn) { // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. _smoothOrientationInitial = initialOrientation; - _smoothOrientationTarget = getOrientation(); + _smoothOrientationTarget = getOrientation(); _smoothOrientationTimer = 0.0f; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c69359cc4c..abcfbe865d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -579,17 +579,6 @@ private: void setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visiblity); - // Basic ease-in-ease-out function for smoothing values. - static inline float easeInOutQuad(float lerpValue) { - assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); - - if (lerpValue < 0.5f) { - return (2.0f * lerpValue * lerpValue); - } - - return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); - } - private: virtual void updatePalms() override {} diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 6043e22978..e993166558 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -18,7 +18,7 @@ QString const ModelOverlay::TYPE = "model"; ModelOverlay::ModelOverlay() - : _model(std::make_shared(std::make_shared(), nullptr, this)), + : _model(std::make_shared(std::make_shared(), nullptr, this)), _modelTextures(QVariantMap()) { _model->init(); @@ -40,11 +40,6 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : } void ModelOverlay::update(float deltatime) { - if (_model && _model->isActive()) { - _model->setRotation(getRotation()); - _model->setTranslation(getPosition()); - } - if (_updateModel) { _updateModel = false; _model->setSnapModelToCenter(true); @@ -274,4 +269,13 @@ bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const g ModelOverlay* ModelOverlay::createClone() const { return new ModelOverlay(this); -} \ No newline at end of file +} + +void ModelOverlay::locationChanged(bool tellPhysics) { + Base3DOverlay::locationChanged(tellPhysics); + + if (_model && _model->isActive()) { + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + } +} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 6991a11bb2..8afe9a20b6 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -39,6 +39,8 @@ public: virtual bool addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) override; virtual void removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) override; + void locationChanged(bool tellPhysics) override; + protected: // helper to extract metadata from our Model's rigged joints template using mapFunction = std::function; diff --git a/libraries/shared/src/Interpolate.cpp b/libraries/shared/src/Interpolate.cpp index ba93a21a8e..35c164f0f2 100644 --- a/libraries/shared/src/Interpolate.cpp +++ b/libraries/shared/src/Interpolate.cpp @@ -77,3 +77,13 @@ float Interpolate::calculateFadeRatio(quint64 start) { const float EASING_SCALE = 1.001f; return std::min(EASING_SCALE * fadeRatio, 1.0f); } + +float Interpolate::easeInOutQuad(float lerpValue) { + assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f))); + + if (lerpValue < 0.5f) { + return (2.0f * lerpValue * lerpValue); + } + + return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f); +} \ No newline at end of file diff --git a/libraries/shared/src/Interpolate.h b/libraries/shared/src/Interpolate.h index 79ebd2f7fc..fca6a40b61 100644 --- a/libraries/shared/src/Interpolate.h +++ b/libraries/shared/src/Interpolate.h @@ -30,6 +30,9 @@ public: static float simpleNonLinearBlend(float fraction); static float calculateFadeRatio(quint64 start); + + // Basic ease-in-ease-out function for smoothing values. + static float easeInOutQuad(float lerpValue); }; #endif // hifi_Interpolate_h From 24f2678cdd17818084300525c41d8a6abbe7ee82 Mon Sep 17 00:00:00 2001 From: trent Date: Wed, 3 May 2017 12:05:21 -0400 Subject: [PATCH 012/135] Adjusted the smooth time for the snap-turn remote view. Fixed tab issue. --- interface/src/avatar/MyAvatar.cpp | 6 +++--- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 970ef51aad..5dd29136e4 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1868,10 +1868,10 @@ void MyAvatar::updateOrientation(float deltaTime) { if (snapTurn) { // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. - _smoothOrientationInitial = initialOrientation; + _smoothOrientationInitial = initialOrientation; _smoothOrientationTarget = getOrientation(); - _smoothOrientationTimer = 0.0f; - } + _smoothOrientationTimer = 0.0f; + } getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index abcfbe865d..afe7fd017a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -629,7 +629,7 @@ private: Setting::Handle _useAdvancedMovementControls; // Smoothing. - const float SMOOTH_TIME_ORIENTATION = 0.15f; + const float SMOOTH_TIME_ORIENTATION = 0.5f; // Smoothing data for blending from one position/orientation to another on remote agents. float _smoothOrientationTime; From fc3cce6b738590aae6bd6026fcd6d3bb4c21f648 Mon Sep 17 00:00:00 2001 From: trent Date: Wed, 3 May 2017 12:35:35 -0400 Subject: [PATCH 013/135] Fixed the silly and stupid subtle issue that was causing the bug in inconsistencies. --- interface/src/avatar/MyAvatar.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5dd29136e4..2f112924f4 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -404,7 +404,12 @@ void MyAvatar::update(float deltaTime) { if (_smoothOrientationTimer < _smoothOrientationTime) { _rotationChanged = true; - _smoothOrientationTimer = min(_smoothOrientationTimer + deltaTime, _smoothOrientationTime); + _smoothOrientationTimer+= deltaTime; + + if (_smoothOrientationTimer >= _smoothOrientationTime) { + // Take it back to astronomical value land. + _smoothOrientationTimer = std::numeric_limits::max(); + } } #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE From d7ed727faf586024878cb50d47a8373f2277411e Mon Sep 17 00:00:00 2001 From: trent Date: Wed, 3 May 2017 16:56:18 -0400 Subject: [PATCH 014/135] Adjustments based on PR feedback, though it looks like this snap-turn-snafu-story is not yet complete. --- interface/src/avatar/MyAvatar.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2f112924f4..4926368f4d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -117,7 +117,6 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false), - _smoothOrientationTime(SMOOTH_TIME_ORIENTATION), _smoothOrientationTimer(std::numeric_limits::max()), _smoothOrientationInitial(), _smoothOrientationTarget(), @@ -270,12 +269,12 @@ QVariant MyAvatar::getOrientationVar() const { glm::quat MyAvatar::getOrientationOutbound() const { // Allows MyAvatar to send out smoothed data to remote agents if required. - if (_smoothOrientationTimer > _smoothOrientationTime) { + if (_smoothOrientationTimer > SMOOTH_TIME_ORIENTATION) { return (getLocalOrientation()); } // Smooth the remote avatar movement. - float t = _smoothOrientationTimer / _smoothOrientationTime; + float t = _smoothOrientationTimer / SMOOTH_TIME_ORIENTATION; float interp = Interpolate::easeInOutQuad(glm::clamp(t, 0.0f, 1.0f)); return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, interp)); } @@ -402,14 +401,9 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau); - if (_smoothOrientationTimer < _smoothOrientationTime) { + if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) { _rotationChanged = true; _smoothOrientationTimer+= deltaTime; - - if (_smoothOrientationTimer >= _smoothOrientationTime) { - // Take it back to astronomical value land. - _smoothOrientationTimer = std::numeric_limits::max(); - } } #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE From 40908fb1a31bd884f87e21c4e524275f8334471b Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 4 May 2017 19:21:37 +0100 Subject: [PATCH 015/135] create a smoke texture --- interface/resources/images/Smoke.png | Bin 0 -> 131940 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 interface/resources/images/Smoke.png diff --git a/interface/resources/images/Smoke.png b/interface/resources/images/Smoke.png new file mode 100644 index 0000000000000000000000000000000000000000..d1b4d8cac9be3a6c7e0bf3f935e09c74d2df2703 GIT binary patch literal 131940 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

5Nc5#J~dS7J53Ck8T^!6SKGgx4v;e^## zuv%~8#TC%w1iK1H3r6iAtFoSi4S%1Z8O}rSL zn33~<%_f1UAQeGkhD$~+geZXG#SsY}C3EES^{HY8V>KrDf;SfqGmT5=8rot{4vb|) zmM0G5jYuf>A}&6B3sGikC=d~qidh^kTBFJ_T{8cc>Oded5QGM2Z%KZ3>+yAVMG{AA zyZ9O!UzRn*=?t3YYdDnueuM`-k!Gjf(i_!LzKJ0o)dTjpuTdR#%5mSUkM zKrKKdFLeZN4$2%e7w>S%aCRIH6(P4>2DWfgcjq9bdLoA#&&(M_<|Huk98+2|Bl5}+ zQ)^(tq0RjW%&T1TIWfk0Zp6&zW^F&s^~HX_TlF^okjT!7$j;9%R@bgxtwiGP(B9N? zAzfbjmj}SF1_Zf$`Le4j9zT9eU;EnE+|2O3?|qM3Yk2d`H|_cJ=k)Dwf2-fQbKC#s zZ~kU{@#2MCzkYrGo!|MLdUp0g_WMab{^;Z3t+(Hr0Y3lx@Bi@EQjz}Fn{WQC3TKfJ9lnB`Op9PZ-0H74xjF)>FCPUOaGWIaaAPx5b-H0{WekV`;H8GyItQl(VS2|7g44eKM8-SFT(e z-1@NF@5(&S6A{bh%a^CPY@&8s4gp)t)SvIi_3PK&J*Lo)9zEjAmoNK+2M;)Fqix)5W=FQi7&e?C?ylEeQ{IT4;dDEXgd(KIco*bXrJWulLzy9^R zj~+fg{^)~`Z_o35>&m4|yRUxzs~>H*yWzKg`?vqspa1+9zr9}J$@YBvwQ(Gfa<&C3 zNhxI_7GgEb+9AY5nc&UIWUi?WTfPjj4%GpG~X)(Vz0f`wMhD-@lZOCRw ziJ-_{QlazhbIki0!!RI~jHBZbaA3RLW7_Y*;wZy_ECH8iP5}f-3AI*~GRD-}9KCaN zsTt^#At%9M-wKoW7pmnUr z5h9Fvnu7iV#-m4%aryFPh%joKV>eF$#ysq1fCM>(x{ujjwH{6})rTP_=jO|=@ksaq zqqYjSNCaky%Ck2@uT%Ky7fOIFMjoORKKt5`GND^+N5BH^aTB-J01B9hBmq@|A>5cT zmkCXTbF@m@6(o@!438LdK}J`DLy>Zd(q0XuvHrB>-(_iWdTnq>2)mdTJX#>ui3q)R zI17>!9O`aBx8`KQn^ZL;Bh5^TlpuoSC`8w{^oD$)2C1A;lqb=_wHS}a^}VB5QUMovUsyNCQUd|3>l09iBxGKwVWt{x0} zpd?;caoKb9#|8%%Ni?`7@jxpCEC9lVf{!05LGl z4ZU`pTv-P~%ROam)(38>EN%X`K@)Q_y`poQ&?VrRf&bWB8JGEP|3_CE86fIxt;mMm%p!@Rm$&eYknQpzR* z%Xt8i8@m`WyCdwLUA2m^5y~+L^T}p&KDGAH%+3z;JRa&{CdaPU>2Q8_-c`GJ*W+p0 zf4O&C@3*@T9`-J~?S*yqRI<$9`qmBhmu86N<^PpifP44uy~GJ&7zRrz(Y0&WthJ`w z?Urucx@E`5$9{Hprfu3x|2Mfigs{pcrO&N*K^e*EnCU;pd>>HqrZ<458#7qD(+&%7oP9%kiTzD7;B1)bTb1sCO1by~! z?B$HkuLLHcd7fe&2f@Okz;3?-RKNpcTo0J0jx^-B#T2iSd1CJxgGX0` zOA6X8g=vO?#S9c-8nNX@%#$fl+rklusaUohi@1v!VMWdbf+0jlNigJyB-Wbu&la_E+cDJqb;qc(?jjnES! zw06Ld2Q)H-MZxf@Y!ddF+h0lz4!R-x8JstlM~B1)6=I< z`(ORlUzN>f10v#YfBW0*_U+p?48zk~w_dmR-uqd8{r2tM>FG(U^PJv$|Gg__#vgzD z(bwO2mPso$$zV=oFwhWaeO$>^X))bCGQrNUlJly3uiDC%K(50?o8y& zK%zt?7nr#ZDZ2<8b75KZ(&imD$5a_2C`E`UVTMz;8Hoi$5s(ZaKrsfGpMdY z+);bV-#!%0yDsB=eNp73$C!O8InHmeboIE+-i!#$uP zb)G|8QELqTjAA{>gq(RX93^lTI4v3i#L(2E1_*YdJKUQmWL0P+ltB9+#Vqtab5pUL zxDd#5=1%TxWEP3?PM&#yL6P}<3{eSe{!Xi?C-w&&o7RTkL`Qk``b%q#xGu+Ng0L*=jZ2_-+%x8s{pS`$?f#g>CZm< z?BVaNHLTSf001BWNkl(0Jw+{GZW{~mUA}b zJRqe+%uHk+@l&ig;1JVrj_f@VazUn)3yzOQytsIQd8!yz1?$bx!ljc)=9w$%`4+V` zXpLkZRiKOlP@n*ZKzP5stSL-8AQW+2ONj|JNJ;T<0_fGiiNFrj<)seuN)PwjV2g9G z!!0aJ=oMuc!%Sxel7!l49OZcV=h~p=7_ovo8$ViF=qW~c(zJBTfE|B|CF6_+HS&A@8R3u7_XxI@Yq%82R}!VG1Gt1WO< zU_Ml&yvV`D7ySaG*YMUc94@qgQUR9aF$-8K0X5Vf3;`i^l5B;@m<#2^Zo+EDv#={O zRc0cWjtRTB}1=@~PH?yE`Y=(u-q-LvQ`*YBfGRKf5Sq{&XD6zITDD{(Q4K zdac*)H?H59uUx&XKfn8P+N@T)lapgxv^y_10C))l`ITA#GsC@m_xzoA-XT@RvuDrz z_kaKQaqr$eRaL%r?HbnWwGYGK&z?QAJ9qBTrAw#y!WX_!zxK7S*%!a~MSSwfr}K7u zk#bJ{?z``vf8{H`*8l$dKNx#&$rNS1-sqDjPp@7+z5MvCx8C~n>9c3`>C>m*Njb^Y zt5<$9P1BpL?USl)Vhll?Sx8vO0XGdItpiTf@zR1Pi%(=2iUSfZDP|;2BuRWI1znWJ z&49VaFf9ami9`si)wt;13otM)onFGx(GfCb)E+?*8ruR8IV2Z&)5w3}9D`&dgDByk47V6CvM|ajEy}wDP7%v#7O?{aLSEc} zZDL3^m^*r#Fjs?X3|`AHU^Pa#38#ckhKv0!kY27)eJwAqEewQi5de!t2-f-(Za`wt z5{l|jSapYlOgTv$%qkTL1g+uS6r@g+EoW8`5h-{*WOSg4$R>-h6N@|6B9x0{ONq31 z?_fFf-n+ZbtE1roAD}a^b;pLtWqovXIv=J>s=GSZ!$Et0M! z`}OHJe&e6Mgo(Ti_@!3@)BjpK@GrLkpM3HO5fMa$hzNi9hks}Q=((txDe?Ee|9$!0-~C;E_uY5EYuB#oZnsV2xa#k}|MT(Dqeu4Q z#raw!&ENj*Z~o$^KYj1U_3PKS7Z(?oKK$^*x6jYd-yu%iTU%Xz{q?^;91ahLq1=D? z@X>$AM37uU^Th&z5u8Xw;O=A!NQxG~oT!Kxfry!9q2T&3WLG1aRk0dIpAH935HuB( zA^_NP$u#C1gj+uWgRx!}bnV#nj(V7oR|SVdUoc%MxN?ga$Za8f#L#@v${TjOJ-k(@ z6^vs9ih>eImhy}oy-yI%aN~FpTZ1}48r1_9|NcDBNKEh~nCAnIMncI0Hsc1`fLbRE z!vN(TIBSlz-TttLJ8-(rxY+K)6tf;fm1hl{9EY7Q(9*iF107>tCXp}M#^^>*&ox zn?Z;$k{r%GN9YYy8^|f5#mF(Y8VrM&i$~frqQY%0Xg?miP&87MTwWa*p%J0Q)#yts zgg6K;l-<#nO^SQWTTRW$lbS#{Gp7?Uv8jzINy{SL!PN|N?+w`o?ay5Syp#h!e*DzO+qadO>GI{vdj0zK!@Ya=Y`s3hPk!>F_3hiQZ-4N^|MD6$Z~x`L{2w0t`+xs` z+}Um~-by*^IIcbgi04{wfA-+Pm$tk8o7b*hxt~jZ4)-*ayqo9w4i_ST!`ez5Gq*&P zk_fX<$=Qix=PUyAW-Kd#Nb)gfA{IDVXdPf=W~g(;ddNg6!Y)?Bh>LWA1YkSOu;!TN zJ<5?$k_P& z86bhsooQJ$h2r zjr+cbUVrdW=6Cz>pPikNh+w%~(%=2v-+ezQM3@=RKKm>!77N|j*pTh*ZM}d0KE3e5 z3v}Va1vxxCd|(*8d-pE4ZKrqcyyNcQKb(B?oBv~1it>vuytsaNc*r5dersp@wu+|t zVzIlqx%nCacfF4bDMjA8apR{{M3qp8s1Uinl$=GR5Rqmrq6%@&!#Ss(xv&fBoEz7H zf!OMN4bC&iEpQpr}WEd}jz1=VE3xs<^^*ls-)45dK97_v!Cwb;&GkN~wi zZV@{LB(Zc|9&y^D33+HohUEy@VBlnahH4V9Hk$&@CiEdx5FJk_d2IR-K}l`@4g1lG zOcsb?$LNL*H1hW9SfObwZAT!qLqaaYxU#e^1V_7Gurr`)-887`fI1Lj6H(Uu&+HkLSSzs0B3l#zHiaB9nxSwrw|(Bzs7kj*;<%+Q1nAW%BE9S z73lcVDr!L_0wWiUby@{`Z$TjwA|Q*9CxZ7>V(o-DiUDjylIRcvb69PK@rgP=yU@8=0BL%Oj6G>ekoRa@%$KeaC+J{^$8!n}Kky*&b7Y0Pr(E^E3KqfA(j* zzP^q-cka;T%a=9htN>_#fBzg7qC|vepMBP?udic&f8VE+>Ez@jeD<^d+Z`gJ*Is)q zzVO0}8;6GnRM)Y)w0Ge!4|(n6Dskx|FE;WlV17Pl!ugNRaM=~IbF6F06@&_ z99z3JgV;Or%nEi=gw%taapnk)m6UM)Mpw~Hks^HXfUt^Y3eH+#GSnraI$|Iwis=y4p zr3~N@#`~y)^B$?>5vg5`>^nhXBb6HDvx174I^7e5Z0JcOI6XVT-rhy17Sz?IWfhs= zMm4&1_idL?WR@QW+4_wM0zq9>h7czk)UmgXf)s&@n8+k0)Gol)0VpQvs2Yz_jDg^Y zkS&*q5(q&US^f>VmO6P5Ceo4iBLV|ido+lPNy=$xrHZI>i^i_{Df|aNs}q!4KvuSFWh{ zUf%PbN8}s-^v`SGc=j7>Kl3v`v-p!g`9Ck5oSf7-XFT`ZH?Q8j`SyE42(q>|d*_iy z-gEc<{r&B|-QC5VJ9i&(jwMzx6FDep=!bsCvt!4e#9)YID<Qh^;~(HUR|-sb+LtMlND&tKs}5!01wsk_IaR6?4r=E=VQWlzH5l zn;;{=IgbK^jQCM#;aq_C4xSujbo`wUa8oKso#CqNcb~wdY`}Pq!(umP z*J1b7AzNpjK&X6$#d40Ss=(y1zCMHZgs$tXT$8MD1JVadZ)Wr9Id2?+bJ`BEm6+^k zWU)Ty8G~fdh!$gJIA%yFGLyfVZ;RQE079GvCKlBjI3laV?j>i|5ugq-#>0!pd;}aBRR<$gUog< zlA;0zL{y2iFb5In;6R0ll{}00zIabirNo}Hb54n zRFGC}o0rRF5|OhZrJKy|j0h#C?7BXgkHouv7|Oto_vNTPtmo8sSv)N33 z@CtAq26FHA3hGMkXm8$lYwzmSt8Z6TjpcGNX`0EPs=n)2RonNw-EwthZ|}m(-ur$zUtSr(2TCAy zE*PtUSlM~5tBTc@dsK&-ouQydi9JzpOfE3l2~S9gM%)%i>;WVTxD>R@0l9BYAestR zT|w?MREpgbK~NpWJ-m^FhzLcBk!Solzb`ys+dJ>T7)v!o=!XG0o6Z{}%Wg=6_0iRw zcmy7i1O~t$0C`;9#TYPcrl^_<=5w@M0J7o5?MMww>k4h(VS9V)0hKssqda*B_$rJA zAR2%3J20u?!^Vlc{e8ypv!71e@po;4hqJaH1O!2!GQ1~KdP~AkGV)l7<}s|6Vbaa> zb@O=?T7gkWV$d%lBMc9NH7+gM3&?q9K>%Wtp4j)VnDuC|f4+4vigg7*V5bj8<$%JI zQIdohB_&%Q8w`GYnUO+8*|8LX5{VMA26lxUD?969SKbLRa~U!KO`+4!o723AtYuB!&#~ypEeCIo_h3RyP zjg1Z2-Q69!u4|8uj;2?yUOl>W>C$q4e}8wiT1|#N#UJ{iCto-|KHmDVAN$CE&neN- z(cy=^V>Km}=MX$O=h->ChvuRjW1tvnA|_HHc*m*`8iq{HS5#G=4f13;eGw(qL?xNu zHVxy-C>0E40KmvaF{IW|!fJQKQWVzoG8l!k&*X@^wd1jl$Iq&&F}{pO5T=EGQWi`m z6`W%@@4;hnkch{vg7wlJ+pgN4J3Fq&DxwYn^|Xc?QMpR|fb^TQ0Ht8LT3~(Me!bRa zlTn9mS@{^lXh9~>_@Z)bKbJ1p{U2^@9Kyko+6xH`5(AZzqcOy!C^DkWNbKK1GzP3~ z51+F!0z?yXvEgIR*#>zfkISp`KD}iiOJ+b6!>~firUBEEL4`&Kpk?=c7Po!{LBBVuI8<*Z>%?M+c_O!xqExNTuBxDa`lo-Iz5o61AMV__>sG7QpPik#`FviBz@=2Gle3ep7$Uv()>}WdT(y&n z7cShqapUHX+`oVD(Uj7LbDo?Ftg7SS&v6Kz7>uf}h?HE_MBA%-mQ)Hmb@u(FLRA$; zaE6c+HD!|$i84}2q(w1gn^21ih(|i_D40-;@mR;Uj#D7U@ZjDWe1szcjVr3UY2X8a zbv&vD160IlSSja>(_hCLMGESwM(UF_24Ofa_Rc82YWLL`D#Xged5_9haM1(Bh`yy* zFFz+6KF-!>sH+;=yW7@#XM$tGFm&Vffl=2HG5B#UcmB1vKOHet6k}NrN+xUO^XWun zu>UuOdC9iB%PC`dwiq3LW<63kFqsA|=Q6e}g|_ceavC>L zYSNQv`g7udrfGmc200xEe`zo^8Wivx5UOC;D6u5t`RRk$)`Q*~z{NXnKkvlj>4d=K z#WBP)iWY_4_E>x%^%Sx(q-lWaS*(0kaPiE((3$IDdyqQvkqxK9~2s?|pRl?p>l!z1+|b?KUCifmy>;sr{o1en zngY=8e)qfXTi^PY&$-Cet5@^WPd}~q?%m^`{K=o}+qQMLZ{PASzx?vfM?d<})y0b! z@-PfCo&M6?xF8~_j~`#rAwFJY?}J!WYXMT zt=dl#tE*z=95F&wF#sV}jxjjrJRCE79~g?_e4``-4MiwYt&3HQDn!dU0AO{pT4B=E zfC~C{K%Z9F*w}y+L0#9##c*t70%b7lx3RKfxolBYwiab3^nDrc=^0Ew9xcfCTmWNXxv?B|n4ZyLiPsY0?CenHgAQbUK-4@fziSAi5I z1ELi|Fh~e_vl+2i{I@8O`vj!|W@lf2cA#=@7#h$IJ-iF!)#^O{P7qNwrU8LOb%cN@ zNm25o3=(n<1&|P+fD>gEW|y4TfjRh!W9oDm`ji&V@fi~dQT0AY-WTnA$z@t7ORdpts+CJACR>pXy2}c;%H>*n3ak`qsB1Gjref?sK2}+*_9~U&f#R`Jb<= zs(a>{XS$#GiBFt9|NM)mlV&;qsJhi^@5g`q2yoL(5Tj9wz4sOkCZpn{KB0;c zhbJe{l2K14=(`SETkBRjQbtn+3^^GAXdF0p?SR?Z1PaF4;tZaEI!q9vM-^tM>IhN? zrGywGj*pMP8c|Ign!15wi@hS-!$ls*LxFPvr5DQ-qyauUfElaR3U#c&&Y|xI48?>k zu5w_pxxrvAE2KoDPr$(ik5$)WXgjbY6lG}6V8=Eu5JDO$yf$RCQ$pVxJ254bb$H1j zcIf&RDMfh4@S^5XfIYZ*$Vg&Q;9RnK6m*oeNP#V{>_tn?FyWd6iEZ@?5}0oTnsZj? zSfFGYLZ~>W#aVMv!6HPV0C^yC;*@hiDba;4Nq?fM9l#gWGtESMvVS|S@%;ep>O;3ZWuO*Fr-qB>#CB=moK`MGPbuj^E>a{<+kl?9`k+o z{{NZi=Re{G6dFClB0?{|_#!^?k&ockty|=rqf!d3Rx2%~@X!A2&*s1Ro4@hv>+5BG zeO<_Mxp?cXxA@u<*OZ9x%rnmn+uPgt%fI}~**CxWTs56eaCWxfr#|rhc57=ZA0HpN z{rx+8pZe5Ky}np3c5dIk^+U`&fGF?nU3gg(7Z%Iq6HU{sR;wrvPt2)LQ zgDavEY<($Erkq8B_X;L$T!kKkLWM+}stOfzQ9+YMZF3Rj!UPs5^``s+M%e}c)kw6m-iD&N-}2CdhpTlVH|N zP}J(PzOs#jrSA&Bm3>8NKwWQ-1fLp33u3e^gYyoSksu5MVYytOt|vfIOzSC57suG% z-ZA}npCLqufuZV=a|@M(ih+K}Sav;{svi3Qgti+-Qt<%s4j6z=GE&ZvVgSw}0vTne zAVE$xmG@$8K@lAdN_Nsx6_nfqRDn6++~Ue$umFXNaaxOI_(cXJQ3xs!I^#5wxeN@~ zO=AtX7{IltotB{E;Lu7|cKa_NhJqY|b1DJ|J0TS%H5Ln7g9tYA>#(1t|o}F!gLy8 zL(d7Sszy~+2+>xAG!6!d8Nqq9eTU_8g+8}XD17j6G=5R@0O!x)m&WI3GK}`d#>{*M z><~sgm~$iGk{xo+h|cQA!3Wf{2Fwn_FksS5aC~%()pBV^`yy~m$fdVnOe$#G4zY?T zBPl2w6$tPM zjz_z%0a48Pm&dGw4OpEzh^DczSl}Ilows2o*?>~iR)SKpu^SwXq#zHBsBj2rv3ju* zScw%35(bI$CO{(3YI}axz+SxbMC=t%B?XacA-Dq8E_&AuDcyF=$Lu(updE(pyQAzx zS+z?kFNo>DIrrwy&h}Z`_M2U|^4|F&##jJo-zR3KRS2<5IbmmKN4u_LRn@NRXt`X< z&6_v%rI%i!t5>h;4`c!G`OkkI?>Y%sRTZ6_oIuQqIp*F2j*gC$8EBfug%CV5QwSj~ zm&@|&zy9m;SAX?aOhmH2zAks}+`+-Y0rh<^hlhv!g)e-en$K4pV=QZHYvrMbF6R$^ z@Pqx@+FIG(-qq8S)4Hka?3_FKjoxP|kDk7x{0p2kM=h??dF?wcZVJ32ZtP6;W4<4RbIe5~fFz5l>@2=oc$ie%@dPOB6 z4+BJ!;h)Hjk_cds48S;ZfSGv$dlU%RJ2XwiWKv;sV~Vx4b!=>`V`F0rTbmnbW))&| z2+^!JA(&OB9|laPGpx;Kh+Kh$kuuPiWS*SB2(iNE#^#s^R0JkWr!#;otTUTdm`o}t zIrKw1!fdpPrmeQtCSAojA|s?CeylD&~t9cNgz{?-Si4 zk37Wlvon4D_1ES2_?Q4VRpq0jBL{#lUcC4}{rcnk;eh{N|41phjg1YueEBk*a}Qnu z&N+8{e9HIl-Qm*dx84E~VP|JY|MXA)v>_s#o}TiDKJ=mC zwbx#mKKkgRXHPuwM0wHT zcd)l!!!RT*q;L)}uVO;Zmafa1(IpdlN*OGZI{y5sCYy%)Xm|h0q??map=s7JiBn9c zGc&>($IuRN zFm7Q85hWXs-vtNfYEx#Bq91zDs5xUe`|m7*p%esf7%~JCf(i#@`%Xe(4g+%$G925G zv8o!d4{%jwc?KcN4rHSfRl!O_Jf3!g(O*Y)q21}(nFvOuprnj643_-M9Yk$yTTdJK zXxK0XgFG~NMhqTR6%o9HcU}Q6-g|*#Ilrid013=Wq+)v`o3?vG5|mD|w5)vCb;EKP z`gcUiN<>H%y=QkuBqda#N5Om;y8RIRovMnbDYdd#u9~)O`Rwd$sj35r^yuhVN6JqS zk#Kx`><}1%l`hp?(gr@AN;`|&>#H4AN;?b_ji2-c<+1P z>rzUHG3s);1T&+mD($*10HChx%HiQ5l~RaCEa`_o{9!oI3<2|MqX! zpM3Jk?$JjdEpNa5Hg9Zf4EOHc4|{uixvpz$Zf=%$-nrus4i2XK`}ZDOE*5@w=K@ZS zk3SMZKvmc1`tI6%e){C*#>V2-JGZw0eF#K3r9mdm)P)$SmwLB}|s!;)y^+*xug8 z?(Qx&Ha6s6|Mg#WGMNNb#f1wOO53)fZCf3NAv)*ux#ym9mo8nB-~7$rlowujUawra zg0Fx5>otJ#*kh0B?c2BcXMgr*^X~3$`Nx0!$I0W5Kc1g_^2q`a{`sH(X?t()Le5#X zL6Q{|MMYwW2O>Ig?9PaZ)@Eyc-89qLWOivbo89h*eo|GH10Z$PMCTn5AkHy@k6hI; zfB=^Bv%Z;xyz@Nt!^oXiQZQu^QY+I#eIcRN4cehYk%E5Jn;l1K6pj$QkM?kkodH#Xip?vaz%aB(1*m;QyBwgU zU^<LjT?Ma%KIg0ix}P(SyH$I}?Ale^VrOUbq?t@sF~-4rpQ|dio12^6YPFKHvoqb= z+VY2ohw}E@Z>PPzy*!`K;k~DgjSYR}l~?rS-e5U=%SH99*x^$@!5kLCqqY^@pmtJ}~eDj;%TzmWG zt&QnyhNiBU-n%}fvfb3x{%kh8esFN_YVhGg-86Gm>=NioPWe4s+grz5TU&RQtL4R% zhRp2M2al?%yh2M=Ro(~3Z06Y_Qdq5MPeqiR_aiMuD65hkU-Q_C)6pJ9MjRtT&enS; z26FDf>=3JnwI-r!ridN2o;bqlIQn+ZhTh!BYezVSQ_g z$+W@x`UD|N;Mt>|PSG?qoM$wXfUfJ1`iz`_B-uLgssLHXy&tUgCTb#&ZfGBL6Ljpj z_kD*<0>=(QgrYLy49uvLl4b0NVSs4~Y|YwsEeH+_MoNR})JKZ0NP#Cy@2M6D#AF>J zQvbRLk0j>$mlQuy(dqF{EAkyhOIHW$D+pCHE%F2Ho2 zweWWOAR_hAPD#!=CGV7()p-UJDVv0)uxDWx2?*W&WFgQrs)RC)I9iduut;tfXlsnFzYGvyXc+3lG3K zS(+D@z2#_g~@DUsAeBQW5Ny~TN^q;N~Wa(_(3YIZlb{| zmBzR(qi!Oq%CJ?A6{QfC%g+9jJQGZ-Ni^5up#Y#Oc1WOGPz{*eogv{ow z#aelRVPs@y#9rh48;Wf7h{5|{|BO*N#$=!sPYiN8au&u_Cy~{$i>3NJpDWGvLDku@ z1Bq!*96RGHGOJ^@7nURS%y5C#`ydPmJEvrv{Q@Z}Ql#Vp24%<8k#n8*;RulXrN}9O z1wfr>S)|mr&XHr!t$uJt?L`A*o} z+@!@~PEFIuVzEHq_gaeN_q^vl`1Gegtv~m3KS!4@U)Hv5aqr$e{P>UmIE4`K+rRzW z0Dv!k@r&HHEkFG5!~Cv?fX+E}&RP?%zy3N_s};`9&Zw#$#0|A+8VMnkJ9q9R?|t6g z-97c*=YRQ^=ccN9&Lh(sH*QiX1%LHde^veQAOG?CwI{Bo7%TqDSH9Z3`s#Q5WYVlY z@PYUL>+a6h>rK;KfBf;sZ?0C0)zQ(x@Z$5&KYH!je$1F01AA5^-yN7&XJH~Ww0`n zGN`CfBBJcQ8-yffkrj!Y3~hHLLdS4es%lOtmr}|qXPL8xd$}J{84`xB&8Md)r#aIpLf$hGC$mo_as8t*xDJ>hRWE zZ_&9VWwBVCqrmV-fAmN6m9Ko|0hi-lp8!7biBHgEGEr5fjg1YufB$~HSS$hnVP*{> zkTp4_a?TBPU5D-M?R@p>)$+(AkEB2R!#^Cp^rbJk#bSY<|M{QS_4V~~>C&Zg<;s=e z10VdLUcdf^tE$?+@x~kLhetSpb!4?O+tgM))> z^Tpyvc6WF0uWxM3=kxi)rKIEg_wRq;>eZ{SA08gOk3j0Zqq=T_NYN0ZcP34BWP}TH zj>&t+bdG=`q46Q3anV7{HtYfkKaW|0iM7j z2osMg*b1&2G61nw9(#xiT<{~7i$}GYIse8;EmQzc=aS7}4C7CMBF--5xJhlV@M+D~`V)Up&qg7QQMvquW z8w%Q0g4hu`7nPm8N=VuMB|@T9k`j6%BvBavZc9l^6*?}^`$Ra=RF+w!6Okk5{9Lsx zl;}{P?Q*^nkV-B?+x2~)Qp(JfFI~ReUA}y=Tg;c@gO?Xxcp=Q^bKTq9%bS~Ws=%ytJYPMa7ht1$=#qRN4k1zsIuWTf0f zm@uQ-_V{C~txp+~No~jLzO~_?_jv2ZO1fVwjhI8IDS3V#YpdFUuY79^g$fEGxSbQT;)grV$0IcvnVib0yB`a!aFi21DWU)uv3Ba zjOYXEXlqv=yhWLr)j38M6~o6^8xyJ`${8ra#45-t1xhM|vL8HavRR`piPK3|ESQm8 zY$ic7&K9e)wr#5re5xu}G2=j4LpQWEbj435(;TDMoO5~OjqBAgB=O#tqvOM1+>KMe zx3`yGdg&z>W7H>}cp?wOAaz|+*LAwHv!h@A>Q_TmReX4OINZ8*3$xjbH#ax2wY8>dl(1vFo}z z=j?}JkfWoc>fqpheRg)1KlZVYmDgW?owv5O^xCy+*xK5{pa1z^#8OJgNn%cU_P+OB zJKEjdUdA)@`l@1XRuD00*Q9%_091+&0Q)nUU z9dHFAAe8~69?Wd5JoDIn) zMUjg&G_Bc4P#puGW4R~Bh^mUHVy&^R;2ps^yVyI=qGO9%R0SYWl|rgXN-5U-mMqX* zN|}qIC8m^fIxR)cL}aPZL%^rmY|WA4WVvkTAY?%EVzKCluFEmRo|v*IhAM=9GOhc^ z9(#0%F=haz@4E-Qgz0o9b_&=nt5u7WlVd$PI-F@pC@A38P*U|SqUVZgd+27xP_g8=){m~zF_SKL2eW-An&*!d` zA~D7Uz^m%O%w9yC9l3ql-Q68>&dtS(7rT4+?)j>!@Y&CPRvv!%VZCtS0>1aX@6zGn zu@lv}yR&`vx&QsM$4%47&dyFpM9Z_YGoDN)LrQr&i|i~Ht0!jD>FMd|F-<0uMJeUu z+uJ+)1nori(rVRSuIqXurC|_J2_a}xH;oU$AogH{;OzTA%JYnHz4hj%oWMOG>#c_pCKG=bW_-|tpkmrHI{%gHdLv}#vTq1rB6 zU9|K5;>8Q6j#=;Cz3W7@q?9n7&dQxTx5M`KPJj9G<+NI@^o=*JhugPr(Y<^3@XRyM zq`vRixW4Z-rKIP4&i}|7 zkiP%nf9IWd>iK-`0Fa3Ac(mZAX(*+ns#;P??#U;gY^y5Z`1quG=%I(&Teoh+haP&k zJp1gk^zzFulZeoAxzwvyA5SO8M^aQVnM{Vi`EOsDzH{g9^c&y!`q~puyf=kVt@Zt| zb?@H3XGRv)H3DzX=V$kJclNr;bh>!w_U(^fym;xBifn^u$T_(xRt*uQSjC2!8IWRR z^U-c=Ic(?HsY2nMCxsFdn7va`QH2l#aNZ-v3Z5N_T05?3M$-;JX-HmbQ93!yNae@L4Pv$=@`mo_ zk;Kbv9sM$H7a&M|#?TKaTCDcV7^P-9ZU(HH&CZS4e;(U~0nvMe5HyB}V7*-g?-YPC z3xS|I-W62F_X|ji7J)!hPNk$&QXUl0QbfB_%Dfwv_lF^!MIXHn{v_7nC1$<=@MN(# zTWZNShSWnr_v~3IUE6m3(5LQ+C$8Mz+uKWZ(};6UkB^W2-MjlZ+uHo_p>&`o=fD5!<#EI~BHac6P=WFJ9CO7cQWbLeD+- zoOWFY5y8R10iJ&PX?oW$fRBFkqyKJmuvjekTr=dHld&UEL?nrb+9=s`_>))_P4&haqIT&_}F7t`i+f^{xhHX&qbu9 z&CQMe_~>*xpD!*j(`n9mXJ>0`DJlay*Bu=lKJoO^AN<;MIz4>n&h0hl{NBTlJpA0z z@jQ_vjv?t93{Ng%Fh)v}{-K z&SS_KwU2PYYpje3v|6^x?5LE2t}96WfLJ-mh`U}bS4epni!}pcG?xMCI9vmR$B9i5 z3mz3I$SDIn8ioTN?C#@ON@~01$lg=1H*+B~VdxCz5jmJXiy1@(ZI{r_TZG!a`~q9k zbwdvb;DQMf$_NY5(fUznWDl^xJwz?`%S6bMAvv3LBn{@b2HD);9B}(Fg zHiYyRAacR0_lyu6?AM4COiE@k77$dSg_JVJ3P6^UODRGEq>@FUh1DSs{g6sJ%6Ygo zbg5$(HmbV5Q`hx#vsrV!@A@+lc`fIBQ&HM}81kfQ4*R~-wYAxPDQUH}y>VvbDr9D- zH*ekusw#Kx+;Jx-$Mnb}k95=NRPNoo=l1sY5)tWQvB0fcxA^6kUk>~G`z7a$H{N)| zedHq_Np0JTh|prOpr@XCO2753Z{cfS`xA*N@@MdmB;=$<$TFIx8L=v zx~?ZDIp@ZEzjDsi{||F-7HesGm1(|fec%7@d7pjeI49=JjEu;1I*oI}E+d>}UsNNb zRMWV?ceKh#_61y(XoR>a(c+E^Om$HpFj7|wEE&518wpG|P9QzzU`Hm-bVf$Tc;9pvE#&!(Ef(C*89GXcZp+mg{H-1=JPL6^Qk!oM*S0v##2&> z6&fq8!x39rt6A!q^1QHe8Zen;7>}mNi?TWkO)PCB2(c3H6lGqGxTI1vm{t*6Wh^U9 zx|xNttWMFgB!kWR1n!-YBeSa{%(GF5XoL|Xnp7vL5tOtt2x&kJB}A?ANmxKA6{&M5 zLIANk2=OvYQK3sq5J5(8osXp|}^;ZzY=1&Yn~-zsOo zp+bGTgl7E&hMP@T(rTP*%w*mtV9y|SL}W(n)O)Yw2_RDw6Q;`15+$k%)J!x=n6W`j z%_x=#nT9Y3Q4W2Qr1g3;>$DnQURs`iZ91OR@}l6iwKH!o%=ZragVCdGF3d0XHrt)n zcx7cN8;vs8Y&K%I+YX0EC&}W%e2glXPBYB)x*;zLYSintxVR`WMiCL*y?fU+8V!5$ z$tTO#Uw@rXPEKfLWhI6XY%mz8h)}!TMp>5ZoU`lKuTxPJ)d;vK00672tF*ehO8?Cp z!1e3b?H_zU$@83MS^+?WlUkxhQPyVh%ktJ+Z|VEq_dY#3+Q;7BK0o{Hvmwi-dT_9h zn>XKfM@PqQb91X@1{XrXomRVi{f*bs<&`tMx3|~1cI{ffEJJ%T8PD~4z0u~z#<}_V zg_x$brL>kFGEqJpjaD0t`f$+iua_YQRT@Q+(em=j_Tk~dB@tb9&atYreCpdw z#8mMGs_D98PefGlMc8IoLGwvMN#fz1Bj+3{1VMGO03h!xGYdP0ilEXQ5gPRdRVJGf zX(O#pG^@Phlku3!5QrR8tKI++S(He%W{O6mMx)6{o9#MflakcLVu%PKfI$dF0dkH^ zD`-ewRHrL>XQHv%jA^E(6Qy?&d!*`1RJUB9A`YO^!JEN3g>#-@h7fWv`wC;`JS0k` z5tzk_Ni-Ct0aoSchftu9Sjl@)CG+NG1{Mc_R1*@jcOt!1E3qX%c>n+)07*naR7@Dn z(uE8&%vOvQT3jlv4U}dHJr2fN;l!OGMyHlHHevrs+bvzS`;$`HI$D1G=_MP7v-V%-dfGZ3sH13 z8Vzg1QC4$~eVNBvyVD%)Y;LVjr{h9QhP~cgvADPp4-b!=s_^;q=ZoXxV{SH^wzs>B zqoYG!Sy>6|YwID;b4t^ih7fdVX-RM2zU_B+cWrxnn^aYLy`D}c6C55M+H^Xl>2x%m zQ&gq@+lP0UY9^bLYqCAr_D++rYsbm9ZR|LcCYuu{V^_ay+qV9zwchvOIe3o0zSrlv zZfor-{o2Mx?kq*xhWdKvzYmpkQP#cH7mM$=|2L~x`~U~HUY$$|U@oEoC=&Z$f=`~g zypnwxi`ba!ZSE>rV`4sjA@mwLxuBY-YhWEGicQ>!io_?#cg{7q{ z}0Kk~P>Ht#Xz zw(k%{Y5e7!yFC|yLw*@&HYd;AbZhxCEfHssg_((oA1j_?Y=(_iY)0(YTIV%(PJXwY z49FrOCuT`dOec1Yj$HU=GW@MxPQ-AwGc|jFr7JgkZ>0O2*=gjnsPY;0# z?aX0ArEa;#RLP0EH&I@rf@O30W6Hn z4$@z`L288c@%^RitQsaUXb0x-o-=?O3yyfY$026C8VCYx^;l{<3}EUEiVR)j45C1; zsPO>uVzGiq1gdeMt1Yd!ZmwO5MQ8O0UwzX{(XjcBOo45q;eGU~miu=rk9N9||7@Gi z9k&qI+7=GSBO{DYq1J|k`@g`MR&A?sy_*<0%HQUWU00Wv3pRh6&sQ15^4hQbgvPjc z92@k%yacVaKjL9xqM@PL+So*Q-17+vx>)Eg8M6gA$8}z%RxkYIYsja=5dW1=M>inr z(D_(QN<#87uL9G#?)>)RsRgv-$yzL~w~CxdKih7glK9E9M-8?{^OGO@MB) zx6Sv>uwG2xZQ^xu%-^rCz9*-rbELjElb26V0&A~l?u>w!EdOhp4shD7aYATlXwNr8 zR-?ZzTm)#|&?;_Hl`0ZaQf68?4!qAaL$etzm?L#vb#+%w*J}u`$wmD|{cW#r!=a&} zGyqNKH6_!S)4u1V4E6JJv#gT30SxUXdQdT;96{<|jFdup{axy;RD{`Z4mm;0$?uSx zfXpZiRRaa?Om00_91HL`VbLw^$gds7qNpOU>3Cu!o!%(|t`vBOB&iPqgFmenF;m*YjLXi&is&M;IEC5|cbVIlX)&N_Yz zVq#^yE!VP;rpHtdL}v`LpEWMbrvq8BAV8t~h>@v)&y;<+CWX>*qJC*~UpS|u%&GNK zRe;phL6#48i8`NaP|2gn_W>Gw6KQ-hCq5SZ#qv{Ge28wg<1`nMS)srA*y=IsF)1`5 zzRKmq5mMO50g0Fqn#h5BfGC1S)k(!jdCHuU5iul3)}8)H_cz?&)vsvbEYgWwx1N<+ zmVC-1lz*8RAE1!_*!GOMQ4RuZHQlr&`{u2l9v{VNJt?mUm@$xswzl?%VASPwsd3@d z?Q}QY$nD(s+`D&WCENGZ#>iQ_O0e*QCZt+_+2ds%k5=Xk}hC?4Jh!?hQ)In$gWp|%#fN9G*-xSv`H3d-?tb|uZ%o`pHnKLSADl!T8mTB2pbaA+>enT*pG~IU zdi7t&rH2o_k_}IevX&4hq3u#uUKhTZK9Kvo!*V|ZV$g`%4ds{7Y=r3y7tzVNkYodT z%0LEBWXED!P4C#hmsoLMUICwcrP{cu8;QhqjqUBxPKJ)a)+Y;!W8x@y!CLsP8cu6! zYB=(Wcm)KUgqRgt)w)~lkJRjH5N_6TcRd&}0C!Ln)_3Wx4t8xK4^S>U0+JezA})nC zmpBCmOesB+v`!2yo*2B-07f}EA@!`)R4YT5XhYP4sWXu1Ka>&}pscPYm26evtRj3L zVUByTO9;`3K89JwD=}O2MU}oELXKScaCZ64)6Q2cCvxprT${Cj&(`hx?afim-G3C} zsN9Y+_DGB#Xv{Ru?ewI>OxzBBm z%1;U5M^~TwQR&7bBI55aNaQwlKo`OhhT*jT;vz^y3$6q~Nk;2xUSEBq*fZ|OO=DU- zF7s{e?WZOuF?o1+4&cZh0@@$8qJ8eq3@WOsj-pvRGcqAByGCy`T3T8sbCo};mQR-%=eF#5P$34ib2@pa~stMtXHdi^~;!+*_2fSZLGA=XnlU_?zFk5g=fL4HSYbngD^tAvy`se z^6A}0WwU(+mv!v;$kE8=hlhVOR@ynKxD;`SwVKI@HOf>iKU;+XRU!uv&hVUI9FQd! zR;u^&3kG{K5KWklUxo|$-6d5sUHOwkzB&xO8bzZxA+z*vJ?7dzPh@u0%CsJ6#iXp5 zBGHI(qu1EK6bTGFSE7nCx6C@zGELkba*(uc$d^)+FPt3Om}-E8$iowxU}xKq5r=^3 zP`N*b#O3N&ie*L_x;TLuIwXuQy8S{--0^H>F~?S&C9q{*==RTalGKP|0Zxwn^TVXO5C^}Y;N7hkYKI}~x!eBM zPrjYo+eVN3Ga;ku`I}ID!;MQ16wJm+tG*6Hl@Ho^B)#j!B5$9chlj_?(sEGfq8rX} z;9m{X=m!Vc^?K@H=HP%{?;1uxRmuR`7Fk+YcsL)0fAO1mditBiZ_#q}6di^kUYVwc z^gG*^)#E%CavO?oSfks{M-&_8fAMehs`I`>MDQBQ+2VT4bGFht)^&m0^$T!8_SQ@G zG&>68FSsQ3l8pZIfd%+cd?1s^Za}vh?pPP4=!HsT`a~yIRNok-* zHvfJGB!!`dU<}HbLLCRXv2N!SY=Cg35@fuh&`Qt9vn6{t;A#g9bN2F22w#HAcCcwp zWpoYU_cOBZRWW1LW@;sLwZyq7)oC*c*~*)6IHRe8?&Zuu<=wQinQ}g+ispSl_xdXY zA9Ut2uL+P=U@eLsuALr5b;cNqrqs9Ygcckg0&wAR$XPg{KZ6<&Ql1}he=XD;HTlV$-~LGJf8Vw))wVjfy=`{vB5*gOq4H4?gB4Z7 zY3S^<_xASo@$jIl5=i+~hGVuf?RfX$G%RS%_3Hl*FK`qXl4R2t3*c!>PbkR13P#;^*e{>OL-M{0TEeNzbcGS1ChUOQVowaDjGYvTsr_b$6zdrB@^&1 z5hI`A+uDy9Qz=5T{$33rHdf_JtfGrG4kbuEQr+Ue+&IpRfGDL$q4zL(CQgNF*gzCp zPVp)T@Jq{=SEU%jS93T0JmnsS&N59jvT}Q&`ma(qUybE=&nf7J78p2HrTEK!8mQ$1 z$yNW+;{=pM8SE13#VTtAp-6KQSKq&&IsfO4$jMXn#PVzLIOFlxs{L#2_2FtAl4Y!w zdi9@qZ0%*^eR)`Zb}$6IjzUO(XBP8~B8(~lMIxeA3>d6rO6c}Sswo|R_cIqL$KJu6 zi|CtNlCpRN%S2sqrf#k!HpkcMTwVT=I;dVHsER=khi2BA+jX2`0y$HWRx^!;I|~&P zbM$(J#jxE985vn1N72^Xd!FI&HR4Ke@ZUce|C<7`)Q=~W)2jVE4=?W{@%jUTva&Kc zWnmdc;qz%<3=3;pd#|>q9M5g#oxFRya#C&f{&u}jn6x1 zB6~}HJ?5q}+h49m-JW3Hdhk0%fBg7i4Ep^02Ap)z_4M$b{d#u(rpzuPa!%@fvidZ| zYgGEJF=g#}ZLpr0*Z$#Br**A_Z2Z%GN)#t%CXPRndbyYKr%;G~3}kVV48b<@31oC+Co<;i3Szoh;|sRvX(1@y~L z!OCd;9#I)3)2PWWn*~NpQHv2qGwhbtOaQD%bg>l_V zi=xct#}|srZHY=2G7O9(8{3+nU}0fm01ZT3*Ah#C+#N)L!!x3@qcEWaAw$YP-0R&B zGlsD-zIMRT;$jAw@!`r=h+fOj_7C$TCd)@{WCte}=%QnRYILM9)Hsq~tP`TL=m5{= zJnPj7rn53ZiN)iZDT<7@l>9+dJaF1+#vE8`LLG6yBV}?a2pu9%fQy_2z$)7G=W?J${pNYB+L}tZOu;c2fi>CqRDQWD~U%3h7A2L}OXa z(02FAa8e&*@9nqO*52)HSIVO-3d;#>+9U!6jY<;>3;Nt_6c0gCjhLX_T`7KN5YWx- zu4VajsdW9N(nXN;S;c1yd(%1ZIUY|JJP#sV`H;iuz3L+K!f20PQqDh)hRD(&Q_UG)adT>7O~hHn)O`+;*(&3M#dsK zwu1t2>SMSmrxtIFiD>Hjbc!~0fDX~(3;b8(Qu_qZK7=HW6nzEkEM`qksYb%Ultp%6 ze=`T?2u7c0Tzzq_Ml2_)Ad`}>t^sUWejl)1GAFiW2FZ5zpF^rJsWFs947i&Q0x1H! zX(Ik108Oc!ZII)*!u4w}C>$G~Nh6ujI=_Fq<8AJ68kZO&15f57SJst>AV`KpW;=1#hC93K zXQw9c z%S$_)@rM{B_q#h5s`Yx8^6<=hX4r`}#)@2lZ^kjSmb zYy9Kzu;b0zp#-`R-Szc#Q%j50`rF;@)6$dP+k#pAZu9wyy{*H!fi(~DE?hd1?d1A!hOcNZnRI2caeHeR@d?s$#lpd4}CYpJxUjPf> zKIjRSS#en)=(C=>YYng*j+vtktfv48ILI+`GFPMq4N#Q9Y6_f(DfS^SA{i5Kz=cGP zeKVwq7=II&^T9G!&3gcJYo>_(_eFCU=s_{@U%q5wIqGlmQ-YZYJD&7BrO%$l6_=@s z=+xv$5;HNOCb&h4vWsFVrAY*t+Jm+iKrRgSLtB6C@|(Z%inGr=>IITxriw{@SZ;gm z+WYMfef`vzU$8kj*t|6^GX+>8erY}OMDSx8QT+XET)EZJ?vXC5?kqdKw9a-t;34>W zVb0-8DYxRg?`tG^gg~+{)yiOTfFs3!J5ILq*gzsR?Rol)HQ8goRW*YRq z=UFLIz#j<>^<<95N*>NwI!&vrsv-a|v9K(yt&yIdoP5OA+|E|`pRYyUoinqt>Ob^C zX%jwsoM{#i2*mfUKCh8?%>z!mO8=jw@HL_LY2*CK_w~oP*L1vh^al8c+;#weEJBtY z>|S;~wOmRB8zUTBjxv2nfLF&jyL1Q!)Z1YUl$__eM_5_KmV$xF?)-SD;{W#n;{V8+ z?0LC~tpvq*>eS7QP+mY-0Nkh-J88_; z-CDnY_1)dx=*VqvKt@K!8J|Tzqq{+P?8?Q&WYb}=U~;x0%^uqspvx=m8Sz;vMhsGV zmQ6fDj=j&LEb;XxA8C>ZvlW1D7(ypI4T7ZTLs5VjPNA zoFCfS%PhQJprWA~Fbow+u$^k7M;!Y(90)wS9!xNOGw_~y`z+0mYY4Qb=nN<}P zicysN$;sH3(ms1oueJ004W>{jJ&+&oYk)!oNXSL`LH#-YaYRX z|E##`_H(_*h2I}1-~hf?$$2v$Ov2`b|J(I>#=opFTHg~hGKmhuKj$=ncYj*n&F~xj z4zETrNhY9j9TO|-1T2weS#8y$wY+Kp25eM5Ibf{6zrQr%K)dS+=>2jz7zy*AOCP~EURkz@W1$%q4IQc`4QqI-53M6?o(HiU8~piz_r3iaGKozdypFrCSSOrgvd7niYIwnllG zY5^Sq?PatKdReJ-zbP~1tm{F!B}owp5b;^4q*9<&v08p41(7HlRZgq0Tz_|5$;?m% zJZ*6qJTIKKfv7lapw1^{U^gVQ&XYEZ@~8|ifZe-LI21ERF*Xv59qat7xUN8O%U_j_ z0O#5bE=qP)d2-j>n4uq3mur9MLkR{Ny`IRv=j>f4U*71d`T8?gcBK@bosV*`q}K}K z7ceDEW~mj?MO6$+!_fe{i`J&8!#pzfKc!NTG!xxcwxj6nd9ot3riJIvU~t*Z7^-xv z<6?go7B1z^iZ!*cC?Y|V`3!Jr%W>i$Tz+{GZ*e=d=Hufl-0r2Kl-bFi@8~cPe0$j5 z-u#I*&ISm zLxW4%=Ku(}e3tSYJI4Yx2?CCpR3^M=)EjdZ_zG)0eW>Z~=5~>L-yS}T($?1I^8V)g zlxp;d>-~J%92xPSeKQygo)o_MjSjA!FBltB>hyW6D*{`8pyd;S*NT^G_iwV&k||1@ zA_QnZp1r+K^Xa_hw%q$ndFD?&Da$N%-$Y}6=N2EPAZbJ%MQloL2?9CrS@WaUT7PQ> zk?}O91dxP4Q3e#BDINa>P%u~;f`0wJ_Oq?|jQNQ&Ge}mcasoQ37$2E2*6=fKu{Nt_ zJ3q5n)zo^YJ$k&ABEXmpQ!VgchMKM)6zXk+F*)PS%Og=_U4`dwDxDh;5zUCRk!O$-RB%Op zqL;^J?xutAAyTJD1dYkV$;+^~M~*H!Dwox61z59*4@3DEVj#$$oG^1D4lZV zeYW4_DtVN!ny4?$oF1x(t`0q3e(I?_jng!bs2X05cYtZbDdd%?(DR{O6uF53wR2$-G=?d^O2t*zD7!2Rofu3R~5ZPUDrsz#mm8=B{J zR603+cGlyP$?X0&XI`oSn@Tvj`DN0WO2EdSYhN>U#cKR;z7i?|ZNnq*rny!*HDKu2b+9ERbbw0bWteK>F{RG- zqsFCW4A|a-*_hK6;d@bw`(mIce_Be7dLm9lP6bibK`waqEZr{&Y|e6$pwv%)$@8(u zE%K8hVNukez2?YP*Zj5hIlE<*x-$NVo@VDE zB!`%bD7MR9A-iSg{h6>Y0d3ms+O5>5PnwyXG9yF_UR& zcxDBY|NU@SFLQ5Ob>btLT3Yhb*U4y3H}YCE=y2nXk*30mblZbMru*X+(pe6(96IF` z6ciqED^Q;FCrO!?_KKv<-GoWDa3%i{e2Sn-z(R>hX^kO;&J$ZS& z79c=*n3N%#ZF4^t(6~-#F^EDJeo$p6L3@08yx!jYX{=psI4WJ>_+9P=Ja!V~USp=TyDf_2OZ*5yvRDwVdCyq2~%@aa_olHf! zq%n(R%o>G(A|C>SiCe0oOg)kk;bfYQz4$v*T#5>YsVp~wgHy6(RxAax6uk%3l`2j#^MSPCg%!-hm~00NN`jR_;Za3f0&8D~UU z$^BAi-U(oo<<6jpuyVd$geX6nV5eJZxifKR2D97MSWq*HvFnDtq>4)|IX8{sA zwpUX*6~0^Ol8#TfpEI*Ct$1zxQ*bYy1~29uqp%nVh+M73Ti;5Hj1Fv5UrtlYnda_m zR?lybkE6v8n)&ZR^{lv#Xc?URou@6Q=?z+bvUk2cxm$U9>OQ{;bJjDnvSvYkFQ4jF z&vOF!1O*F=ifFQ8XTdf$qg4iNcj$*JcftC3A~&cT4-Yyj%E~uWMG7n-#;0ySfVfg> z|IterySn0zfu;cmAJpaO(h?CuFbp(inyV4t(BogO$JXb!BBLjokHpF4%k{){mV;mV z(XD`Mn}Oinf|=9fm4);8WaW?Mm!+l5it6fRE5YK>P){L#!iU$_SL%?G^78Wj$w@Uv z1aO2kggTOkIMxK`5PI*GC47mEOP}LBjnOZPK`E?>IT1I=u9rUamCrH@p4TmA8R|1C z+6qxF%pAshTtOLKr0yVBIr0SLo!s1G2rC$b$u{pBdkXHT1#dtf-dL-VX-aAM@+P^ zCTF0qqKme0$g1XL96a?DrOLRPfF!BJNchqILI9maN@U_$QY&^KDvC=VC>)kJVUe#a zo4}TVR)b>&?yw4C0do0cEpZ{7hzeK+{HLPf-^)KZKAFegQeARDurmi(y;yr^?%Rua zXjfQ4JwHtCdn8sRrUrYhd%Dj{7Bg@WsSLzR###zqCMvw7r>Eh)O-L8;kg7N{N7b#` za>Wg9v!9=mFzxShu`n~|%p5nGg2AN=tILk=XDdct@6UPsB=Lg_tEAXTOnf}No4v8-*IkA5FXtWOW3K%uR?R=Wc1Li>u1VV3 z+AI*L*O3GN{(S~mMl*hP14anV;-qXO;p;la zECr0WHJ)|jjg}#fk{Yor4*Rb$QD(VUOz2;7YQ=UM%1IPB5WXl*c0rb8N``K4L>#KL zHJ)u)mt!AHca8erDL%(HQ3iU0vxP*$FSJBfxW99Yk*C`Yh?@+l(1Ktq~1 zARb_2WGNnqAnGVGGgiqI?~@tkQC5E>g)$Ia5s{^4XZ#=-jWPdFT6_B{R|2JWA zoYln86W~^fSs?#2zwy_LzDVhfsDL!(ztJreld8sXN|YFsr!QkiOZ|s^$AhJu180sd1VUJ zye_vM*^PrcPCHzjZOVHUrwyy|o15IMbKGni+S<)9U%yMm>hL@8A93^yx;io}ZszDjj@BcRlUP%gcF4M z1qD=P^T);@rD+_!#Y<(HepTQPef_=;@B0&tyeFKGo#%se8WKoDjO#Us+npMTwGORylv)Cq1%!(84l?2dZ#Q=o%~)758MVTG=%6G{ z1|7!bkf;0izF)xYaau_iQAslmcG{z?#j27f6oP1Lm+@21%LV8WptwG)90H*-mJDm3 zVxOLYE*Qf~piOs{l|rPrQOmp}a9R4;)MGtkmUyky(o718;<>^)BNW9ra3;;7^NV@7 z7ifFq`fL7JqY-u4gJ|ee)$B}Z!vlPmF;`2&`kIg%&6xZ7i?aikiw~p|+bjqNOr4Zb z@O{O-F_2B~SYYfpra?%bq!PV`6cCumRKlkVg2j^dJ&UqZl2Ezqc!`>qb3i<#mVLGs znLKxq6@r3-_E@@(cPn0ehv@Q@DU!IXFx&)?2<$s;!}$UA`yV0zUSbea|?r4&mu^?PM5A@yueE zpFc&}2Q>Dl4kZDc=x8GxI!J-yNDmK#B5@!5flI73od3(9KlI}gF8sVW@wblY5?kaF zTVB$|$tkF{mA{YR@ZuuD(Es(;+`>XC9WUKhF!#UV;mC8heZC22eDNLX%Oc6$HJ@rh ze!>Ros654bM!7rtcwINZlsPH z!w!xIv<2nwGQy$I+hrmN@`xXP%I8t3`L0-Hh|`LZP<9{)LxW_7S!t;|r@`*aW;p7-O__V_BZhR_ekm=4+-tBWVRVU< zotkNzagZ3vt)YNUEAvP!Q7^2%umF4F;DwtORRzdR(5J7kyCz6ke`6!JSX<|M3RI`<|Gc)ee$w8&+%6?7)`HAM>RmO_=vA ztgr7Ab|Qb2o^wR}bL*L?fc@7K@3Cub?&txg9FQ$u|7+flrjAds>kp`}Lo&jq9|GE_ z)m4M8&QALe5ov8rjd8QXx?8Mt@7&zn$?d$6yMltkP+g2!gnsqBi-iThOPh0TTCo5` z85>mR)q#l^EaodP=1mb+m{!bI?_}cW_idmhhuTj;^0_Z1ox&yKQK$UvUtC#CRKB3H z#=QXm8+T09MX{SMj1AnlJlMNAw5wYy8iMU6=ma%$JG1(aj3tPTVWduW`o<7v)p9Q= zCo5_w?VnqQ=TQeSL{~)<0=XZvEh+1h)SyDic6Q*rre!CS;kM{WAkg4WVS`V=&0VF) zkk%Z2=?l;w zTgUVz@k*2$WXchtc8}u*8~Q0ZQkp)49meV;!Y73Tvlh8{34SsIde<+WoMMA;laq+w z$l?tvxlW2I^GYCT4K}sTu07~!W6x-v4Z#hWg*-7IM|Amv{z$NVS z3tbHrn1I2$w8@|G@bO*z{mE)$w88@Hn+s%QWMrpjmQI^yt+{bA^;>?5nZ+Eq2n^N5 zWRB6Et+ZZ`>v`TSTAs4LZLxl|`FR*TdLx@V{CK^SdY`aM-{#$oBd`&mM6^bg2)K^g z+1qn)$eU4)PYu{`mmt^eg`}O{OeQ;Qg3>g`@56Z>MJNWTG-}UDD*Lff9 zD=Dcs_toYmre*?4SSQpRjbZM52B+f&SNVi}TsTd)6 z^2LW$W!gNG8cczu00(T=Orh`nR8ZexVWRT#QO%W!O!KIw3$0X*j9bSQZ6?MMQpeAxH-*{a% za61z66`?k=6o-8PCNa{TWdixIHl>tkmtB5dv5t;Cs?VN@<}(($DU*YAVj>}tl$K&N z0Y;G^p4jJU=S8k{^(0XFwBAItt@g;z3X;15NyR^{j1smkKgR0jbG}DeFAfho3J{@M ztB73hP_sH!&f7?}O(zAf_UaLe-*0TNHRz!Z*R6PU@E^LhE!pz05ungdI3KxuuU)Jd zHD;4Uf|1JIUVBC5%~5{1xCj`}HAr7w72?~s^6RvWR#0%XJ5_9V+(tM(JG&Yf2>U45 zJl~&x@ZGz+7S9`qA|*yITlkL!WUnPFtQ4O;oP_LB9ygdKi6A%-D4Zo z?`Bfw@~FJ}YHA8A+c{;wb$NMtTlr70^Zxka<|YLzGxOs3c-;A|*TFqv=d87SrB-ut za&pR|MVTHl#Y|SLw5Ny5ifyx=R#-RN&&u35;@iI`CnsLqji7?Se7X;xe}=8CEe~L8 zma}|1dE1OJiVv|aMU+_$`TBabW71f_Xy8h`ZFU6;r`(9RA;mBu2`pX;V2= zg#iU@+cDJygoH)nl7}wvfznTo&u;7co~QNqde_@)XAKe9jrnqJvz5+I*~0s|Z4*y( zdfulUov9{!l$1O%>{aYs;Sde3#H2xe#0^m!1LJNY0I-2)8uGg|nOd5hnW85k~ zWwfHaQXNWJV7g0GAR#gHbG3n5k;6D3L{wXz*O7z~cW~HpDsK8e(I~n-=_2-g81veN z$VFQLO@<^#l4A8z#SHn@%upQTn1i!3twr0+Px9oni4*&+dUi;CT0uCRjva=*inN2F z;<=GXpo!d1y}dNq{Pm5b?=z(^4NRY%_n@22>%z3(1)^r==UHJgqWWP;1YD5PS&i)F zi?Cn;F0QVQMn-v7)hC<)K&t$wNTk1QZI6xfhRKZDRa|xx_{r42omyi6faX;(w`}LRgg}nTXcBD{<w!!=;CPW-ZIxJA-$>bN`q*6h06i0?s@dmMf zmz06~s&*N4MKVQNCkeX%4j1wFl%!)JQziEHvmef2t(KbAvzS#(T#a5Oc-^7Is{0H>}rrB-zY!N zs#}-wBa*w|mnM8PexAzLuTr_f9l^!nxq_kc{Xe(*Da`UYgg~lD5U)~G9uG=9^>|T zx&cFXoi?6@pE~24SDd0DoR(-hxDcY+j%VyJ(BnFqunJB!;6( zr_34qH?NM#WU;C8>mY5VtX1WAj#igclpk?6k#uqDh3rLJEeBrqQaV`hW)#UvUu7tm z=j$Vp{&TDdK=};9(<5%Mjm~Fd!Ae|?>)2Uxek*LGw<m&qp@Sp~6Rmg@sdSezB!M4NM&BlPAm>!>dpnXvDCy z<|$XL)=sUlYxr!CCJwGV8PU-VcVtqmKv|*-sZpxem7q+ivf&>9gqkEb@W>;<6co^~ z;C;7{8RW_|F0kkEeQcdDNbmuz`RIA~@3#BD`)>roRqNy#-FfwZnA^8j9P4fjR%iU-vy!s`S?)AKwrJQylAOz!qL9=P|F$L z9?hZveqL;d={H)>?!2Or3i8qCQBhHO+#INVjOFU;SOgL=kL`w#_Z>N)I}DZhVH)A> zTN>)aJB)u&4FEuUUzGBzaiCOwLjqid=R3P7Z+vBObAOKv3Ug{j`RFAj^8RwokvCDl z@A-2x6yF+@#@ca{v}*g*?(^t01~oO_Na1!rTM0p`08JyBVh9N#!vTJ|uls6au)Fqi zwx}|d+!1-@^4Bk`GC~kQ!tYMaqI7&R;!*ho|QkcQCz zB6Ao5VuS3yPo!CF&s7=~$&fxug;14#3j_pYko@QBbqs5Yem$9m$i26LuGpIWXa2Sd2Pj z-n&is_p`6&5JUiga=x(Nzpeb%?5Tl&i)>=yp6kenz_2WXQ2}FT%4`8Il8#ugkscKPLM9tn85ijZM^G{ zb{8vU?JVNG6zII63*6V|y+VCvEcw&%FFr_)w)4&`zncOwIY0oY9(;RucO+dvp-24% zXtO(#TDE9=6$n>dXgQH(9`be zTsk%o)QP8qU%?F#v^G541Un^4E}bM&>*; z!OYarT(X$0O}%spMKqWYLDSz$?JY^4Q%~i85zEQxXOSF+s4d@u4$+;?NiD z1*A{_dc-g&q~KFEg+(*Qen1fQB+`&{enSL3JiMl3kmL?PRKDMFs%++;AqIRcMi9yT z?{4(Wh@fD;kna26gk1mVDx2)uApV!p239CKYWYJ}VaxA_#Gv&~d<}5`n<*9-qpDrV z-kkqJ&v$p>NXZxpiEo}hD~JHQ+k3L2K{Edp06^dOb~a-S6q=&G&{R3P%TYKDR{Z1a zG@L}^^!DPZ3y2v|DDdNqswpqm_-nr;pA^z$*CutkbW7ls3l}PmIak&3BGmmf=71Jm zI@bK{p`KHc-W5b9f&3V7VrQ5yq<1CVCvvd zP5AX|zk}a()=eL_%0}N?FS-a5ZA$M)6x9;uZ71Pn+t4b;GhXVQZth*BxMO_MQ4;=s z-QRz-ImOwihS$FOq}TcorPMn!jvtiR_Hh7 z>M67 z<>5qo;azz&iXT@QD%7gC-$kcm*3odYCn(cJz}hJ)7RD;4XwgoJf04^Q1`j%a)|ECK zF`0L5q#cvyv}PtpIzj-DBT;b*=xt675gbd}RC06vLJy$e43SIHY?!0K{3O60H@9^6 z3dm;zQ98l=N<1i}E<$+$6`Vc$E#9vDJza>Ui#bZ8F&o8V*!7A$<)i&V)nT_hh61Ru z7zK4c?u`xGy?aCtTR1iNzhCz_noGi$0I4zU81S50p3|R0IH;TKUl~?C_j7#SU+=GO z4s+Nn$6to;A5Q8g>Qa(McI#MDKjdiD*RCPMn zF~t7s%X_=opSl**>GJcnncUH}d%Uu6x!8Dz1zc1av>AJNTnTXie)_+kkLSkcdHF@X zd|s$ELsb6s4u>vCjvNX*k;xq`UxdZMd9qL|(SYAS3BQPl`R{DC?Y&~-UFXX$rMpXE z?YD~Ve8C^OKBg>C-LE=Kc&&f_gcqyijmyN5z)V~}c&|U_-u57~8f$CgR~mK*@Vg%2 zRQ+g1!EEf7mW4dP|3 z9}_ktf%_w z$&^;WaSL9S-qmvdhB|LzI>i;)=VXCB7egG-y;yH~sfp~k=83#O0EdkCf(ihz8a3gh zu^99K0QfAe0I%g|%Vt;YY6}E;y-yO2|LK`!)0J{RCdMfpe3LtuiUwa!ks3Ogpwq~i zGBp0hMXXh$4GdfV<-*Vp2q@JgG8n`HpbS`<1LTuJVkSSUm3Pj1BJV>a>BSN9BDR`% zzs2x~6{uM%=#4n)3l(@Hzyu)msK&>o-u`t>&%zu0wNp$MIEf%Q_?v!riZ&~KxF@}< zJuIKDhfcl0@0FVfurX=S=GYrX9Ft-!~krZwVgMikqI-&$jGoG!P>mPz5Zv*GwDd;@qF4mS;vGeg{fH%^xT6Ix}UAI zE;ZRX8H5LQ|LN=$-h39W(5V&IE(gK_^jdyydJlx|JFVm0GVN$_Jzkiv-`$gWPrRS` z?{%#qX0ZAmIz??~OhZt9q#+A{fl;j|b-;$Bq}`kH^f${ZqV;=-i|LWyFQPG8gZ3** z1V9PzQ8$Wg0A-BnzX1L6d2+2U0Y4Ol=>Y3*uk5nhdEyj8!@c5ktHy zKMl$Y^^&@?97y{?^?_tkO|-(N7Og0N8s~jw;lT<5W!q-EW!kAjuBPAwK}QxO*tJ&Y zZz5MiRD0FRFg^*C4>gX#DzGmaMw&)OWX)a3y=*wwdt;of`pVoys8CS-@WMid6&H~p zA76w*ItzE_c{yL)_Mcp@kZ{d0`_7|ZV~*L+5XKjWf%z{j3HcG6 zp!7#qR!m)Qppm43X}C^mJZh+0E`P{MN!ze-0OwkYZ6JZ>Q;|BozT>6T%T_cZrkW_h zV2%H}Gg99a7P;M^l(t?n>t(6$yU^z{)~ePJ7(mtg6>d9dtZcYb9_zMq{nY{eR1^^4 zu=+S5$i~(^K+v)s=MR>dvfOO=UjU5&a{p;r%kwhg)?05y0D!%F_wu8UKFZ7tKm6gp z^LM{{FMs=Azbzhq_@|g85s#0L0RX_g_uj(*0C(JR$3*@KpExmqOP4O8l`AhcKls5z zli*&n=5OC)$ Date: Thu, 4 May 2017 19:27:43 +0100 Subject: [PATCH 016/135] change default particle properties --- scripts/system/edit.js | 62 +++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a6d2d165f7..10f876999a 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -309,7 +309,7 @@ var toolBar = (function () { gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 } }); } - } + } } function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. @@ -482,22 +482,52 @@ var toolBar = (function () { createNewEntity({ type: "ParticleEffect", isEmitting: true, + emitterShouldTrail: true, + color: { + red: 200, + green: 200, + blue: 200 + }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + colorStart: { + red: 200, + green: 200, + blue: 200 + }, + colorFinish: { + red: 0, + green: 0, + blue: 0 + }, emitAcceleration: { - x: 0, - y: -1, - z: 0 + x: 0.5, + y: 1.5, + z: 0.5 }, accelerationSpread: { - x: 5, - y: 0, - z: 5 + x: 1.0, + y: 0.0, + z: 1.0 }, - emitSpeed: 1, + emitRate: 10, + emitSpeed: 2, + speedSpread: 0, lifespan: 1, - particleRadius: 0.025, + maxParticles: 100, + particleRadius: 0.25, + radiusStart: 0.05, + radiusFinish: 0.25, + radiusSpread: 0.5, + alpha: 0, + alphaStart: 0.5, alphaFinish: 0, - emitRate: 100, - textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png" + polarStart: 0, + polarFinish: 0, + textures: "https://raw.githubusercontent.com/Triplelexx/hifi/40908fb1a31bd884f87e21c4e524275f8334471b/interface/resources/images/Smoke.png" }); }); @@ -656,7 +686,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { return; var data = JSON.parse(message); - + if (data.method === "selectOverlay") { print("setting selection to overlay " + data.overlayID); var entity = entityIconOverlayManager.findEntity(data.overlayID); @@ -664,7 +694,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { if (entity !== null) { selectionManager.setSelections([entity]); } - } + } } Messages.subscribe("entityToolUpdates"); @@ -774,7 +804,7 @@ function wasTabletClicked(event) { var result = Overlays.findRayIntersection(rayPick, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); return result.intersects; } - + function mouseClickEvent(event) { var wantDebug = false; var result, properties, tabletClicked; @@ -784,7 +814,7 @@ function mouseClickEvent(event) { if (tabletClicked) { return; } - + if (result === null || result === undefined) { if (!event.isShifted) { selectionManager.clearSelections(); @@ -2062,7 +2092,7 @@ function selectParticleEntity(entityID) { selectedParticleEntity = entityID; particleExplorerTool.setActiveParticleEntity(entityID); - particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); + particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); // Switch to particle explorer var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); From fae51c5c4652b2bb623168243968b0474f91c4a7 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Sat, 6 May 2017 20:12:09 +0100 Subject: [PATCH 017/135] change default particle texture to production --- scripts/system/edit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 10f876999a..28d7b89209 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -488,7 +488,7 @@ var toolBar = (function () { green: 200, blue: 200 }, - colorSpread: { + colorSpread: { red: 0, green: 0, blue: 0 @@ -527,7 +527,7 @@ var toolBar = (function () { alphaFinish: 0, polarStart: 0, polarFinish: 0, - textures: "https://raw.githubusercontent.com/Triplelexx/hifi/40908fb1a31bd884f87e21c4e524275f8334471b/interface/resources/images/Smoke.png" + textures: "https://hifi-content.s3.amazonaws.com/DomainContent/production/Particles/Smoke.png" }); }); From 03490c81da5a6a76f06f13e1341d44d4fcea9f5e Mon Sep 17 00:00:00 2001 From: Mike Moody Date: Sat, 6 May 2017 15:36:18 -0700 Subject: [PATCH 018/135] v1 with bug, looking into. --- .../system/libraries/entitySelectionTool.js | 101 ++++++++++++++++-- 1 file changed, 94 insertions(+), 7 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 79d45d5cd2..70ded31ca4 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -340,6 +340,11 @@ SelectionDisplay = (function() { green: 120, blue: 120 }; + var grabberColorCloner = { + red: 0, + green: 155, + blue: 0 + }; var grabberLineWidth = 0.5; var grabberSolid = true; var grabberMoveUpPosition = { @@ -405,6 +410,23 @@ SelectionDisplay = (function() { borderSize: 1.4, }; + var grabberPropertiesCloner = { + position: { + x: 0, + y: 0, + z: 0 + }, + size: grabberSizeCorner + 0.025, + color: grabberColorCloner, + alpha: 1, + solid: grabberSolid, + visible: false, + dashed: false, + lineWidth: grabberLineWidth, + drawInFront: true, + borderSize: 1.4, + }; + var spotLightLineProperties = { color: lightOverlayColor, lineWidth: 1.5, @@ -582,6 +604,8 @@ SelectionDisplay = (function() { var grabberPointLightF = Overlays.addOverlay("cube", grabberPropertiesEdge); var grabberPointLightN = Overlays.addOverlay("cube", grabberPropertiesEdge); + var grabberCloner = Overlays.addOverlay("cube", grabberPropertiesCloner); + var stretchHandles = [ grabberLBN, grabberRBN, @@ -969,6 +993,9 @@ SelectionDisplay = (function() { grabberPointLightCircleX, grabberPointLightCircleY, grabberPointLightCircleZ, + + grabberCloner + ].concat(stretchHandles); overlayNames[highlightBox] = "highlightBox"; @@ -1015,7 +1042,7 @@ SelectionDisplay = (function() { overlayNames[rotateZeroOverlay] = "rotateZeroOverlay"; overlayNames[rotateCurrentOverlay] = "rotateCurrentOverlay"; - + overlayNames[grabberCloner] = "grabberCloner"; var activeTool = null; var grabberTools = {}; @@ -1107,7 +1134,7 @@ SelectionDisplay = (function() { if (event !== false) { pickRay = generalComputePickRay(event.x, event.y); - var wantDebug = false; + var wantDebug = true; if (wantDebug) { print("select() with EVENT...... "); print(" event.y:" + event.y); @@ -2291,7 +2318,11 @@ SelectionDisplay = (function() { }, rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), }); - + Overlays.editOverlay(grabberCloner, { + visible: stretchHandlesVisible, + rotation: rotation, + position: RIGHT + }); }; @@ -2371,7 +2402,7 @@ SelectionDisplay = (function() { return (origin.y - intersection.y) / Vec3.distance(origin, intersection); }, onMove: function(event) { - var wantDebug = false; + var wantDebug = true; pickRay = generalComputePickRay(event.x, event.y); var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { @@ -2556,7 +2587,7 @@ SelectionDisplay = (function() { vector.x = 0; vector.z = 0; - var wantDebug = false; + var wantDebug = true; if (wantDebug) { print("translateUpDown... "); print(" event.y:" + event.y); @@ -2580,6 +2611,52 @@ SelectionDisplay = (function() { }, }); + addGrabberTool(grabberCloner, { + mode: "CLONE", + pickPlanePosition: { x: 0, y: 0, z: 0 }, + greatestDimension: 0.0, + startingDistance: 0.0, + startingElevation: 0.0, + onBegin: function(event) { + SelectionManager.saveProperties(); + startPosition = SelectionManager.worldPosition; + var dimensions = SelectionManager.worldDimensions; + + var pickRay = generalComputePickRay(event.x, event.y); + initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { + x: 0, + y: 1, + z: 0 + }); + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + + duplicatedEntityIDs = []; + for (var otherEntityID in SelectionManager.savedProperties) { + var properties = SelectionManager.savedProperties[otherEntityID]; + if (!properties.locked) { + var entityID = Entities.addEntity(properties); + duplicatedEntityIDs.push({ + entityID: entityID, + properties: properties, + }); + } + } + + isConstrained = false; + }, + elevation: translateXZTool.elevation, + + onEnd: translateXZTool.onEnd, + + onMove: translateXZTool.onMove + }); + + + + var vec3Mult = function(v1, v2) { return { x: v1.x * v2.x, @@ -2844,7 +2921,7 @@ SelectionDisplay = (function() { }); } - var wantDebug = false; + var wantDebug = true; if (wantDebug) { print(stretchMode); //Vec3.print(" newIntersection:", newIntersection); @@ -3861,7 +3938,7 @@ SelectionDisplay = (function() { }; that.mousePressEvent = function(event) { - var wantDebug = false; + var wantDebug = true; if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; @@ -3958,6 +4035,10 @@ SelectionDisplay = (function() { mode = "STRETCH_LEFT"; somethingClicked = mode; break; + // case grabberCloner: + // mode = "CLONE"; + // somethingClicked = mode; + // break; default: mode = "UNKNOWN"; @@ -4344,6 +4425,12 @@ SelectionDisplay = (function() { highlightNeeded = true; break; + case grabberCloner: + pickedColor = grabberColorCloner; + pickedAlpha = grabberAlpha; + highlightNeeded = true; + break; + default: if (previousHandle) { Overlays.editOverlay(previousHandle, { From 1873558df15ec8d8bbbdcac7a275738469c1af10 Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Sat, 6 May 2017 18:41:57 -0700 Subject: [PATCH 019/135] added floating lantern box that spawns floating lanterns --- scripts/tutorials/createFloatingLanternBox.js | 42 +++++++ .../entity_scripts/floatingLantern.js | 106 ++++++++++++++++++ .../entity_scripts/floatingLanternBox.js | 101 +++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 scripts/tutorials/createFloatingLanternBox.js create mode 100644 scripts/tutorials/entity_scripts/floatingLantern.js create mode 100644 scripts/tutorials/entity_scripts/floatingLanternBox.js diff --git a/scripts/tutorials/createFloatingLanternBox.js b/scripts/tutorials/createFloatingLanternBox.js new file mode 100644 index 0000000000..611e995fcb --- /dev/null +++ b/scripts/tutorials/createFloatingLanternBox.js @@ -0,0 +1,42 @@ +"use strict"; +/* jslint vars: true, plusplus: true, forin: true*/ +/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// createFloatinLanternBox.js +// +// Created by MrRoboman on 17/05/04 +// Copyright 2017 High Fidelity, Inc. +// +// Creates a crate that spawn floating lanterns +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var COMPOUND_SHAPE_URL = "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/maracas/woodenCrate_phys.obj"; +var MODEL_URL = "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/maracas/woodenCrate_VR.fbx"; +var SCRIPT_URL = Script.resolvePath("./entity_scripts/floatingLanternBox.js?v=" + Date.now()); +var START_POSITION = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), 2)); +START_POSITION.y -= .6; +var LIFETIME = 3600; + +var lanternBox = { + type: "Model", + name: "Floating Lantern Box", + description: "Spawns Lanterns that float away when grabbed and released!", + script: SCRIPT_URL, + modelURL: MODEL_URL, + shapeType: "Compound", + compoundShapeURL: COMPOUND_SHAPE_URL, + position: START_POSITION, + lifetime: LIFETIME, + dimensions: { + x: 0.8696, + y: 0.58531, + z: 0.9264 + }, + owningAvatarID: MyAvatar.sessionUUID +}; + +Entities.addEntity(lanternBox); +Script.stop(); diff --git a/scripts/tutorials/entity_scripts/floatingLantern.js b/scripts/tutorials/entity_scripts/floatingLantern.js new file mode 100644 index 0000000000..8fa2828c90 --- /dev/null +++ b/scripts/tutorials/entity_scripts/floatingLantern.js @@ -0,0 +1,106 @@ +"use strict"; +/* jslint vars: true, plusplus: true, forin: true*/ +/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// floatinLantern.js +// +// Created by MrRoboman on 17/05/04 +// Copyright 2017 High Fidelity, Inc. +// +// Makes floating lanterns rise upon being released and corrects their rotation as the fly. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +(function() { + var _this; + + var SLOW_SPIN_THRESHOLD = 0.1; + var ROTATION_COMPLETE_THRESHOLD = 0.01; + var ROTATION_SPEED = 0.2; + var HOME_ROTATION = {x: 0, y: 0, z: 0, w: 0}; + + + floatingLantern = function() { + _this = this; + this.updateConnected = false; + }; + + floatingLantern.prototype = { + + preload: function(entityID) { + this.entityID = entityID; + }, + + unload: function(entityID) { + this.disconnectUpdate(); + }, + + startNearGrab: function() { + this.disconnectUpdate(); + }, + + startDistantGrab: function() { + this.disconnectUpdate(); + }, + + releaseGrab: function() { + Entities.editEntity(this.entityID, { + gravity: { + x: 0, + y: 0.5, + z: 0 + } + }); + }, + + update: function(dt) { + var lanternProps = Entities.getEntityProperties(_this.entityID); + + if(lanternProps && lanternProps.rotation && lanternProps.owningAvatarID === MyAvatar.sessionUUID) { + + var spinningSlowly = ( + Math.abs(lanternProps.angularVelocity.x) < SLOW_SPIN_THRESHOLD && + Math.abs(lanternProps.angularVelocity.y) < SLOW_SPIN_THRESHOLD && + Math.abs(lanternProps.angularVelocity.z) < SLOW_SPIN_THRESHOLD + ); + + var rotationComplete = ( + Math.abs(lanternProps.rotation.x - HOME_ROTATION.x) < ROTATION_COMPLETE_THRESHOLD && + Math.abs(lanternProps.rotation.y - HOME_ROTATION.y) < ROTATION_COMPLETE_THRESHOLD && + Math.abs(lanternProps.rotation.z - HOME_ROTATION.z) < ROTATION_COMPLETE_THRESHOLD + ); + + if(spinningSlowly && !rotationComplete) { + var newRotation = Quat.slerp(lanternProps.rotation, HOME_ROTATION, ROTATION_SPEED * dt); + + Entities.editEntity(_this.entityID, { + rotation: newRotation, + angularVelocity: { + x: 0, + y: 0, + z: 0 + } + }); + } + } + }, + + connectUpdate: function() { + if(!this.updateConnected) { + this.updateConnected = true; + Script.update.connect(this.update); + } + }, + + disconnectUpdate: function() { + if(this.updateConnected) { + this.updateConnected = false; + Script.update.disconnect(this.update); + } + } + }; + + return new floatingLantern(); +}); diff --git a/scripts/tutorials/entity_scripts/floatingLanternBox.js b/scripts/tutorials/entity_scripts/floatingLanternBox.js new file mode 100644 index 0000000000..2c483f6129 --- /dev/null +++ b/scripts/tutorials/entity_scripts/floatingLanternBox.js @@ -0,0 +1,101 @@ +"use strict"; +/* jslint vars: true, plusplus: true, forin: true*/ +/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// floatingLanternBox.js +// +// Created by MrRoboman on 17/05/04 +// Copyright 2017 High Fidelity, Inc. +// +// Spawns new floating lanterns every couple seconds if the old ones have been removed. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +(function() { + + var _this; + var LANTERN_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Welcome%20Area/Models/chinaLantern_capsule.fbx"; + var LANTERN_SCRIPT_URL = Script.resolvePath("floatingLantern.js?v=" + Date.now()); + var LIFETIME = 120; + var RESPAWN_INTERVAL = 1000; + var MAX_LANTERNS = 4; + + var LANTERN = { + type: "Model", + name: "Floating Lantern", + description: "Spawns Lanterns that float away when grabbed and released!", + modelURL: LANTERN_MODEL_URL, + script: LANTERN_SCRIPT_URL, + dimensions: { + x: 0.2049, + y: 0.4, + z: 0.2049 + }, + gravity: { + x: 0, + y: -1, + z: 0 + }, + velocity: { + x: 0, y: .01, z: 0 + }, + linearDampening: 0, + shapeType: 'Box', + lifetime: LIFETIME, + dynamic: true + }; + + lanternBox = function() { + _this = this; + }; + + lanternBox.prototype = { + + preload: function(entityID) { + this.entityID = entityID; + var props = Entities.getEntityProperties(this.entityID); + + if(props.owningAvatarID === MyAvatar.sessionUUID){ + this.respawnTimer = Script.setInterval(this.spawnAllLanterns.bind(this), RESPAWN_INTERVAL); + } + }, + + unload: function(entityID) { + if(this.respawnTimer) + Script.clearInterval(this.respawnTimer); + }, + + spawnAllLanterns: function() { + var props = Entities.getEntityProperties(this.entityID); + var lanternCount = 0; + var nearbyEntities = Entities.findEntities(props.position, props.dimensions.x * 0.75); + + for(var i = 0; i < nearbyEntities.length; i++) { + var name = Entities.getEntityProperties(nearbyEntities[i], ["name"]).name; + if(name === "Floating Lantern") { + lanternCount++; + } + } + + while(lanternCount++ < MAX_LANTERNS) { + this.spawnLantern(); + } + }, + + spawnLantern: function() { + var boxProps = Entities.getEntityProperties(this.entityID); + + LANTERN.position = boxProps.position; + LANTERN.position.x += Math.random() * .2 - .1; + LANTERN.position.y += Math.random() * .2 + .1; + LANTERN.position.z += Math.random() * .2 - .1; + LANTERN.owningAvatarID = boxProps.owningAvatarID; + + return Entities.addEntity(LANTERN); + } + }; + + return new lanternBox(); +}); From dae652d3db31309f90bbbb63df61c9c1b692187f Mon Sep 17 00:00:00 2001 From: Mike Moody Date: Mon, 8 May 2017 11:25:24 -0700 Subject: [PATCH 020/135] v1 HMD duplication overlay. --- .../system/libraries/entitySelectionTool.js | 70 +++++++------------ 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 70ded31ca4..3e4b8d8518 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -416,7 +416,7 @@ SelectionDisplay = (function() { y: 0, z: 0 }, - size: grabberSizeCorner + 0.025, + size: grabberSizeCorner, color: grabberColorCloner, alpha: 1, solid: grabberSolid, @@ -652,6 +652,8 @@ SelectionDisplay = (function() { grabberPointLightR, grabberPointLightF, grabberPointLightN, + + grabberCloner ]; @@ -994,8 +996,6 @@ SelectionDisplay = (function() { grabberPointLightCircleY, grabberPointLightCircleZ, - grabberCloner - ].concat(stretchHandles); overlayNames[highlightBox] = "highlightBox"; @@ -1134,7 +1134,7 @@ SelectionDisplay = (function() { if (event !== false) { pickRay = generalComputePickRay(event.x, event.y); - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print("select() with EVENT...... "); print(" event.y:" + event.y); @@ -2355,7 +2355,7 @@ SelectionDisplay = (function() { greatestDimension: 0.0, startingDistance: 0.0, startingElevation: 0.0, - onBegin: function(event) { + onBegin: function(event,isAltFromGrab) { SelectionManager.saveProperties(); startPosition = SelectionManager.worldPosition; var dimensions = SelectionManager.worldDimensions; @@ -2370,7 +2370,7 @@ SelectionDisplay = (function() { // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not // the new ones. - if (event.isAlt) { + if (event.isAlt || isAltFromGrab) { duplicatedEntityIDs = []; for (var otherEntityID in SelectionManager.savedProperties) { var properties = SelectionManager.savedProperties[otherEntityID]; @@ -2402,7 +2402,7 @@ SelectionDisplay = (function() { return (origin.y - intersection.y) / Vec3.distance(origin, intersection); }, onMove: function(event) { - var wantDebug = true; + var wantDebug = false; pickRay = generalComputePickRay(event.x, event.y); var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { @@ -2587,7 +2587,7 @@ SelectionDisplay = (function() { vector.x = 0; vector.z = 0; - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print("translateUpDown... "); print(" event.y:" + event.y); @@ -2613,45 +2613,27 @@ SelectionDisplay = (function() { addGrabberTool(grabberCloner, { mode: "CLONE", - pickPlanePosition: { x: 0, y: 0, z: 0 }, - greatestDimension: 0.0, - startingDistance: 0.0, - startingElevation: 0.0, onBegin: function(event) { - SelectionManager.saveProperties(); - startPosition = SelectionManager.worldPosition; - var dimensions = SelectionManager.worldDimensions; var pickRay = generalComputePickRay(event.x, event.y); - initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { - x: 0, - y: 1, - z: 0 - }); + var result = Overlays.findRayIntersection(pickRay); + translateXZTool.pickPlanePosition = result.intersection; + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), + SelectionManager.worldDimensions.z); - // Duplicate entities if alt is pressed. This will make a - // copy of the selected entities and move the _original_ entities, not - // the new ones. - - duplicatedEntityIDs = []; - for (var otherEntityID in SelectionManager.savedProperties) { - var properties = SelectionManager.savedProperties[otherEntityID]; - if (!properties.locked) { - var entityID = Entities.addEntity(properties); - duplicatedEntityIDs.push({ - entityID: entityID, - properties: properties, - }); - } - } - - isConstrained = false; + translateXZTool.onBegin(event,true); + }, + elevation: function (event) { + translateXZTool.elevation(event); }, - elevation: translateXZTool.elevation, - onEnd: translateXZTool.onEnd, + onEnd: function (event) { + translateXZTool.onEnd(event); + }, - onMove: translateXZTool.onMove + onMove: function (event) { + translateXZTool.onMove(event); + } }); @@ -2921,7 +2903,7 @@ SelectionDisplay = (function() { }); } - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print(stretchMode); //Vec3.print(" newIntersection:", newIntersection); @@ -3938,7 +3920,7 @@ SelectionDisplay = (function() { }; that.mousePressEvent = function(event) { - var wantDebug = true; + var wantDebug = false; if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; @@ -4035,10 +4017,6 @@ SelectionDisplay = (function() { mode = "STRETCH_LEFT"; somethingClicked = mode; break; - // case grabberCloner: - // mode = "CLONE"; - // somethingClicked = mode; - // break; default: mode = "UNKNOWN"; From 138a987a8fb43e51caaaca48c870ad0458ae40cb Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 May 2017 14:27:18 -0700 Subject: [PATCH 021/135] _rotationChanged is a timestamp --- interface/src/avatar/MyAvatar.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4926368f4d..253a8addcc 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -402,9 +402,9 @@ void MyAvatar::update(float deltaTime) { _hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau); if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) { - _rotationChanged = true; - _smoothOrientationTimer+= deltaTime; - } + _rotationChanged = usecTimestampNow(); + _smoothOrientationTimer += deltaTime; + } #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y)); @@ -1825,10 +1825,10 @@ void MyAvatar::updateOrientation(float deltaTime) { // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another // snap turn every half second. - bool snapTurn = false; + bool snapTurn = false; if (getDriveKey(STEP_YAW) != 0.0f) { totalBodyYaw += getDriveKey(STEP_YAW); - snapTurn = true; + snapTurn = true; } // use head/HMD orientation to turn while flying @@ -1865,8 +1865,8 @@ void MyAvatar::updateOrientation(float deltaTime) { glm::quat initialOrientation = getOrientationOutbound(); setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)))); - if (snapTurn) { - // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. + if (snapTurn) { + // Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position. _smoothOrientationInitial = initialOrientation; _smoothOrientationTarget = getOrientation(); _smoothOrientationTimer = 0.0f; From afe044732230626ee4af5e5cca5abb2315106e90 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 May 2017 14:27:39 -0700 Subject: [PATCH 022/135] remove unnecessary virtual keyword --- interface/src/avatar/MyAvatar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index afe7fd017a..901ddbef76 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -188,7 +188,7 @@ public: Q_INVOKABLE QVariant getOrientationVar() const; // A method intended to be overriden by MyAvatar for polling orientation for network transmission. - virtual glm::quat getOrientationOutbound() const override; + glm::quat getOrientationOutbound() const override; // Pass a recent sample of the HMD to the avatar. // This can also update the avatar's position to follow the HMD From 13186dcb78c6c1d4464d99368f393fb8982d366f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 May 2017 14:28:08 -0700 Subject: [PATCH 023/135] replace tab characer r with spaces --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b1b10f1427..14f80d1f11 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1490,7 +1490,7 @@ void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& ide } glm::quat AvatarData::getOrientationOutbound() const { - return (getLocalOrientation()); + return (getLocalOrientation()); } static const QUrl emptyURL(""); From a260163aee756b185572f6a32f54323ca5056dc9 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 2 May 2017 18:22:29 -0700 Subject: [PATCH 024/135] WIP commit, first pass at generating limit center joints + debug draw --- interface/src/avatar/MyAvatar.cpp | 23 +++++++++++++++ .../animation/src/AnimInverseKinematics.cpp | 14 +++++++++ .../animation/src/AnimInverseKinematics.h | 5 ++++ libraries/animation/src/AnimUtil.cpp | 18 ++++++++++++ libraries/animation/src/AnimUtil.h | 4 +-- libraries/animation/src/ElbowConstraint.cpp | 8 +++++ libraries/animation/src/ElbowConstraint.h | 1 + libraries/animation/src/Rig.cpp | 29 +++++++++++-------- libraries/animation/src/Rig.h | 3 ++ libraries/animation/src/RotationConstraint.h | 3 ++ .../animation/src/SwingTwistConstraint.cpp | 27 +++++++++++++++++ .../animation/src/SwingTwistConstraint.h | 6 ++-- 12 files changed, 125 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3f3ce7d9e9..7d2aefa7bf 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -1701,6 +1702,28 @@ void MyAvatar::postUpdate(float deltaTime) { initAnimGraph(); } + // AJT: REMOVE. + { + auto ikNode = _rig->getAnimInverseKinematicsNode(); + if (ikNode) { + // the rig is in the skeletonModel frame + AnimPose xform(glm::vec3(1), _skeletonModel->getRotation(), _skeletonModel->getTranslation()); + AnimPoseVec limitCenterPoses = ikNode->getLimitCenterPoses(); + + // HACK: convert joints from geom to avatar space + int hipsIndex = _rig->indexOfJoint("Hips"); + for (size_t i = 0; i < limitCenterPoses.size(); i++) { + if (i == hipsIndex) { + //limitCenterPoses[i].trans() = glm::vec3(); // zero the hips + } + // convert from cm to m + limitCenterPoses[i].trans() = 0.01f * limitCenterPoses[i].trans(); + } + _rig->getAnimSkeleton()->convertRelativePosesToAbsolute(limitCenterPoses); + AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarLimitCenterPoses", _rig->getAnimSkeleton(), limitCenterPoses, xform, glm::vec4(1)); + } + } + if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { auto animSkeleton = _rig->getAnimSkeleton(); diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 6edd969568..e21db11eed 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -957,6 +957,19 @@ void AnimInverseKinematics::initConstraints() { } } +void AnimInverseKinematics::initLimitCenterPoses() { + assert(_skeleton); + _limitCenterPoses.reserve(_skeleton->getNumJoints()); + for (int i = 0; i < _skeleton->getNumJoints(); i++) { + AnimPose pose = _skeleton->getRelativeDefaultPose(i); + RotationConstraint* constraint = getConstraint(i); + if (constraint) { + pose.rot() = constraint->computeCenterRotation(); + } + _limitCenterPoses.push_back(pose); + } +} + void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { AnimNode::setSkeletonInternal(skeleton); @@ -973,6 +986,7 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele if (skeleton) { initConstraints(); + initLimitCenterPoses(); _headIndex = _skeleton->nameToJointIndex("Head"); _hipsIndex = _skeleton->nameToJointIndex("Hips"); diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index c91b7aa9c4..e7427d9ebc 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -43,6 +43,9 @@ public: float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; } + // AJT: TODO REMOVE? for debugging. + const AnimPoseVec& getLimitCenterPoses() const { return _limitCenterPoses; } + protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solveWithCyclicCoordinateDescent(const std::vector& targets); @@ -55,6 +58,7 @@ protected: RotationConstraint* getConstraint(int index); void clearConstraints(); void initConstraints(); + void initLimitCenterPoses(); void computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt); // no copies @@ -85,6 +89,7 @@ protected: std::vector _targetVarVec; AnimPoseVec _defaultRelativePoses; // poses of the relaxed state AnimPoseVec _relativePoses; // current relative poses + AnimPoseVec _limitCenterPoses; // relative // experimental data for moving hips during IK glm::vec3 _hipsOffset { Vectors::ZERO }; diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index c5643034e5..314f4a1c3a 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -33,6 +33,24 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A } } +glm::quat averageQuats(size_t numQuats, const glm::quat* quats) { + if (numQuats == 0) { + return glm::quat(); + } + float alpha = 1.0f / (float)numQuats; + glm::quat accum(0, 0, 0, 0); + glm::quat firstRot = quats[0]; + for (size_t i = 0; i < numQuats; i++) { + glm::quat rot = quats[i]; + float dot = glm::dot(firstRot, rot); + if (dot < 0.0f) { + rot = -rot; + } + accum += alpha * rot; + } + return glm::normalize(accum); +} + float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, const QString& id, AnimNode::Triggers& triggersOut) { diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 6d394be882..055fd630eb 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -16,9 +16,9 @@ // this is where the magic happens void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result); +glm::quat averageQuats(size_t numQuats, const glm::quat* quats); + float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, const QString& id, AnimNode::Triggers& triggersOut); #endif - - diff --git a/libraries/animation/src/ElbowConstraint.cpp b/libraries/animation/src/ElbowConstraint.cpp index 6833c1762e..17c6bb2da6 100644 --- a/libraries/animation/src/ElbowConstraint.cpp +++ b/libraries/animation/src/ElbowConstraint.cpp @@ -13,6 +13,7 @@ #include #include +#include "AnimUtil.h" ElbowConstraint::ElbowConstraint() : _minAngle(-PI), @@ -77,3 +78,10 @@ bool ElbowConstraint::apply(glm::quat& rotation) const { return false; } +glm::quat ElbowConstraint::computeCenterRotation() const { + const size_t NUM_LIMITS = 2; + glm::quat limits[NUM_LIMITS]; + limits[0] = glm::angleAxis(_minAngle, _axis) * _referenceRotation; + limits[1] = glm::angleAxis(_maxAngle, _axis) * _referenceRotation; + return averageQuats(NUM_LIMITS, limits); +} diff --git a/libraries/animation/src/ElbowConstraint.h b/libraries/animation/src/ElbowConstraint.h index 21288715b5..868f5cdc6b 100644 --- a/libraries/animation/src/ElbowConstraint.h +++ b/libraries/animation/src/ElbowConstraint.h @@ -18,6 +18,7 @@ public: void setHingeAxis(const glm::vec3& axis); void setAngleLimits(float minAngle, float maxAngle); virtual bool apply(glm::quat& rotation) const override; + virtual glm::quat computeCenterRotation() const override; protected: glm::vec3 _axis; glm::vec3 _perpAxis; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 700761b248..b66b0eafa5 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -305,30 +305,35 @@ void Rig::clearJointAnimationPriority(int index) { } } -void Rig::clearIKJointLimitHistory() { +std::shared_ptr Rig::getAnimInverseKinematicsNode() const { + std::shared_ptr result; if (_animNode) { _animNode->traverse([&](AnimNode::Pointer node) { // only report clip nodes as valid roles. auto ikNode = std::dynamic_pointer_cast(node); if (ikNode) { - ikNode->clearIKJointLimitHistory(); + result = ikNode; + return false; + } else { + return true; } - return true; }); } + return result; +} + +void Rig::clearIKJointLimitHistory() { + auto ikNode = getAnimInverseKinematicsNode(); + if (ikNode) { + ikNode->clearIKJointLimitHistory(); + } } void Rig::setMaxHipsOffsetLength(float maxLength) { _maxHipsOffsetLength = maxLength; - - if (_animNode) { - _animNode->traverse([&](AnimNode::Pointer node) { - auto ikNode = std::dynamic_pointer_cast(node); - if (ikNode) { - ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength); - } - return true; - }); + auto ikNode = getAnimInverseKinematicsNode(); + if (ikNode) { + ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 2d024628f5..e0c5e9f421 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -26,6 +26,7 @@ #include "SimpleMovingAverage.h" class Rig; +class AnimInverseKinematics; typedef std::shared_ptr RigPointer; // Rig instances are reentrant. @@ -111,6 +112,8 @@ public: void clearJointStates(); void clearJointAnimationPriority(int index); + std::shared_ptr Rig::getAnimInverseKinematicsNode() const; + void clearIKJointLimitHistory(); void setMaxHipsOffsetLength(float maxLength); float getMaxHipsOffsetLength() const; diff --git a/libraries/animation/src/RotationConstraint.h b/libraries/animation/src/RotationConstraint.h index 277e5293c6..e4a5334d41 100644 --- a/libraries/animation/src/RotationConstraint.h +++ b/libraries/animation/src/RotationConstraint.h @@ -38,6 +38,9 @@ public: /// \brief reset any remembered joint limit history virtual void clearHistory() {}; + /// \brief return the rotation that lies at the "center" of all the joint limits. + virtual glm::quat computeCenterRotation() const = 0; + protected: glm::quat _referenceRotation = glm::quat(); }; diff --git a/libraries/animation/src/SwingTwistConstraint.cpp b/libraries/animation/src/SwingTwistConstraint.cpp index 12d7e618e5..c1b325a74a 100644 --- a/libraries/animation/src/SwingTwistConstraint.cpp +++ b/libraries/animation/src/SwingTwistConstraint.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "AnimUtil.h" const float MIN_MINDOT = -0.999f; @@ -430,3 +431,29 @@ void SwingTwistConstraint::dynamicallyAdjustLimits(const glm::quat& rotation) { void SwingTwistConstraint::clearHistory() { _lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY; } + +glm::quat SwingTwistConstraint::computeCenterRotation() const { + const size_t NUM_MIN_DOTS = getMinDots().size(); + const size_t NUM_LIMITS = 2 * NUM_MIN_DOTS; + std::vector limits; + limits.reserve(NUM_LIMITS); + glm::quat minTwistRot; + glm::quat maxTwistRot; + if (_minTwist != _maxTwist) { + minTwistRot = glm::angleAxis(_minTwist, _referenceRotation * Vectors::UNIT_Y); + minTwistRot = glm::angleAxis(_maxTwist, _referenceRotation * Vectors::UNIT_Y); + } + const float D_THETA = TWO_PI / NUM_MIN_DOTS; + float theta = 0.0f; + for (size_t i = 0; i < NUM_MIN_DOTS; i++, theta += D_THETA) { + // compute swing rotation from theta and phi angles. + float phi = acos(getMinDots()[i]); + float cos_phi = getMinDots()[i]; + float sin_phi = sinf(phi); + glm::vec3 swungAxis(sin_phi * cosf(theta), cos_phi, sin_phi * sinf(theta)); + glm::quat swing = glm::angleAxis(phi, glm::cross(Vectors::UNIT_Y, swungAxis)); + limits.push_back(swing * minTwistRot); + limits.push_back(swing * maxTwistRot); + } + return averageQuats(limits.size(), &limits[0]); +} diff --git a/libraries/animation/src/SwingTwistConstraint.h b/libraries/animation/src/SwingTwistConstraint.h index 06afd64232..295edb3ebf 100644 --- a/libraries/animation/src/SwingTwistConstraint.h +++ b/libraries/animation/src/SwingTwistConstraint.h @@ -58,7 +58,7 @@ public: virtual void dynamicallyAdjustLimits(const glm::quat& rotation) override; // for testing purposes - const std::vector& getMinDots() { return _swingLimitFunction.getMinDots(); } + const std::vector& getMinDots() const { return _swingLimitFunction.getMinDots(); } // SwingLimitFunction is an implementation of the constraint check described in the paper: // "The Parameterization of Joint Rotation with the Unit Quaternion" by Quang Liu and Edmond C. Prakash @@ -81,7 +81,7 @@ public: float getMinDot(float theta) const; // for testing purposes - const std::vector& getMinDots() { return _minDots; } + const std::vector& getMinDots() const { return _minDots; } private: // the limits are stored in a lookup table with cyclic boundary conditions @@ -99,6 +99,8 @@ public: void clearHistory() override; + virtual glm::quat computeCenterRotation() const override; + private: float handleTwistBoundaryConditions(float twistAngle) const; From e992d6703a8faf21e5a38cab5a694bd48da8ff06 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 3 May 2017 18:51:17 -0700 Subject: [PATCH 025/135] WIP: debug render joint constraints. --- interface/src/avatar/MyAvatar.cpp | 4 +- libraries/animation/src/AnimContext.cpp | 6 +- libraries/animation/src/AnimContext.h | 4 +- .../animation/src/AnimInverseKinematics.cpp | 120 +++++++++++++++++- .../animation/src/AnimInverseKinematics.h | 3 +- libraries/animation/src/AnimPose.cpp | 7 +- libraries/animation/src/AnimPose.h | 3 +- libraries/animation/src/ElbowConstraint.h | 5 + libraries/animation/src/Rig.cpp | 6 +- libraries/animation/src/Rig.h | 2 +- .../animation/src/SwingTwistConstraint.cpp | 22 ++-- .../animation/src/SwingTwistConstraint.h | 3 + libraries/render-utils/src/Model.cpp | 3 +- 13 files changed, 164 insertions(+), 24 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7d2aefa7bf..526b80e3f1 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1703,6 +1703,7 @@ void MyAvatar::postUpdate(float deltaTime) { } // AJT: REMOVE. + /* { auto ikNode = _rig->getAnimInverseKinematicsNode(); if (ikNode) { @@ -1714,7 +1715,7 @@ void MyAvatar::postUpdate(float deltaTime) { int hipsIndex = _rig->indexOfJoint("Hips"); for (size_t i = 0; i < limitCenterPoses.size(); i++) { if (i == hipsIndex) { - //limitCenterPoses[i].trans() = glm::vec3(); // zero the hips + limitCenterPoses[i].trans() = glm::vec3(); // zero the hips } // convert from cm to m limitCenterPoses[i].trans() = 0.01f * limitCenterPoses[i].trans(); @@ -1723,6 +1724,7 @@ void MyAvatar::postUpdate(float deltaTime) { AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarLimitCenterPoses", _rig->getAnimSkeleton(), limitCenterPoses, xform, glm::vec4(1)); } } + */ if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { diff --git a/libraries/animation/src/AnimContext.cpp b/libraries/animation/src/AnimContext.cpp index c8d3e7bcda..c59c75b191 100644 --- a/libraries/animation/src/AnimContext.cpp +++ b/libraries/animation/src/AnimContext.cpp @@ -10,7 +10,9 @@ #include "AnimContext.h" -AnimContext::AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix) : +AnimContext::AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) : _enableDebugDrawIKTargets(enableDebugDrawIKTargets), - _geometryToRigMatrix(geometryToRigMatrix) { + _geometryToRigMatrix(geometryToRigMatrix), + _rigToWorldMatrix(rigToWorldMatrix) +{ } diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index 3170911e14..067e64026a 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -16,15 +16,17 @@ class AnimContext { public: - AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix); + AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix); bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; } const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; } + const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; } protected: bool _enableDebugDrawIKTargets { false }; glm::mat4 _geometryToRigMatrix; + glm::mat4 _rigToWorldMatrix; }; #endif // hifi_AnimContext_h diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index e21db11eed..ae2ca94d66 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -399,6 +399,8 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { + debugDrawConstraints(context); + const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay if (dt > MAX_OVERLAY_DT) { dt = MAX_OVERLAY_DT; @@ -604,9 +606,9 @@ void AnimInverseKinematics::clearIKJointLimitHistory() { } } -RotationConstraint* AnimInverseKinematics::getConstraint(int index) { +RotationConstraint* AnimInverseKinematics::getConstraint(int index) const { RotationConstraint* constraint = nullptr; - std::map::iterator constraintItr = _constraints.find(index); + std::map::const_iterator constraintItr = _constraints.find(index); if (constraintItr != _constraints.end()) { constraint = constraintItr->second; } @@ -1003,3 +1005,117 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele _hipsParentIndex = -1; } } + +void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) const { + + if (_skeleton) { + const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); + const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + const vec4 PURPLE(0.5f, 0.0f, 1.0f, 1.0f); + const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f); + const vec4 GRAY(0.2f, 0.2f, 0.2f, 1.0f); + const vec4 MAGENTA(1.0f, 0.0f, 1.0f, 1.0f); + const float AXIS_LENGTH = 2.0f; // cm + const float TWIST_LENGTH = 4.0f; // cm + const float HINGE_LENGTH = 6.0f; // cm + const float SWING_LENGTH = 5.0f; // cm + AnimPoseVec absPoses = /*_limitCenterPoses;*/ _skeleton->getRelativeDefaultPoses(); + _skeleton->convertRelativePosesToAbsolute(absPoses); + + mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(); + for (int i = 0; i < absPoses.size(); i++) { + // transform local axes into world space. + auto pose = absPoses[i]; + glm::vec3 xAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_X); + glm::vec3 yAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Y); + glm::vec3 zAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Z); + glm::vec3 pos = transformPoint(geomToWorldMatrix, pose.trans()); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * xAxis, RED); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * yAxis, GREEN); + DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE); + + // draw line to parent + int parentIndex = _skeleton->getParentIndex(i); + if (parentIndex != -1) { + glm::vec3 parentPos = transformPoint(geomToWorldMatrix, absPoses[parentIndex].trans()); + DebugDraw::getInstance().drawRay(pos, parentPos, GRAY); + } + + glm::quat parentAbsRot; + if (parentIndex != -1) { + parentAbsRot = absPoses[parentIndex].rot(); + } + + const RotationConstraint* constraint = getConstraint(i); + if (constraint) { + glm::quat refRot = constraint->getReferenceRotation(); + const ElbowConstraint* elbowConstraint = dynamic_cast(constraint); + if (elbowConstraint) { + glm::vec3 hingeAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * elbowConstraint->getHingeAxis()); + DebugDraw::getInstance().drawRay(pos, pos + HINGE_LENGTH * hingeAxis, MAGENTA); + + // draw elbow constraints + glm::quat minRot = glm::angleAxis(elbowConstraint->getMinAngle(), elbowConstraint->getHingeAxis()); + glm::quat maxRot = glm::angleAxis(elbowConstraint->getMaxAngle(), elbowConstraint->getHingeAxis()); + + glm::vec3 minYAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * minRot * refRot * Vectors::UNIT_Y); + glm::vec3 maxYAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * maxRot * refRot * Vectors::UNIT_Y); + + const int NUM_SWING_STEPS = 10; + for (int i = 0; i < NUM_SWING_STEPS + 1; i++) { + glm::quat rot = glm::normalize(glm::lerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS))); + glm::vec3 axis = transformVectorFast(geomToWorldMatrix, parentAbsRot * rot * refRot * Vectors::UNIT_Y); + DebugDraw::getInstance().drawRay(pos, pos + TWIST_LENGTH * axis, CYAN); + } + + } else { + const SwingTwistConstraint* swingTwistConstraint = dynamic_cast(constraint); + if (swingTwistConstraint) { + // twist constraints + + glm::vec3 hingeAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * Vectors::UNIT_Y); + DebugDraw::getInstance().drawRay(pos, pos + HINGE_LENGTH * hingeAxis, MAGENTA); + + glm::quat minRot = glm::angleAxis(swingTwistConstraint->getMinTwist(), Vectors::UNIT_Y); + glm::quat maxRot = glm::angleAxis(swingTwistConstraint->getMaxTwist(), Vectors::UNIT_Y); + + glm::vec3 minTwistYAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * minRot * refRot * Vectors::UNIT_X); + glm::vec3 maxTwistYAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * maxRot * refRot * Vectors::UNIT_X); + + const int NUM_SWING_STEPS = 10; + for (int i = 0; i < NUM_SWING_STEPS + 1; i++) { + glm::quat rot = glm::normalize(glm::lerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS))); + glm::vec3 axis = transformVectorFast(geomToWorldMatrix, parentAbsRot * rot * refRot * Vectors::UNIT_X); + DebugDraw::getInstance().drawRay(pos, pos + TWIST_LENGTH * axis, CYAN); + } + + // draw swing constraints. + glm::vec3 previousSwingTip; + const size_t NUM_MIN_DOTS = swingTwistConstraint->getMinDots().size(); + const float D_THETA = TWO_PI / NUM_MIN_DOTS; + float theta = 0.0f; + for (size_t i = 0; i < NUM_MIN_DOTS; i++, theta += D_THETA) { + // compute swing rotation from theta and phi angles. + float phi = acos(swingTwistConstraint->getMinDots()[i]); + float cos_phi = swingTwistConstraint->getMinDots()[i]; + float sin_phi = sinf(phi); + glm::vec3 swungAxis(sin_phi * cosf(theta), cos_phi, sin_phi * sinf(theta)); + glm::vec3 worldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * swungAxis); + + glm::vec3 swingTip = pos + SWING_LENGTH * worldSwungAxis; + DebugDraw::getInstance().drawRay(pos, swingTip, PURPLE); + + if (previousSwingTipValid) { + DebugDraw::getInstance().drawRay(previousSwingTip, swingTip, PURPLE); + } + previousSwingTip = swingTip; + previousSwingTipValid = true; + } + } + } + pose.rot() = constraint->computeCenterRotation(); + } + } + } +} diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index e7427d9ebc..5ad5638709 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -51,11 +51,12 @@ protected: void solveWithCyclicCoordinateDescent(const std::vector& targets); int solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; + void debugDrawConstraints(const AnimContext& context) const; // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } - RotationConstraint* getConstraint(int index); + RotationConstraint* getConstraint(int index) const; void clearConstraints(); void initConstraints(); void initLimitCenterPoses(); diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index e1c8528e0b..470bbab8b6 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -39,7 +39,7 @@ glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const { return *this * rhs; } -// really slow +// really slow, but accurate for transforms with non-uniform scale glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { glm::vec3 xAxis = _rot * glm::vec3(_scale.x, 0.0f, 0.0f); glm::vec3 yAxis = _rot * glm::vec3(0.0f, _scale.y, 0.0f); @@ -49,6 +49,11 @@ glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { return transInvMat * rhs; } +// faster, but does not handle non-uniform scale correctly. +glm::vec3 AnimPose::xformVectorFast(const glm::vec3& rhs) const { + return _rot * (_scale * rhs); +} + AnimPose AnimPose::operator*(const AnimPose& rhs) const { glm::mat4 result; glm_mat4u_mul(*this, rhs, result); diff --git a/libraries/animation/src/AnimPose.h b/libraries/animation/src/AnimPose.h index 893a5c1382..a2e22a24be 100644 --- a/libraries/animation/src/AnimPose.h +++ b/libraries/animation/src/AnimPose.h @@ -25,7 +25,8 @@ public: static const AnimPose identity; glm::vec3 xformPoint(const glm::vec3& rhs) const; - glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow + glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow, but accurate for transforms with non-uniform scale + glm::vec3 xformVectorFast(const glm::vec3& rhs) const; // faster, but does not handle non-uniform scale correctly. glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint AnimPose operator*(const AnimPose& rhs) const; diff --git a/libraries/animation/src/ElbowConstraint.h b/libraries/animation/src/ElbowConstraint.h index 868f5cdc6b..d3f080374a 100644 --- a/libraries/animation/src/ElbowConstraint.h +++ b/libraries/animation/src/ElbowConstraint.h @@ -19,6 +19,11 @@ public: void setAngleLimits(float minAngle, float maxAngle); virtual bool apply(glm::quat& rotation) const override; virtual glm::quat computeCenterRotation() const override; + + glm::vec3 getHingeAxis() const { return _axis; } + float getMinAngle() const { return _minAngle; } + float getMaxAngle() const { return _maxAngle; } + protected: glm::vec3 _axis; glm::vec3 _perpAxis; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b66b0eafa5..2689fe5be8 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -941,7 +941,7 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh } } -void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { +void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform, glm::mat4 rigToWorldTransform) { PROFILE_RANGE_EX(simulation_animation_detail, __FUNCTION__, 0xffff00ff, 0); PerformanceTimer perfTimer("updateAnimations"); @@ -954,7 +954,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); - AnimContext context(_enableDebugDrawIKTargets, getGeometryToRigTransform()); + AnimContext context(_enableDebugDrawIKTargets, getGeometryToRigTransform(), rigToWorldTransform); // evaluate the animation AnimNode::Triggers triggersOut; @@ -1445,7 +1445,7 @@ void Rig::computeAvatarBoundingCapsule( // call overlay twice: once to verify AnimPoseVec joints and again to do the IK AnimNode::Triggers triggersOut; - AnimContext context(false, glm::mat4()); + AnimContext context(false, glm::mat4(), glm::mat4()); float dt = 1.0f; // the value of this does not matter ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e0c5e9f421..f8ae0bdfae 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -162,7 +162,7 @@ public: void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState); // Regardless of who started the animations or how many, update the joints. - void updateAnimations(float deltaTime, glm::mat4 rootTransform); + void updateAnimations(float deltaTime, glm::mat4 rootTransform, glm::mat4 rigToWorldTransform); // legacy void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority, diff --git a/libraries/animation/src/SwingTwistConstraint.cpp b/libraries/animation/src/SwingTwistConstraint.cpp index c1b325a74a..475ee6a59e 100644 --- a/libraries/animation/src/SwingTwistConstraint.cpp +++ b/libraries/animation/src/SwingTwistConstraint.cpp @@ -435,13 +435,13 @@ void SwingTwistConstraint::clearHistory() { glm::quat SwingTwistConstraint::computeCenterRotation() const { const size_t NUM_MIN_DOTS = getMinDots().size(); const size_t NUM_LIMITS = 2 * NUM_MIN_DOTS; - std::vector limits; - limits.reserve(NUM_LIMITS); - glm::quat minTwistRot; - glm::quat maxTwistRot; + std::vector swingLimits; + swingLimits.reserve(NUM_LIMITS); + + glm::quat twistLimits[2]; if (_minTwist != _maxTwist) { - minTwistRot = glm::angleAxis(_minTwist, _referenceRotation * Vectors::UNIT_Y); - minTwistRot = glm::angleAxis(_maxTwist, _referenceRotation * Vectors::UNIT_Y); + twistLimits[0] = glm::angleAxis(_minTwist, _referenceRotation * Vectors::UNIT_Y); + twistLimits[1] = glm::angleAxis(_maxTwist, _referenceRotation * Vectors::UNIT_Y); } const float D_THETA = TWO_PI / NUM_MIN_DOTS; float theta = 0.0f; @@ -451,9 +451,11 @@ glm::quat SwingTwistConstraint::computeCenterRotation() const { float cos_phi = getMinDots()[i]; float sin_phi = sinf(phi); glm::vec3 swungAxis(sin_phi * cosf(theta), cos_phi, sin_phi * sinf(theta)); - glm::quat swing = glm::angleAxis(phi, glm::cross(Vectors::UNIT_Y, swungAxis)); - limits.push_back(swing * minTwistRot); - limits.push_back(swing * maxTwistRot); + glm::quat swing = glm::angleAxis(phi, glm::normalize(glm::cross(Vectors::UNIT_Y, swungAxis))); + swingLimits.push_back(swing); } - return averageQuats(limits.size(), &limits[0]); + glm::quat limits[2]; + limits[0] = averageQuats(swingLimits.size(), &swingLimits[0]); + limits[1] = averageQuats(2, twistLimits); + return averageQuats(2, limits); } diff --git a/libraries/animation/src/SwingTwistConstraint.h b/libraries/animation/src/SwingTwistConstraint.h index 295edb3ebf..ffe9a1d800 100644 --- a/libraries/animation/src/SwingTwistConstraint.h +++ b/libraries/animation/src/SwingTwistConstraint.h @@ -101,6 +101,9 @@ public: virtual glm::quat computeCenterRotation() const override; + const float getMinTwist() const { return _minTwist; } + const float getMaxTwist() const { return _maxTwist; } + private: float handleTwistBoundaryConditions(float twistAngle) const; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index acc84646c5..766a584b85 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1046,7 +1046,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) { //virtual void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { _needsUpdateClusterMatrices = true; - _rig->updateAnimations(deltaTime, parentTransform); + glm::mat4 rigToWorldTransform = createMatFromQuatAndPos(getRotation(), getTranslation()); + _rig->updateAnimations(deltaTime, parentTransform, rigToWorldTransform); } void Model::computeMeshPartLocalBounds() { From 7af93f9fea98ac0538d414adc4a9fa88ff724043 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 4 May 2017 17:16:22 -0700 Subject: [PATCH 026/135] Hooked up IK constraint rendering --- interface/src/Menu.cpp | 2 + interface/src/Menu.h | 1 + interface/src/avatar/MyAvatar.cpp | 5 + interface/src/avatar/MyAvatar.h | 2 + libraries/animation/src/AnimContext.cpp | 4 +- libraries/animation/src/AnimContext.h | 5 +- .../animation/src/AnimInverseKinematics.cpp | 102 ++++++++++-------- libraries/animation/src/Rig.cpp | 5 +- libraries/animation/src/Rig.h | 2 + .../animation/src/SwingTwistConstraint.cpp | 15 +-- 10 files changed, 89 insertions(+), 54 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9688694287..560fe7582d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -523,6 +523,8 @@ Menu::Menu() { avatar.get(), SLOT(setEnableDebugDrawSensorToWorldMatrix(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKTargets, 0, false, avatar.get(), SLOT(setEnableDebugDrawIKTargets(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKConstraints, 0, false, + avatar.get(), SLOT(setEnableDebugDrawIKConstraints(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl, Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu()), diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 250d2241ac..1231e0c72d 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -161,6 +161,7 @@ namespace MenuOption { const QString RenderResolutionQuarter = "1/4"; const QString RenderSensorToWorldMatrix = "Show SensorToWorld Matrix"; const QString RenderIKTargets = "Show IK Targets"; + const QString RenderIKConstraints = "Show IK Constraints"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString ResetSensors = "Reset Sensors"; const QString RunningScripts = "Running Scripts..."; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 526b80e3f1..368b403910 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -505,6 +505,7 @@ void MyAvatar::simulate(float deltaTime) { if (_rig) { _rig->setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets); + _rig->setEnableDebugDrawIKConstraints(_enableDebugDrawIKConstraints); } _skeletonModel->simulate(deltaTime); @@ -930,6 +931,10 @@ void MyAvatar::setEnableDebugDrawIKTargets(bool isEnabled) { _enableDebugDrawIKTargets = isEnabled; } +void MyAvatar::setEnableDebugDrawIKConstraints(bool isEnabled) { + _enableDebugDrawIKConstraints = isEnabled; +} + void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene()); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 7c510f0556..5a7bd7c79c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -518,6 +518,7 @@ public slots: void setEnableDebugDrawHandControllers(bool isEnabled); void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled); void setEnableDebugDrawIKTargets(bool isEnabled); + void setEnableDebugDrawIKConstraints(bool isEnabled); bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } void setEnableMeshVisible(bool isEnabled); void setUseAnimPreAndPostRotations(bool isEnabled); @@ -703,6 +704,7 @@ private: bool _enableDebugDrawHandControllers { false }; bool _enableDebugDrawSensorToWorldMatrix { false }; bool _enableDebugDrawIKTargets { false }; + bool _enableDebugDrawIKConstraints { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; diff --git a/libraries/animation/src/AnimContext.cpp b/libraries/animation/src/AnimContext.cpp index c59c75b191..70ca3764b0 100644 --- a/libraries/animation/src/AnimContext.cpp +++ b/libraries/animation/src/AnimContext.cpp @@ -10,8 +10,10 @@ #include "AnimContext.h" -AnimContext::AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) : +AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, + const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) : _enableDebugDrawIKTargets(enableDebugDrawIKTargets), + _enableDebugDrawIKConstraints(enableDebugDrawIKConstraints), _geometryToRigMatrix(geometryToRigMatrix), _rigToWorldMatrix(rigToWorldMatrix) { diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h index 067e64026a..f68535005c 100644 --- a/libraries/animation/src/AnimContext.h +++ b/libraries/animation/src/AnimContext.h @@ -16,15 +16,18 @@ class AnimContext { public: - AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix); + AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, + const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix); bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; } + bool getEnableDebugDrawIKConstraints() const { return _enableDebugDrawIKConstraints; } const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; } const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; } protected: bool _enableDebugDrawIKTargets { false }; + bool _enableDebugDrawIKConstraints{ false }; glm::mat4 _geometryToRigMatrix; glm::mat4 _rigToWorldMatrix; }; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index ae2ca94d66..5dcbfcafad 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -399,7 +399,9 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { - debugDrawConstraints(context); + if (context.getEnableDebugDrawIKConstraints()) { + debugDrawConstraints(context); + } const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay if (dt > MAX_OVERLAY_DT) { @@ -624,17 +626,19 @@ void AnimInverseKinematics::clearConstraints() { _constraints.clear(); } -// set up swing limits around a swingTwistConstraint in an ellipse, where lateralSwingTheta is the swing limit for lateral swings (side to side) -// anteriorSwingTheta is swing limit for forward and backward swings. (where x-axis of reference rotation is sideways and -z-axis is forward) -static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float lateralSwingTheta, float anteriorSwingTheta) { +// set up swing limits around a swingTwistConstraint in an ellipse, where lateralSwingPhi is the swing limit for lateral swings (side to side) +// anteriorSwingPhi is swing limit for forward and backward swings. (where x-axis of reference rotation is sideways and -z-axis is forward) +static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float lateralSwingPhi, float anteriorSwingPhi) { assert(stConstraint); - const int NUM_SUBDIVISIONS = 8; + const int NUM_SUBDIVISIONS = 16; std::vector minDots; minDots.reserve(NUM_SUBDIVISIONS); float dTheta = TWO_PI / NUM_SUBDIVISIONS; float theta = 0.0f; for (int i = 0; i < NUM_SUBDIVISIONS; i++) { - minDots.push_back(cosf(glm::length(glm::vec2(anteriorSwingTheta * cosf(theta), lateralSwingTheta * sinf(theta))))); + float theta_prime = atanf((lateralSwingPhi / anteriorSwingPhi) * tanf(theta)); + float phi = (cosf(2.0f * theta_prime) * ((lateralSwingPhi - anteriorSwingPhi) / 2.0f)) + ((lateralSwingPhi + anteriorSwingPhi) / 2.0f); + minDots.push_back(cosf(phi)); theta += dTheta; } stConstraint->setSwingLimits(minDots); @@ -642,7 +646,6 @@ static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float l void AnimInverseKinematics::initConstraints() { if (!_skeleton) { - return; } // We create constraints for the joints shown here // (and their Left counterparts if applicable). @@ -746,30 +749,27 @@ void AnimInverseKinematics::initConstraints() { std::vector swungDirections; float deltaTheta = PI / 4.0f; float theta = 0.0f; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); + swungDirections.push_back(glm::vec3(cosf(theta), -0.25f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); + swungDirections.push_back(glm::vec3(cosf(theta), 0.0f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); // posterior + swungDirections.push_back(glm::vec3(cosf(theta), 0.25f, sinf(theta))); // posterior theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); + swungDirections.push_back(glm::vec3(cosf(theta), 0.0f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); + swungDirections.push_back(glm::vec3(cosf(theta), -0.25f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); + swungDirections.push_back(glm::vec3(cosf(theta), -0.5f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); // anterior + swungDirections.push_back(glm::vec3(cosf(theta), -0.5f, sinf(theta))); // anterior theta += deltaTheta; - swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); + swungDirections.push_back(glm::vec3(cosf(theta), -0.5f, sinf(theta))); - // rotate directions into joint-frame - glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot()); - int numDirections = (int)swungDirections.size(); - for (int j = 0; j < numDirections; ++j) { - swungDirections[j] = invAbsoluteRotation * swungDirections[j]; + std::vector minDots; + for (int i = 0; i < swungDirections.size(); i++) { + minDots.push_back(glm::dot(glm::normalize(swungDirections[i]), Vectors::UNIT_Y)); } - stConstraint->setSwingLimits(swungDirections); - + stConstraint->setSwingLimits(minDots); constraint = static_cast(stConstraint); } else if (0 == baseName.compare("Hand", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); @@ -1006,8 +1006,13 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele } } -void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) const { +static glm::vec3 sphericalToCartesian(float phi, float theta) { + float cos_phi = cosf(phi); + float sin_phi = sinf(phi); + return glm::vec3(sin_phi * cosf(theta), cos_phi, -sin_phi * sinf(theta)); +} +void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) const { if (_skeleton) { const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f); @@ -1020,13 +1025,26 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con const float TWIST_LENGTH = 4.0f; // cm const float HINGE_LENGTH = 6.0f; // cm const float SWING_LENGTH = 5.0f; // cm - AnimPoseVec absPoses = /*_limitCenterPoses;*/ _skeleton->getRelativeDefaultPoses(); - _skeleton->convertRelativePosesToAbsolute(absPoses); + + AnimPoseVec poses = _skeleton->getRelativeDefaultPoses(); + + // copy reference rotations into the relative poses + for (int i = 0; i < poses.size(); i++) { + const RotationConstraint* constraint = getConstraint(i); + if (constraint) { + poses[i].rot() = constraint->getReferenceRotation(); + } + } + + // convert relative poses to absolute + _skeleton->convertRelativePosesToAbsolute(poses); mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(); - for (int i = 0; i < absPoses.size(); i++) { + + // draw each pose and constraint + for (int i = 0; i < poses.size(); i++) { // transform local axes into world space. - auto pose = absPoses[i]; + auto pose = poses[i]; glm::vec3 xAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_X); glm::vec3 yAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Y); glm::vec3 zAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Z); @@ -1038,13 +1056,13 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con // draw line to parent int parentIndex = _skeleton->getParentIndex(i); if (parentIndex != -1) { - glm::vec3 parentPos = transformPoint(geomToWorldMatrix, absPoses[parentIndex].trans()); + glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans()); DebugDraw::getInstance().drawRay(pos, parentPos, GRAY); } glm::quat parentAbsRot; if (parentIndex != -1) { - parentAbsRot = absPoses[parentIndex].rot(); + parentAbsRot = poses[parentIndex].rot(); } const RotationConstraint* constraint = getConstraint(i); @@ -1091,26 +1109,24 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con } // draw swing constraints. - glm::vec3 previousSwingTip; const size_t NUM_MIN_DOTS = swingTwistConstraint->getMinDots().size(); - const float D_THETA = TWO_PI / NUM_MIN_DOTS; + const float D_THETA = TWO_PI / (NUM_MIN_DOTS - 1); float theta = 0.0f; - for (size_t i = 0; i < NUM_MIN_DOTS; i++, theta += D_THETA) { + for (size_t i = 0, j = NUM_MIN_DOTS - 2; i < NUM_MIN_DOTS - 1; j = i, i++, theta += D_THETA) { // compute swing rotation from theta and phi angles. - float phi = acos(swingTwistConstraint->getMinDots()[i]); - float cos_phi = swingTwistConstraint->getMinDots()[i]; - float sin_phi = sinf(phi); - glm::vec3 swungAxis(sin_phi * cosf(theta), cos_phi, sin_phi * sinf(theta)); + float phi = acosf(swingTwistConstraint->getMinDots()[i]); + glm::vec3 swungAxis = sphericalToCartesian(phi, theta); glm::vec3 worldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * swungAxis); - glm::vec3 swingTip = pos + SWING_LENGTH * worldSwungAxis; - DebugDraw::getInstance().drawRay(pos, swingTip, PURPLE); - if (previousSwingTipValid) { - DebugDraw::getInstance().drawRay(previousSwingTip, swingTip, PURPLE); - } - previousSwingTip = swingTip; - previousSwingTipValid = true; + float prevPhi = acos(swingTwistConstraint->getMinDots()[j]); + float prevTheta = theta - D_THETA; + glm::vec3 prevSwungAxis = sphericalToCartesian(prevPhi, prevTheta); + glm::vec3 prevWorldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * prevSwungAxis); + glm::vec3 prevSwingTip = pos + SWING_LENGTH * prevWorldSwungAxis; + + DebugDraw::getInstance().drawRay(pos, swingTip, PURPLE); + DebugDraw::getInstance().drawRay(prevSwingTip, swingTip, PURPLE); } } } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 2689fe5be8..2bcd71d5c3 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -954,7 +954,8 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform, glm::mat4 r updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); - AnimContext context(_enableDebugDrawIKTargets, getGeometryToRigTransform(), rigToWorldTransform); + AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, + getGeometryToRigTransform(), rigToWorldTransform); // evaluate the animation AnimNode::Triggers triggersOut; @@ -1445,7 +1446,7 @@ void Rig::computeAvatarBoundingCapsule( // call overlay twice: once to verify AnimPoseVec joints and again to do the IK AnimNode::Triggers triggersOut; - AnimContext context(false, glm::mat4(), glm::mat4()); + AnimContext context(false, false, glm::mat4(), glm::mat4()); float dt = 1.0f; // the value of this does not matter ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index f8ae0bdfae..396e68d633 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -231,6 +231,7 @@ public: const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; } void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; } + void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; } // input assumed to be in rig space void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const; @@ -341,6 +342,7 @@ protected: float _maxHipsOffsetLength { 1.0f }; bool _enableDebugDrawIKTargets { false }; + bool _enableDebugDrawIKConstraints { false }; private: QMap _stateHandlers; diff --git a/libraries/animation/src/SwingTwistConstraint.cpp b/libraries/animation/src/SwingTwistConstraint.cpp index 475ee6a59e..aba0516b2a 100644 --- a/libraries/animation/src/SwingTwistConstraint.cpp +++ b/libraries/animation/src/SwingTwistConstraint.cpp @@ -443,19 +443,20 @@ glm::quat SwingTwistConstraint::computeCenterRotation() const { twistLimits[0] = glm::angleAxis(_minTwist, _referenceRotation * Vectors::UNIT_Y); twistLimits[1] = glm::angleAxis(_maxTwist, _referenceRotation * Vectors::UNIT_Y); } - const float D_THETA = TWO_PI / NUM_MIN_DOTS; + const float D_THETA = TWO_PI / (NUM_MIN_DOTS - 1); float theta = 0.0f; - for (size_t i = 0; i < NUM_MIN_DOTS; i++, theta += D_THETA) { + for (size_t i = 0; i < NUM_MIN_DOTS - 1; i++, theta += D_THETA) { // compute swing rotation from theta and phi angles. float phi = acos(getMinDots()[i]); float cos_phi = getMinDots()[i]; float sin_phi = sinf(phi); glm::vec3 swungAxis(sin_phi * cosf(theta), cos_phi, sin_phi * sinf(theta)); - glm::quat swing = glm::angleAxis(phi, glm::normalize(glm::cross(Vectors::UNIT_Y, swungAxis))); + + // to ensure that swings > 90 degrees do not flip the center rotation, we devide phi / 2 + glm::quat swing = glm::angleAxis(phi / 2, glm::normalize(glm::cross(Vectors::UNIT_Y, swungAxis))); swingLimits.push_back(swing); } - glm::quat limits[2]; - limits[0] = averageQuats(swingLimits.size(), &swingLimits[0]); - limits[1] = averageQuats(2, twistLimits); - return averageQuats(2, limits); + glm::quat averageSwing = averageQuats(swingLimits.size(), &swingLimits[0]); + glm::quat averageTwist = averageQuats(2, twistLimits); + return averageSwing * averageTwist * _referenceRotation; } From 712fcbe27a9dce1fa59dc4a37a766575dfa7179f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 4 May 2017 18:41:42 -0700 Subject: [PATCH 027/135] Removed cruft/debug code & comments --- interface/src/avatar/MyAvatar.cpp | 24 ------------------- .../animation/src/AnimInverseKinematics.h | 3 --- 2 files changed, 27 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 368b403910..7870bd4968 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1707,30 +1707,6 @@ void MyAvatar::postUpdate(float deltaTime) { initAnimGraph(); } - // AJT: REMOVE. - /* - { - auto ikNode = _rig->getAnimInverseKinematicsNode(); - if (ikNode) { - // the rig is in the skeletonModel frame - AnimPose xform(glm::vec3(1), _skeletonModel->getRotation(), _skeletonModel->getTranslation()); - AnimPoseVec limitCenterPoses = ikNode->getLimitCenterPoses(); - - // HACK: convert joints from geom to avatar space - int hipsIndex = _rig->indexOfJoint("Hips"); - for (size_t i = 0; i < limitCenterPoses.size(); i++) { - if (i == hipsIndex) { - limitCenterPoses[i].trans() = glm::vec3(); // zero the hips - } - // convert from cm to m - limitCenterPoses[i].trans() = 0.01f * limitCenterPoses[i].trans(); - } - _rig->getAnimSkeleton()->convertRelativePosesToAbsolute(limitCenterPoses); - AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarLimitCenterPoses", _rig->getAnimSkeleton(), limitCenterPoses, xform, glm::vec4(1)); - } - } - */ - if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { auto animSkeleton = _rig->getAnimSkeleton(); diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 5ad5638709..b40f025ecd 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -43,9 +43,6 @@ public: float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; } - // AJT: TODO REMOVE? for debugging. - const AnimPoseVec& getLimitCenterPoses() const { return _limitCenterPoses; } - protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solveWithCyclicCoordinateDescent(const std::vector& targets); From 2166d8c159e15e20a9ff03e62486b426d511268d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 8 May 2017 14:10:16 -0700 Subject: [PATCH 028/135] Added setSolutionSource to AnimInverseKinematics node. --- .../animation/src/AnimInverseKinematics.cpp | 58 +++++++++++++------ .../animation/src/AnimInverseKinematics.h | 13 +++++ 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 5dcbfcafad..bf05d9358c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -414,25 +414,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars PROFILE_RANGE_EX(simulation_animation, "ik/relax", 0xffff00ff, 0); - // relax toward underPoses - // HACK: this relaxation needs to be constant per-frame rather than per-realtime - // in order to prevent IK "flutter" for bad FPS. The bad news is that the good parts - // of this relaxation will be FPS dependent (low FPS will make the limbs align slower - // in real-time), however most people will not notice this and this problem is less - // annoying than the flutter. - const float blend = (1.0f / 60.0f) / (0.25f); // effectively: dt / RELAXATION_TIMESCALE - int numJoints = (int)_relativePoses.size(); - for (int i = 0; i < numJoints; ++i) { - float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), underPoses[i].rot())); - if (_accumulators[i].isDirty()) { - // this joint is affected by IK --> blend toward underPose rotation - _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * underPoses[i].rot(), blend)); - } else { - // this joint is NOT affected by IK --> slam to underPose rotation - _relativePoses[i].rot() = underPoses[i].rot(); - } - _relativePoses[i].trans() = underPoses[i].trans(); - } + initRelativePosesFromSolutionSource(underPoses); if (!underPoses.empty()) { // Sometimes the underpose itself can violate the constraints. Rather than @@ -1135,3 +1117,41 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con } } } + +void AnimInverseKinematics::relaxToPoses(const AnimPoseVec& poses) { + // relax toward poses + const float blend = (1.0f / 60.0f) / (0.25f); + int numJoints = (int)_relativePoses.size(); + for (int i = 0; i < numJoints; ++i) { + float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), poses[i].rot())); + if (_accumulators[i].isDirty()) { + // this joint is affected by IK --> blend toward each pose rotation + _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * poses[i].rot(), blend)); + } else { + // this joint is NOT affected by IK --> slam to underPose rotation + _relativePoses[i].rot() = poses[i].rot(); + } + _relativePoses[i].trans() = poses[i].trans(); + } +} + +void AnimInverseKinematics::initRelativePosesFromSolutionSource(const AnimPoseVec& underPoses) { + switch (_solutionSource) { + default: + case SolutionSource::RelaxToUnderPoses: + relaxToPoses(underPoses); + break; + case SolutionSource::RelaxToLimitCenterPoses: + relaxToPoses(_limitCenterPoses); + break; + case SolutionSource::PreviousSolution: + // do nothing... _relativePoses is already the previous solution + break; + case SolutionSource::UnderPoses: + _relativePoses = underPoses; + break; + case SolutionSource::LimitCenterPoses: + _relativePoses = _limitCenterPoses; + break; + } +} diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index b40f025ecd..9b7c095e6b 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -43,12 +43,24 @@ public: float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; } + enum class SolutionSource { + RelaxToUnderPoses = 0, + RelaxToLimitCenterPoses, + PreviousSolution, + UnderPoses, + LimitCenterPoses + }; + + void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; } + protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solveWithCyclicCoordinateDescent(const std::vector& targets); int solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; void debugDrawConstraints(const AnimContext& context) const; + void initRelativePosesFromSolutionSource(const AnimPoseVec& underPose); + void relaxToPoses(const AnimPoseVec& poses); // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } @@ -103,6 +115,7 @@ protected: float _maxErrorOnLastSolve { FLT_MAX }; bool _previousEnableDebugIKTargets { false }; + SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses }; }; #endif // hifi_AnimInverseKinematics_h From 84aa86b4645ed5437c970af80f6089e49459e7c1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 8 May 2017 18:07:45 -0700 Subject: [PATCH 029/135] Added animVar support for IK solutionSource. --- .../resources/avatar/avatar-animation.json | 2 + .../animation/src/AnimInverseKinematics.cpp | 9 +++-- .../animation/src/AnimInverseKinematics.h | 7 +++- libraries/animation/src/AnimNodeLoader.cpp | 37 +++++++++++++++++++ libraries/animation/src/Rig.cpp | 2 + 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 9efe3dd29b..eb8403634a 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -49,6 +49,8 @@ "id": "ik", "type": "inverseKinematics", "data": { + "solutionSource": "relaxToUnderPoses", + "solutionSourceVar": "solutionSource", "targets": [ { "jointName": "Hips", diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index bf05d9358c..513d770e64 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -399,6 +399,9 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar //virtual const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { + // allows solutionSource to be overridden by an animVar + auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource); + if (context.getEnableDebugDrawIKConstraints()) { debugDrawConstraints(context); } @@ -414,7 +417,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars PROFILE_RANGE_EX(simulation_animation, "ik/relax", 0xffff00ff, 0); - initRelativePosesFromSolutionSource(underPoses); + initRelativePosesFromSolutionSource((SolutionSource)solutionSource, underPoses); if (!underPoses.empty()) { // Sometimes the underpose itself can violate the constraints. Rather than @@ -1135,8 +1138,8 @@ void AnimInverseKinematics::relaxToPoses(const AnimPoseVec& poses) { } } -void AnimInverseKinematics::initRelativePosesFromSolutionSource(const AnimPoseVec& underPoses) { - switch (_solutionSource) { +void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPoses) { + switch (solutionSource) { default: case SolutionSource::RelaxToUnderPoses: relaxToPoses(underPoses); diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 9b7c095e6b..a78662cbeb 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -48,10 +48,12 @@ public: RelaxToLimitCenterPoses, PreviousSolution, UnderPoses, - LimitCenterPoses + LimitCenterPoses, + NumSolutionSources, }; void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; } + void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; } protected: void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); @@ -59,7 +61,7 @@ protected: int solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; void debugDrawConstraints(const AnimContext& context) const; - void initRelativePosesFromSolutionSource(const AnimPoseVec& underPose); + void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); void relaxToPoses(const AnimPoseVec& poses); // for AnimDebugDraw rendering @@ -116,6 +118,7 @@ protected: float _maxErrorOnLastSolve { FLT_MAX }; bool _previousEnableDebugIKTargets { false }; SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses }; + QString _solutionSourceVar; }; #endif // hifi_AnimInverseKinematics_h diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index bda4541f36..592667bb72 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -352,6 +352,23 @@ static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { return AnimOverlay::NumBoneSets; } +static const char* solutionSourceStrings[AnimInverseKinematics::SolutionSource::NumSolutionSources] = { + "relaxToUnderPoses", + "relaxToLimitCenterPoses", + "previousSolution", + "underPoses", + "limitCenterPoses" +}; + +static AnimInverseKinematics::SolutionSource stringToSolutionSourceEnum(const QString& str) { + for (int i = 0; i < (int)AnimInverseKinematics::SolutionSource::NumSolutionSources; i++) { + if (str == solutionSourceStrings[i]) { + return (AnimInverseKinematics::SolutionSource)i; + } + } + return AnimInverseKinematics::SolutionSource::NumSolutionSources; +} + static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { READ_STRING(boneSet, jsonObj, id, jsonUrl, nullptr); @@ -457,6 +474,26 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS node->setTargetVars(jointName, positionVar, rotationVar, typeVar); }; + READ_OPTIONAL_STRING(solutionSource, jsonObj); + + if (!solutionSource.isEmpty()) { + qCDebug(animation) << "AJT: REMOVE solutionSource = " << solutionSource; + + AnimInverseKinematics::SolutionSource solutionSourceType = stringToSolutionSourceEnum(solutionSource); + if (solutionSourceType != AnimInverseKinematics::SolutionSource::NumSolutionSources) { + node->setSolutionSource(solutionSourceType); + } else { + qCWarning(animation) << "AnimNodeLoader, bad solutionSourceType in \"solutionSource\", id = " << id << ", url = " << jsonUrl.toDisplayString(); + } + } + + READ_OPTIONAL_STRING(solutionSourceVar, jsonObj); + + if (!solutionSourceVar.isEmpty()) { + qCDebug(animation) << "AJT: REMOVE solutionSourceVar = " << solutionSourceVar; + node->setSolutionSourceVar(solutionSourceVar); + } + return node; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 2bcd71d5c3..c1ef443684 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1031,10 +1031,12 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { _animVars.set("notIsTalking", !params.isTalking); if (params.hipsEnabled) { + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToCenterJointLimits); _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("hipsPosition", extractTranslation(params.hipsMatrix)); _animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix)); } else { + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses); _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } From 5a4b21c0a9833911c3786e5b3d4195a669615323 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 8 May 2017 18:10:56 -0700 Subject: [PATCH 030/135] Removed debug code --- libraries/animation/src/AnimNodeLoader.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 592667bb72..6e2c070ae5 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -477,8 +477,6 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS READ_OPTIONAL_STRING(solutionSource, jsonObj); if (!solutionSource.isEmpty()) { - qCDebug(animation) << "AJT: REMOVE solutionSource = " << solutionSource; - AnimInverseKinematics::SolutionSource solutionSourceType = stringToSolutionSourceEnum(solutionSource); if (solutionSourceType != AnimInverseKinematics::SolutionSource::NumSolutionSources) { node->setSolutionSource(solutionSourceType); @@ -490,7 +488,6 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS READ_OPTIONAL_STRING(solutionSourceVar, jsonObj); if (!solutionSourceVar.isEmpty()) { - qCDebug(animation) << "AJT: REMOVE solutionSourceVar = " << solutionSourceVar; node->setSolutionSourceVar(solutionSourceVar); } From fe69f58174243438224fff7b88d779c1ae051966 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 9 May 2017 09:47:26 -0700 Subject: [PATCH 031/135] Bug fix centerLimit rot for LeftArm, also, lower arms in centerLimit pose Lowering the arms in centerLimit poses will help keep the elbows relaxed on the side of the body. --- libraries/animation/src/AnimInverseKinematics.cpp | 13 +++++++++++++ libraries/animation/src/Rig.cpp | 2 +- libraries/animation/src/SwingTwistConstraint.cpp | 13 +++++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 513d770e64..9c3aaddcfa 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -955,6 +955,19 @@ void AnimInverseKinematics::initLimitCenterPoses() { } _limitCenterPoses.push_back(pose); } + + // The limit center rotations for the LeftArm and RightArm form a t-pose. + // In order for the elbows to look more natural, we rotate them down by the avatar's sides + const float UPPER_ARM_THETA = 3.0f * PI / 8.0f; // 67.5 deg + int leftArmIndex = _skeleton->nameToJointIndex("LeftArm"); + const glm::quat armRot = glm::angleAxis(UPPER_ARM_THETA, Vectors::UNIT_X); + if (leftArmIndex >= 0 && leftArmIndex < _limitCenterPoses.size()) { + _limitCenterPoses[leftArmIndex].rot() = _limitCenterPoses[leftArmIndex].rot() * armRot; + } + int rightArmIndex = _skeleton->nameToJointIndex("RightArm"); + if (rightArmIndex >= 0 && rightArmIndex < _limitCenterPoses.size()) { + _limitCenterPoses[rightArmIndex].rot() = _limitCenterPoses[rightArmIndex].rot() * armRot; + } } void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index c1ef443684..f933002c2c 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1031,7 +1031,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { _animVars.set("notIsTalking", !params.isTalking); if (params.hipsEnabled) { - _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToCenterJointLimits); + _animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses); _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("hipsPosition", extractTranslation(params.hipsMatrix)); _animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix)); diff --git a/libraries/animation/src/SwingTwistConstraint.cpp b/libraries/animation/src/SwingTwistConstraint.cpp index aba0516b2a..212343d4eb 100644 --- a/libraries/animation/src/SwingTwistConstraint.cpp +++ b/libraries/animation/src/SwingTwistConstraint.cpp @@ -433,15 +433,16 @@ void SwingTwistConstraint::clearHistory() { } glm::quat SwingTwistConstraint::computeCenterRotation() const { + const size_t NUM_TWIST_LIMITS = 2; const size_t NUM_MIN_DOTS = getMinDots().size(); - const size_t NUM_LIMITS = 2 * NUM_MIN_DOTS; std::vector swingLimits; - swingLimits.reserve(NUM_LIMITS); + swingLimits.reserve(NUM_MIN_DOTS); - glm::quat twistLimits[2]; + glm::quat twistLimits[NUM_TWIST_LIMITS]; if (_minTwist != _maxTwist) { - twistLimits[0] = glm::angleAxis(_minTwist, _referenceRotation * Vectors::UNIT_Y); - twistLimits[1] = glm::angleAxis(_maxTwist, _referenceRotation * Vectors::UNIT_Y); + // to ensure that twists do not flip the center rotation, we devide twist angle by 2. + twistLimits[0] = glm::angleAxis(_minTwist / 2.0f, _referenceRotation * Vectors::UNIT_Y); + twistLimits[1] = glm::angleAxis(_maxTwist / 2.0f, _referenceRotation * Vectors::UNIT_Y); } const float D_THETA = TWO_PI / (NUM_MIN_DOTS - 1); float theta = 0.0f; @@ -450,7 +451,7 @@ glm::quat SwingTwistConstraint::computeCenterRotation() const { float phi = acos(getMinDots()[i]); float cos_phi = getMinDots()[i]; float sin_phi = sinf(phi); - glm::vec3 swungAxis(sin_phi * cosf(theta), cos_phi, sin_phi * sinf(theta)); + glm::vec3 swungAxis(sin_phi * cosf(theta), cos_phi, -sin_phi * sinf(theta)); // to ensure that swings > 90 degrees do not flip the center rotation, we devide phi / 2 glm::quat swing = glm::angleAxis(phi / 2, glm::normalize(glm::cross(Vectors::UNIT_Y, swungAxis))); From 7faed38bb52cde987cddf19845c1a79d78a09314 Mon Sep 17 00:00:00 2001 From: trent Date: Tue, 9 May 2017 12:51:25 -0400 Subject: [PATCH 032/135] Just use the smooth time constant instead of a variable. --- 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 afe7fd017a..fee27729da 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -632,7 +632,6 @@ private: const float SMOOTH_TIME_ORIENTATION = 0.5f; // Smoothing data for blending from one position/orientation to another on remote agents. - float _smoothOrientationTime; float _smoothOrientationTimer; glm::quat _smoothOrientationInitial; glm::quat _smoothOrientationTarget; From 7b35e8c7fd6141b1e3797387e25ed48adee1d9bf Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 9 May 2017 11:57:41 -0700 Subject: [PATCH 033/135] Bug fix for hands, reduced elbow angle to 60 degrees from horizontal. --- .../animation/src/AnimInverseKinematics.cpp | 29 +++++++++++-------- .../animation/src/AnimInverseKinematics.h | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 9c3aaddcfa..11c14d8899 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -958,7 +958,7 @@ void AnimInverseKinematics::initLimitCenterPoses() { // The limit center rotations for the LeftArm and RightArm form a t-pose. // In order for the elbows to look more natural, we rotate them down by the avatar's sides - const float UPPER_ARM_THETA = 3.0f * PI / 8.0f; // 67.5 deg + const float UPPER_ARM_THETA = PI / 3.0f; // 60 deg int leftArmIndex = _skeleton->nameToJointIndex("LeftArm"); const glm::quat armRot = glm::angleAxis(UPPER_ARM_THETA, Vectors::UNIT_X); if (leftArmIndex >= 0 && leftArmIndex < _limitCenterPoses.size()) { @@ -1134,31 +1134,35 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con } } -void AnimInverseKinematics::relaxToPoses(const AnimPoseVec& poses) { +// for bones under IK, blend between previous solution (_relativePoses) to targetPoses +// for bones NOT under IK, copy directly from underPoses. +// mutates _relativePoses. +void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPoses, float blendFactor) { // relax toward poses - const float blend = (1.0f / 60.0f) / (0.25f); int numJoints = (int)_relativePoses.size(); for (int i = 0; i < numJoints; ++i) { - float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), poses[i].rot())); + float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot())); if (_accumulators[i].isDirty()) { - // this joint is affected by IK --> blend toward each pose rotation - _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * poses[i].rot(), blend)); + // this joint is affected by IK --> blend toward the targetPoses rotation + _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor)); } else { - // this joint is NOT affected by IK --> slam to underPose rotation - _relativePoses[i].rot() = poses[i].rot(); + // this joint is NOT affected by IK --> slam to underPoses rotation + _relativePoses[i].rot() = underPoses[i].rot(); } - _relativePoses[i].trans() = poses[i].trans(); + _relativePoses[i].trans() = underPoses[i].trans(); } } void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPoses) { + const float RELAX_BLEND_FACTOR = (1.0f / 16.0f); + const float COPY_BLEND_FACTOR = 1.0f; switch (solutionSource) { default: case SolutionSource::RelaxToUnderPoses: - relaxToPoses(underPoses); + blendToPoses(underPoses, underPoses, RELAX_BLEND_FACTOR); break; case SolutionSource::RelaxToLimitCenterPoses: - relaxToPoses(_limitCenterPoses); + blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR); break; case SolutionSource::PreviousSolution: // do nothing... _relativePoses is already the previous solution @@ -1167,7 +1171,8 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s _relativePoses = underPoses; break; case SolutionSource::LimitCenterPoses: - _relativePoses = _limitCenterPoses; + // essentially copy limitCenterPoses over to _relativePoses. + blendToPoses(_limitCenterPoses, underPoses, COPY_BLEND_FACTOR); break; } } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index a78662cbeb..f73ed95935 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -62,7 +62,7 @@ protected: virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override; void debugDrawConstraints(const AnimContext& context) const; void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose); - void relaxToPoses(const AnimPoseVec& poses); + void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor); // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; } From 0bcc3c023efdf8fc57bf0846d998f1e4369a61b2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 9 May 2017 13:07:06 -0700 Subject: [PATCH 034/135] warning fixes --- libraries/animation/src/AnimInverseKinematics.cpp | 10 +++++----- libraries/animation/src/Rig.h | 2 +- libraries/animation/src/SwingTwistConstraint.h | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 11c14d8899..6ece8cdc3d 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -751,7 +751,7 @@ void AnimInverseKinematics::initConstraints() { swungDirections.push_back(glm::vec3(cosf(theta), -0.5f, sinf(theta))); std::vector minDots; - for (int i = 0; i < swungDirections.size(); i++) { + for (size_t i = 0; i < swungDirections.size(); i++) { minDots.push_back(glm::dot(glm::normalize(swungDirections[i]), Vectors::UNIT_Y)); } stConstraint->setSwingLimits(minDots); @@ -961,11 +961,11 @@ void AnimInverseKinematics::initLimitCenterPoses() { const float UPPER_ARM_THETA = PI / 3.0f; // 60 deg int leftArmIndex = _skeleton->nameToJointIndex("LeftArm"); const glm::quat armRot = glm::angleAxis(UPPER_ARM_THETA, Vectors::UNIT_X); - if (leftArmIndex >= 0 && leftArmIndex < _limitCenterPoses.size()) { + if (leftArmIndex >= 0 && leftArmIndex < (int)_limitCenterPoses.size()) { _limitCenterPoses[leftArmIndex].rot() = _limitCenterPoses[leftArmIndex].rot() * armRot; } int rightArmIndex = _skeleton->nameToJointIndex("RightArm"); - if (rightArmIndex >= 0 && rightArmIndex < _limitCenterPoses.size()) { + if (rightArmIndex >= 0 && rightArmIndex < (int)_limitCenterPoses.size()) { _limitCenterPoses[rightArmIndex].rot() = _limitCenterPoses[rightArmIndex].rot() * armRot; } } @@ -1027,7 +1027,7 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con AnimPoseVec poses = _skeleton->getRelativeDefaultPoses(); // copy reference rotations into the relative poses - for (int i = 0; i < poses.size(); i++) { + for (int i = 0; i < (int)poses.size(); i++) { const RotationConstraint* constraint = getConstraint(i); if (constraint) { poses[i].rot() = constraint->getReferenceRotation(); @@ -1040,7 +1040,7 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix(); // draw each pose and constraint - for (int i = 0; i < poses.size(); i++) { + for (int i = 0; i < (int)poses.size(); i++) { // transform local axes into world space. auto pose = poses[i]; glm::vec3 xAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_X); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 396e68d633..bee6518557 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -112,7 +112,7 @@ public: void clearJointStates(); void clearJointAnimationPriority(int index); - std::shared_ptr Rig::getAnimInverseKinematicsNode() const; + std::shared_ptr getAnimInverseKinematicsNode() const; void clearIKJointLimitHistory(); void setMaxHipsOffsetLength(float maxLength); diff --git a/libraries/animation/src/SwingTwistConstraint.h b/libraries/animation/src/SwingTwistConstraint.h index ffe9a1d800..a41664d353 100644 --- a/libraries/animation/src/SwingTwistConstraint.h +++ b/libraries/animation/src/SwingTwistConstraint.h @@ -101,8 +101,8 @@ public: virtual glm::quat computeCenterRotation() const override; - const float getMinTwist() const { return _minTwist; } - const float getMaxTwist() const { return _maxTwist; } + float getMinTwist() const { return _minTwist; } + float getMaxTwist() const { return _maxTwist; } private: float handleTwistBoundaryConditions(float twistAngle) const; From d7f195bc42cd48fae022797240c936114cdebd07 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 9 May 2017 13:17:06 -0700 Subject: [PATCH 035/135] warning fix --- libraries/animation/src/AnimNodeLoader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 6e2c070ae5..6bc7342d7f 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -352,7 +352,7 @@ static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { return AnimOverlay::NumBoneSets; } -static const char* solutionSourceStrings[AnimInverseKinematics::SolutionSource::NumSolutionSources] = { +static const char* solutionSourceStrings[(int)AnimInverseKinematics::SolutionSource::NumSolutionSources] = { "relaxToUnderPoses", "relaxToLimitCenterPoses", "previousSolution", From e63dc52ec9a2574926b573866846a70d807447a1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 9 May 2017 13:59:07 -0700 Subject: [PATCH 036/135] moar warning fixes --- libraries/animation/src/AnimInverseKinematics.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 6ece8cdc3d..92c74b1793 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -1075,9 +1075,6 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con glm::quat minRot = glm::angleAxis(elbowConstraint->getMinAngle(), elbowConstraint->getHingeAxis()); glm::quat maxRot = glm::angleAxis(elbowConstraint->getMaxAngle(), elbowConstraint->getHingeAxis()); - glm::vec3 minYAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * minRot * refRot * Vectors::UNIT_Y); - glm::vec3 maxYAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * maxRot * refRot * Vectors::UNIT_Y); - const int NUM_SWING_STEPS = 10; for (int i = 0; i < NUM_SWING_STEPS + 1; i++) { glm::quat rot = glm::normalize(glm::lerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS))); @@ -1096,9 +1093,6 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con glm::quat minRot = glm::angleAxis(swingTwistConstraint->getMinTwist(), Vectors::UNIT_Y); glm::quat maxRot = glm::angleAxis(swingTwistConstraint->getMaxTwist(), Vectors::UNIT_Y); - glm::vec3 minTwistYAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * minRot * refRot * Vectors::UNIT_X); - glm::vec3 maxTwistYAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * maxRot * refRot * Vectors::UNIT_X); - const int NUM_SWING_STEPS = 10; for (int i = 0; i < NUM_SWING_STEPS + 1; i++) { glm::quat rot = glm::normalize(glm::lerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS))); From a7cc58245993bfdc3c3cbd6c0b3e54a610fb3711 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 10 May 2017 10:32:43 +1200 Subject: [PATCH 037/135] Don't multiply heartbeats --- scripts/system/playRecordingAC.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index b4fae9a2e3..6c7a5eb8b1 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -368,13 +368,16 @@ }; }()); - function sendHeartbeat() { + function sendHeartbeat(isOneShot) { Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({ playing: Player.isPlaying(), recording: Player.recording(), entity: Entity.id() })); - heartbeatTimer = Script.setTimeout(sendHeartbeat, HEARTBEAT_INTERVAL); + + if (!isOneShot) { + heartbeatTimer = Script.setTimeout(sendHeartbeat, HEARTBEAT_INTERVAL); + } } function stopHeartbeat() { @@ -398,12 +401,12 @@ } else { log("Didn't start playing " + message.recording + " because already playing " + Player.recording()); } - sendHeartbeat(); + sendHeartbeat(true); break; case PLAYER_COMMAND_STOP: Player.stop(); Player.autoPlay(); // There may be another recording to play. - sendHeartbeat(); + sendHeartbeat(true); break; } } From 771b76690cff33cfac07f0fbcf4ec72b3c80086b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 10 May 2017 10:56:23 +1200 Subject: [PATCH 038/135] Clear timeout that checks recording started when stop recording --- scripts/system/record.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/record.js b/scripts/system/record.js index 3db82696ef..f612fea80d 100644 --- a/scripts/system/record.js +++ b/scripts/system/record.js @@ -350,7 +350,7 @@ // Cancel check that recording started playing. index = playerIDs.indexOf(playerID); if (index !== -1 && playerStartupTimeouts[index] !== null) { - // Cannot clearTimeout() without program log error, so just set null. + Script.clearTimeout(playerStartupTimeouts[index]); playerStartupTimeouts[index] = null; } @@ -374,6 +374,7 @@ if (index === -1) { index = playerIDs.length; playerIDs[index] = sender; + playerStartupTimeouts[index] = null; } playerIsPlayings[index] = message.playing; playerRecordings[index] = message.recording; From c2d22a1246248e49b9454449df84a6483e02bbf3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 10 May 2017 10:57:53 +1200 Subject: [PATCH 039/135] Log stopping playing recording --- scripts/system/record.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/record.js b/scripts/system/record.js index f612fea80d..4d5427c874 100644 --- a/scripts/system/record.js +++ b/scripts/system/record.js @@ -524,6 +524,7 @@ break; case STOP_PLAYING_RECORDING_ACTION: // Stop the specified player. + log("Unload recording " + message.value); Player.stopPlayingRecording(message.value); break; case LOAD_RECORDING_ACTION: From 5a11911e1d49bfa7e6b23ea3dd555b1f5c0c2aab Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 9 May 2017 19:38:52 -0400 Subject: [PATCH 040/135] close audio device when switching --- libraries/audio-client/src/AudioClient.cpp | 1 + libraries/audio-client/src/AudioClient.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index dae37ffc4b..440bfefe28 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1504,6 +1504,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice // cleanup any previously initialized device if (_audioOutput) { + _audioOutputIODevice.close(); _audioOutput->stop(); delete _audioOutput; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 0e5363e0ff..9bdf55504c 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -94,7 +94,6 @@ public: _audio(audio), _unfulfilledReads(0) {} void start() { open(QIODevice::ReadOnly | QIODevice::Unbuffered); } - void stop() { close(); } qint64 readData(char * data, qint64 maxSize) override; qint64 writeData(const char * data, qint64 maxSize) override { return 0; } int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; } From a15c5999b5ae6845457ec386c9fbb8a496ea0bf1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 10 May 2017 16:58:51 -0700 Subject: [PATCH 041/135] first cut at octree storage of Triangle Set --- interface/src/Application.cpp | 2 +- libraries/render-utils/src/Model.cpp | 4 +- libraries/shared/src/AABox.cpp | 41 +++++++ libraries/shared/src/AABox.h | 15 +++ libraries/shared/src/TriangleSet.cpp | 159 ++++++++++++++++++++++++++- libraries/shared/src/TriangleSet.h | 66 ++++++++++- 6 files changed, 274 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8220b3e891..01b855bb73 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -336,7 +336,7 @@ public: // Don't actually crash in debug builds, in case this apparent deadlock is simply from // the developer actively debugging code - #ifdef NDEBUG + #if 0 //def NDEBUG deadlockDetectionCrash(); #endif } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index acc84646c5..2c338674eb 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -376,7 +376,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f)); - for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { + for (auto& triangleSet : _modelSpaceMeshTriangleSets) { float triangleSetDistance = 0.0f; BoxFace triangleSetFace; glm::vec3 triangleSetNormal; @@ -462,6 +462,8 @@ bool Model::convexHullContains(glm::vec3 point) { void Model::calculateTriangleSets() { PROFILE_RANGE(render, __FUNCTION__); + qDebug() << __FUNCTION__ << "url:" << _url; + const FBXGeometry& geometry = getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 3f3146cc04..cea0a83d52 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -114,6 +114,10 @@ static bool isWithin(float value, float corner, float size) { return value >= corner && value <= corner + size; } +bool AABox::contains(const Triangle& triangle) const { + return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2); +} + bool AABox::contains(const glm::vec3& point) const { return isWithin(point.x, _corner.x, _scale.x) && isWithin(point.y, _corner.y, _scale.y) && @@ -622,3 +626,40 @@ void AABox::transform(const glm::mat4& matrix) { _corner = newCenter - newDir; _scale = newDir * 2.0f; } + +AABox AABox::getOctreeChild(OctreeChild child) const { + AABox result(*this); // self + switch (child) { + case topLeftNear: + result._corner.y += _scale.y / 2.0f; + break; + case topLeftFar: + result._corner.y += _scale.y / 2.0f; + result._corner.z += _scale.z / 2.0f; + break; + case topRightNear: + result._corner.y += _scale.y / 2.0f; + result._corner.x += _scale.x / 2.0f; + break; + case topRightFar: + result._corner.y += _scale.y / 2.0f; + result._corner.x += _scale.x / 2.0f; + result._corner.z += _scale.z / 2.0f; + break; + case bottomLeftNear: + // _corner = same as parent + break; + case bottomLeftFar: + result._corner.z += _scale.z / 2.0f; + break; + case bottomRightNear: + result._corner.x += _scale.x / 2.0f; + break; + case bottomRightFar: + result._corner.x += _scale.x / 2.0f; + result._corner.z += _scale.z / 2.0f; + break; + } + result._scale /= 2.0f; // everything is half the scale + return result; +} diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index a53cc26163..eef83974ea 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -20,6 +20,7 @@ #include #include "BoxBase.h" +#include "GeometryUtil.h" #include "StreamUtils.h" class AACube; @@ -58,6 +59,7 @@ public: const glm::vec3& getMinimumPoint() const { return _corner; } glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); } + bool contains(const Triangle& triangle) const; bool contains(const glm::vec3& point) const; bool contains(const AABox& otherBox) const; bool touches(const AABox& otherBox) const; @@ -112,6 +114,19 @@ public: void clear() { _corner = INFINITY_VECTOR; _scale = glm::vec3(0.0f); } + typedef enum { + topLeftNear, + topLeftFar, + topRightNear, + topRightFar, + bottomLeftNear, + bottomLeftFar, + bottomRightNear, + bottomRightFar + } OctreeChild; + + AABox getOctreeChild(OctreeChild child) const; // returns the AABox of the would be octree child of this AABox + private: glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const; glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const; diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index cdb3fd6b2c..e13d8b126f 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -12,7 +12,22 @@ #include "GLMHelpers.h" #include "TriangleSet.h" -void TriangleSet::insert(const Triangle& t) { + + +bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) { + + if (!_isBalanced) { + balanceOctree(); + } + + int trianglesTouched = 0; + auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched); + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population; + return result; +} + +void InternalTriangleSet::insert(const Triangle& t) { _triangles.push_back(t); _bounds += t.v0; @@ -20,15 +35,15 @@ void TriangleSet::insert(const Triangle& t) { _bounds += t.v2; } -void TriangleSet::clear() { +void InternalTriangleSet::clear() { _triangles.clear(); _bounds.clear(); } // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. -bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) const { +bool InternalTriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { bool intersectedSomething = false; float boxDistance = std::numeric_limits::max(); @@ -38,6 +53,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& if (precision) { for (const auto& triangle : _triangles) { float thisTriangleDistance; + trianglesTouched++; if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { if (thisTriangleDistance < bestDistance) { bestDistance = thisTriangleDistance; @@ -57,7 +73,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& } -bool TriangleSet::convexHullContains(const glm::vec3& point) const { +bool InternalTriangleSet::convexHullContains(const glm::vec3& point) const { if (!_bounds.contains(point)) { return false; } @@ -74,3 +90,136 @@ bool TriangleSet::convexHullContains(const glm::vec3& point) const { return insideMesh; } +void TriangleSet::debugDump() { + qDebug() << __FUNCTION__; + qDebug() << "bounds:" << getBounds(); + qDebug() << "triangles:" << size() << "at top level...."; + qDebug() << "----- _triangleOctree -----"; + _triangleOctree.debugDump(); +} + +void TriangleSet::balanceOctree() { + _triangleOctree.reset(_bounds, 0); + for (const auto& triangle : _triangles) { + _triangleOctree.insert(triangle); + } + _isBalanced = true; +} + +static const int MAX_DEPTH = 3; // for now +static const int MAX_CHILDREN = 8; + +TriangleOctreeCell::TriangleOctreeCell(const AABox& bounds, int depth) { + reset(bounds, depth); +} + +void TriangleOctreeCell::clear() { + _triangleSet.clear(); + _population = 0; +} + +void TriangleOctreeCell::reset(const AABox& bounds, int depth) { + //qDebug() << __FUNCTION__ << "bounds:" << bounds << "depth:" << depth; + clear(); + _triangleSet._bounds = bounds; + _depth = depth; + if (depth <= MAX_DEPTH) { + int childDepth = depth + 1; + _children.clear(); + for (int child = 0; child < MAX_CHILDREN; child++) { + AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child); + /* + qDebug() << __FUNCTION__ << "bounds:" << bounds << "depth:" << depth + << "child:" << child << "childBounds:" << childBounds << "childDepth:" << childDepth; + */ + + _children.push_back(TriangleOctreeCell(childBounds, childDepth)); + } + } +} + +void TriangleOctreeCell::debugDump() { + qDebug() << __FUNCTION__; + qDebug() << "bounds:" << getBounds(); + qDebug() << "depth:" << _depth; + qDebug() << "triangleSet:" << _triangleSet.size() << "at this level"; + qDebug() << "population:" << _population << "this level or below"; + if (_depth < MAX_DEPTH) { + int childNum = 0; + for (auto& child : _children) { + qDebug() << "child:" << childNum; + child.debugDump(); + childNum++; + } + } +} + +void TriangleOctreeCell::insert(const Triangle& t) { + _population++; + // if we're not yet at the max depth, then check which child the triangle fits in + if (_depth < MAX_DEPTH) { + for (auto& child : _children) { + if (child.getBounds().contains(t)) { + child.insert(t); + return; + } + } + } + // either we're at max depth, or the triangle doesn't fit in one of our + // children and so we want to just record it here + _triangleSet.insert(t); +} + +bool TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { + + if (_population < 1) { + return false; // no triangles below here, so we can't intersect + } + + float bestLocalDistance = std::numeric_limits::max(); + BoxFace bestLocalFace; + glm::vec3 bestLocalNormal; + bool intersects = false; + + // if the ray intersects our bounding box, then continue + if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) { + bestLocalDistance = std::numeric_limits::max(); + + float childDistance = std::numeric_limits::max(); + BoxFace childFace; + glm::vec3 childNormal; + + // if we're not yet at the max depth, then check which child the triangle fits in + if (_depth < MAX_DEPTH) { + for (auto& child : _children) { + // check each child, if there's an intersection, it will return some distance that we need + // to compare against the other results, because there might be multiple intersections and + // we will always choose the best (shortest) intersection + if (child.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalNormal = childNormal; + intersects = true; + } + } + } + } + // also check our local triangle set + if (_triangleSet.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalNormal = childNormal; + intersects = true; + } + } + } + if (intersects) { + distance = bestLocalDistance; + face = bestLocalFace; + surfaceNormal = bestLocalNormal; + } + return intersects; +} diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index b54f1a642a..c8703c6445 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -14,20 +14,21 @@ #include "AABox.h" #include "GeometryUtil.h" -class TriangleSet { +class InternalTriangleSet { public: + InternalTriangleSet() { } + void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles size_t size() const { return _triangles.size(); } - const Triangle& getTriangle(size_t t) const { return _triangles[t]; } - - void insert(const Triangle& t); + virtual void insert(const Triangle& t); void clear(); // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an // intersection occurs, the distance and surface normal will be provided. + // note: this might side-effect internal structures bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) const; + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); // Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to // determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a @@ -35,7 +36,60 @@ public: bool convexHullContains(const glm::vec3& point) const; const AABox& getBounds() const { return _bounds; } -private: +protected: std::vector _triangles; AABox _bounds; + + friend class TriangleOctreeCell; +}; + +class TriangleOctreeCell { +public: + TriangleOctreeCell() { } + + void insert(const Triangle& t); + void reset(const AABox& bounds, int depth = 0); + void clear(); + + // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an + // intersection occurs, the distance and surface normal will be provided. + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + + const AABox& getBounds() const { return _triangleSet.getBounds(); } + + void debugDump(); + +protected: + TriangleOctreeCell(const AABox& bounds, int depth); + + InternalTriangleSet _triangleSet; + std::vector _children; + int _depth { 0 }; + int _population { 0 }; + + friend class TriangleSet; +}; + +class TriangleSet : public InternalTriangleSet { + // pass through public implementation all the features of InternalTriangleSet +public: + TriangleSet() { } + + void debugDump(); + + virtual void insert(const Triangle& t) { + _isBalanced = false; + InternalTriangleSet::insert(t); + } + + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision); + + void balanceOctree(); + +protected: + + bool _isBalanced { false }; + TriangleOctreeCell _triangleOctree; }; From f98c3556fd0011150e35dc20275434b6321ac014 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 11 May 2017 01:08:04 +0100 Subject: [PATCH 042/135] create a wispy smoke texture --- interface/resources/images/Smoke.png | Bin 131940 -> 0 bytes interface/resources/images/wispy-smoke.png | Bin 0 -> 79835 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 interface/resources/images/Smoke.png create mode 100644 interface/resources/images/wispy-smoke.png diff --git a/interface/resources/images/Smoke.png b/interface/resources/images/Smoke.png deleted file mode 100644 index d1b4d8cac9be3a6c7e0bf3f935e09c74d2df2703..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131940 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

5Nc5#J~dS7J53Ck8T^!6SKGgx4v;e^## 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 i>pI-HapR?2DE|*TvSYzVE)fF&0000 Date: Thu, 11 May 2017 01:21:42 +0100 Subject: [PATCH 043/135] change default particle to wispy smoke --- scripts/system/edit.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 28d7b89209..b06aeb2320 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -504,30 +504,30 @@ var toolBar = (function () { blue: 0 }, emitAcceleration: { - x: 0.5, - y: 1.5, - z: 0.5 + x: 0, + y: 5, + z: 0 }, accelerationSpread: { - x: 1.0, - y: 0.0, - z: 1.0 + x: 1, + y: 0, + z: 1 }, - emitRate: 10, - emitSpeed: 2, + emitRate: 3, + emitSpeed: 0, speedSpread: 0, lifespan: 1, - maxParticles: 100, + maxParticles: 10, particleRadius: 0.25, - radiusStart: 0.05, - radiusFinish: 0.25, - radiusSpread: 0.5, + radiusStart: 0, + radiusFinish: 0.1, + radiusSpread: 0, alpha: 0, - alphaStart: 0.5, + alphaStart: 0.1, alphaFinish: 0, polarStart: 0, polarFinish: 0, - textures: "https://hifi-content.s3.amazonaws.com/DomainContent/production/Particles/Smoke.png" + textures: "https://raw.githubusercontent.com/Triplelexx/hifi/f98c3556fd0011150e35dc20275434b6321ac014/interface/resources/images/wispy-smoke.png" }); }); From 90432562df729c8961ab297bacc698562309ba0b Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 11 May 2017 01:23:21 +0100 Subject: [PATCH 044/135] remove unapproved texture --- interface/resources/images/wispy-smoke.png | Bin 79835 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 interface/resources/images/wispy-smoke.png diff --git a/interface/resources/images/wispy-smoke.png b/interface/resources/images/wispy-smoke.png deleted file mode 100644 index 6c781c824befad6e069574c51eecc309f06047d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79835 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 i>pI-HapR?2DE|*TvSYzVE)fF&0000 Date: Thu, 11 May 2017 04:16:22 -0700 Subject: [PATCH 045/135] Updated HMD grab Clone to work with all entity types. --- scripts/system/libraries/entitySelectionTool.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 3e4b8d8518..3389ab0836 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2162,6 +2162,12 @@ SelectionDisplay = (function() { position: FAR }); + Overlays.editOverlay(grabberCloner, { + visible: true, + rotation: rotation, + position: EdgeTR + }); + var boxPosition = Vec3.multiplyQbyV(rotation, center); boxPosition = Vec3.sum(position, boxPosition); Overlays.editOverlay(selectionBox, { @@ -2318,11 +2324,6 @@ SelectionDisplay = (function() { }, rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), }); - Overlays.editOverlay(grabberCloner, { - visible: stretchHandlesVisible, - rotation: rotation, - position: RIGHT - }); }; From a14fa5dab9f3da5942a8f1d08f263a5c909821dd Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 11 May 2017 10:54:15 -0700 Subject: [PATCH 046/135] code review feedback --- .../animation/src/AnimInverseKinematics.cpp | 16 ++++++++-------- libraries/animation/src/AnimUtil.cpp | 7 +++---- libraries/animation/src/Rig.cpp | 2 +- libraries/animation/src/Rig.h | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 92c74b1793..5e6927afcb 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -734,21 +734,21 @@ void AnimInverseKinematics::initConstraints() { std::vector swungDirections; float deltaTheta = PI / 4.0f; float theta = 0.0f; - swungDirections.push_back(glm::vec3(cosf(theta), -0.25f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(cosf(theta), 0.0f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(cosf(theta), 0.25f, sinf(theta))); // posterior + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); // posterior theta += deltaTheta; - swungDirections.push_back(glm::vec3(cosf(theta), 0.0f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(cosf(theta), -0.25f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(cosf(theta), -0.5f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta))); theta += deltaTheta; - swungDirections.push_back(glm::vec3(cosf(theta), -0.5f, sinf(theta))); // anterior + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta))); // anterior theta += deltaTheta; - swungDirections.push_back(glm::vec3(cosf(theta), -0.5f, sinf(theta))); + swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta))); std::vector minDots; for (size_t i = 0; i < swungDirections.size(); i++) { diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index 314f4a1c3a..a4659f1e76 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -37,16 +37,15 @@ glm::quat averageQuats(size_t numQuats, const glm::quat* quats) { if (numQuats == 0) { return glm::quat(); } - float alpha = 1.0f / (float)numQuats; - glm::quat accum(0, 0, 0, 0); + glm::quat accum = quats[0]; glm::quat firstRot = quats[0]; - for (size_t i = 0; i < numQuats; i++) { + for (size_t i = 1; i < numQuats; i++) { glm::quat rot = quats[i]; float dot = glm::dot(firstRot, rot); if (dot < 0.0f) { rot = -rot; } - accum += alpha * rot; + accum += rot; } return glm::normalize(accum); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index f933002c2c..cb5aebe930 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -941,7 +941,7 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh } } -void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform, glm::mat4 rigToWorldTransform) { +void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform) { PROFILE_RANGE_EX(simulation_animation_detail, __FUNCTION__, 0xffff00ff, 0); PerformanceTimer perfTimer("updateAnimations"); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index bee6518557..33b66f91ea 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -162,7 +162,7 @@ public: void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState); // Regardless of who started the animations or how many, update the joints. - void updateAnimations(float deltaTime, glm::mat4 rootTransform, glm::mat4 rigToWorldTransform); + void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform); // legacy void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority, From 5d457eaa3951b9b19fa9762506f85fb9c3850756 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 11 May 2017 20:44:08 +0100 Subject: [PATCH 047/135] better handling of when lost tracking of pucks --- plugins/openvr/src/ViveControllerManager.cpp | 30 ++++++++++++++++---- plugins/openvr/src/ViveControllerManager.h | 2 ++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 6e5697730b..411cac3d2b 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -32,8 +32,6 @@ #include -#include "OpenVrHelpers.h" - extern PoseData _nextSimPoseData; vr::IVRSystem* acquireOpenVrSystem(); @@ -207,6 +205,7 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle } updateCalibratedLimbs(); + _lastSimPoseData = _nextSimPoseData; } void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { @@ -217,11 +216,30 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid && poseIndex <= controller::TRACKED_OBJECT_15) { - // process pose - const mat4& mat = _nextSimPoseData.poses[deviceIndex]; - const vec3 linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; - const vec3 angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex]; + mat4& mat = mat4(); + vec3 linearVelocity = vec3(); + vec3 angularVelocity = vec3(); + // check if the device is tracking out of range, then process the correct pose depending on the result. + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult != vr::TrackingResult_Running_OutOfRange) { + mat = _nextSimPoseData.poses[deviceIndex]; + linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; + angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex]; + } else { + mat = _lastSimPoseData.poses[deviceIndex]; + linearVelocity = _lastSimPoseData.linearVelocities[deviceIndex]; + angularVelocity = _lastSimPoseData.angularVelocities[deviceIndex]; + // make sure that we do not overwrite the pose in the _lastSimPose with incorrect data. + _nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex]; + _nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex]; + _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; + + } + +/* const mat4& mat; + const vec3 linearVelocity; + const vec3 angularVelocity;*/ + controller::Pose pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity); // transform into avatar frame diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 4e8b2b3a04..ca78fd0b37 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -25,6 +25,7 @@ #include #include #include +#include "OpenVrHelpers.h" namespace vr { class IVRSystem; @@ -108,6 +109,7 @@ private: std::vector> _validTrackedObjects; std::map _pucksOffset; std::map _jointToPuckMap; + PoseData _lastSimPoseData; // perform an action when the InputDevice mutex is acquired. using Locker = std::unique_lock; template From f4df223d2337870dbb14177c9561d403c13432d5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 9 May 2017 14:48:42 -0700 Subject: [PATCH 048/135] cleanup Head::simulation() code --- interface/src/avatar/MyHead.cpp | 27 ++- .../src/avatars-renderer/Head.cpp | 199 +++++++++--------- .../src/avatars-renderer/Head.h | 5 +- libraries/avatars/src/HeadData.cpp | 6 - libraries/avatars/src/HeadData.h | 14 +- 5 files changed, 127 insertions(+), 124 deletions(-) diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index 34a75c5461..793fbb79c4 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -44,14 +44,17 @@ glm::quat MyHead::getCameraOrientation() const { void MyHead::simulate(float deltaTime) { auto player = DependencyManager::get(); // Only use face trackers when not playing back a recording. - if (!player->isPlaying()) { + if (player->isPlaying()) { + Parent::simulate(deltaTime); + } else { + computeAudioLoudness(deltaTime); + FaceTracker* faceTracker = qApp->getActiveFaceTracker(); - _isFaceTrackerConnected = faceTracker != NULL && !faceTracker->isMuted(); + _isFaceTrackerConnected = faceTracker && !faceTracker->isMuted(); if (_isFaceTrackerConnected) { _transientBlendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); if (typeid(*faceTracker) == typeid(DdeFaceTracker)) { - if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) { calculateMouthShapes(deltaTime); @@ -68,9 +71,19 @@ void MyHead::simulate(float deltaTime) { } applyEyelidOffset(getFinalOrientationInWorldFrame()); } - } + } else { + computeFaceMovement(deltaTime); + } + auto eyeTracker = DependencyManager::get(); - _isEyeTrackerConnected = eyeTracker->isTracking(); + _isEyeTrackerConnected = eyeTracker && eyeTracker->isTracking(); + if (_isEyeTrackerConnected) { + // TODO? figure out where EyeTracker data harvested. Move it here? + _saccade = glm::vec3(); + } else { + computeEyeMovement(deltaTime); + } + } - Parent::simulate(deltaTime); -} \ No newline at end of file + computeEyePosition(); +} diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index 93fe246266..b4b0929c0c 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -23,9 +23,10 @@ #include "Avatar.h" +const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for + using namespace std; -static bool fixGaze { false }; static bool disableEyelidAdjustment { false }; Head::Head(Avatar* owningAvatar) : @@ -42,17 +43,11 @@ void Head::reset() { _baseYaw = _basePitch = _baseRoll = 0.0f; } -void Head::simulate(float deltaTime) { - const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for - +void Head::computeAudioLoudness(float deltaTime) { // grab the audio loudness from the owning avatar, if we have one - float audioLoudness = 0.0f; + float audioLoudness = _owningAvatar ? _owningAvatar->getAudioLoudness() : 0.0f; - if (_owningAvatar) { - audioLoudness = _owningAvatar->getAudioLoudness(); - } - - // Update audio trailing average for rendering facial animations + // Update audio trailing average for rendering facial animations const float AUDIO_AVERAGING_SECS = 0.05f; const float AUDIO_LONG_TERM_AVERAGING_SECS = 30.0f; _averageLoudness = glm::mix(_averageLoudness, audioLoudness, glm::min(deltaTime / AUDIO_AVERAGING_SECS, 1.0f)); @@ -63,116 +58,114 @@ void Head::simulate(float deltaTime) { _longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f)); } - if (!_isFaceTrackerConnected) { - if (!_isEyeTrackerConnected) { - // Update eye saccades - const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; - const float AVERAGE_SACCADE_INTERVAL = 6.0f; - const float MICROSACCADE_MAGNITUDE = 0.002f; - const float SACCADE_MAGNITUDE = 0.04f; - const float NOMINAL_FRAME_RATE = 60.0f; + float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz + _audioAttack = audioAttackAveragingRate * _audioAttack + + (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness); + _lastLoudness = (audioLoudness - _longTermAverageLoudness); +} - if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { - _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); - } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { - _saccadeTarget = SACCADE_MAGNITUDE * randVector(); - } - _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); - } else { - _saccade = glm::vec3(); - } +void Head::computeEyeMovement(float deltaTime) { + // Update eye saccades + const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; + const float AVERAGE_SACCADE_INTERVAL = 6.0f; + const float MICROSACCADE_MAGNITUDE = 0.002f; + const float SACCADE_MAGNITUDE = 0.04f; + const float NOMINAL_FRAME_RATE = 60.0f; - // Detect transition from talking to not; force blink after that and a delay - bool forceBlink = false; - const float TALKING_LOUDNESS = 100.0f; - const float BLINK_AFTER_TALKING = 0.25f; - _timeWithoutTalking += deltaTime; - if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) { - _timeWithoutTalking = 0.0f; - } else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) { - forceBlink = true; - } + if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { + _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); + } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { + _saccadeTarget = SACCADE_MAGNITUDE * randVector(); + } + _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); - // Update audio attack data for facial animation (eyebrows and mouth) - float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz - _audioAttack = audioAttackAveragingRate * _audioAttack + - (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness); - _lastLoudness = (audioLoudness - _longTermAverageLoudness); + // Detect transition from talking to not; force blink after that and a delay + bool forceBlink = false; + const float TALKING_LOUDNESS = 100.0f; + const float BLINK_AFTER_TALKING = 0.25f; + _timeWithoutTalking += deltaTime; + if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) { + _timeWithoutTalking = 0.0f; + } else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) { + forceBlink = true; + } - const float BROW_LIFT_THRESHOLD = 100.0f; - if (_audioAttack > BROW_LIFT_THRESHOLD) { - _browAudioLift += sqrtf(_audioAttack) * 0.01f; - } - _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); - - const float BLINK_SPEED = 10.0f; - const float BLINK_SPEED_VARIABILITY = 1.0f; - const float BLINK_START_VARIABILITY = 0.25f; - const float FULLY_OPEN = 0.0f; - const float FULLY_CLOSED = 1.0f; - if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { - // no blinking when brows are raised; blink less with increasing loudness - const float BASE_BLINK_RATE = 15.0f / 60.0f; - const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; - if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * - ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { - _leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; - _rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; - if (randFloat() < 0.5f) { - _leftEyeBlink = BLINK_START_VARIABILITY; - } else { - _rightEyeBlink = BLINK_START_VARIABILITY; - } - } - } else { - _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); - _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); - - if (_leftEyeBlink == FULLY_CLOSED) { - _leftEyeBlinkVelocity = -BLINK_SPEED; - - } else if (_leftEyeBlink == FULLY_OPEN) { - _leftEyeBlinkVelocity = 0.0f; - } - if (_rightEyeBlink == FULLY_CLOSED) { - _rightEyeBlinkVelocity = -BLINK_SPEED; - - } else if (_rightEyeBlink == FULLY_OPEN) { - _rightEyeBlinkVelocity = 0.0f; + const float BLINK_SPEED = 10.0f; + const float BLINK_SPEED_VARIABILITY = 1.0f; + const float BLINK_START_VARIABILITY = 0.25f; + const float FULLY_OPEN = 0.0f; + const float FULLY_CLOSED = 1.0f; + if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { + // no blinking when brows are raised; blink less with increasing loudness + const float BASE_BLINK_RATE = 15.0f / 60.0f; + const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; + if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * + ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { + _leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; + _rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; + if (randFloat() < 0.5f) { + _leftEyeBlink = BLINK_START_VARIABILITY; + } else { + _rightEyeBlink = BLINK_START_VARIABILITY; } } - - // use data to update fake Faceshift blendshape coefficients - calculateMouthShapes(deltaTime); - FaceTracker::updateFakeCoefficients(_leftEyeBlink, - _rightEyeBlink, - _browAudioLift, - _audioJawOpen, - _mouth2, - _mouth3, - _mouth4, - _transientBlendshapeCoefficients); - - applyEyelidOffset(getOrientation()); - } else { - _saccade = glm::vec3(); - } - if (fixGaze) { // if debug menu turns off, use no saccade - _saccade = glm::vec3(); + _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); + _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED); + + if (_leftEyeBlink == FULLY_CLOSED) { + _leftEyeBlinkVelocity = -BLINK_SPEED; + + } else if (_leftEyeBlink == FULLY_OPEN) { + _leftEyeBlinkVelocity = 0.0f; + } + if (_rightEyeBlink == FULLY_CLOSED) { + _rightEyeBlinkVelocity = -BLINK_SPEED; + + } else if (_rightEyeBlink == FULLY_OPEN) { + _rightEyeBlinkVelocity = 0.0f; + } } + applyEyelidOffset(getOrientation()); +} + +void Head::computeFaceMovement(float deltaTime) { + // Update audio attack data for facial animation (eyebrows and mouth) + const float BROW_LIFT_THRESHOLD = 100.0f; + if (_audioAttack > BROW_LIFT_THRESHOLD) { + _browAudioLift += sqrtf(_audioAttack) * 0.01f; + } + _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); + + // use data to update fake Faceshift blendshape coefficients + calculateMouthShapes(deltaTime); + FaceTracker::updateFakeCoefficients(_leftEyeBlink, + _rightEyeBlink, + _browAudioLift, + _audioJawOpen, + _mouth2, + _mouth3, + _mouth4, + _transientBlendshapeCoefficients); +} + +void Head::computeEyePosition() { _leftEyePosition = _rightEyePosition = getPosition(); - _eyePosition = getPosition(); - if (_owningAvatar) { auto skeletonModel = static_cast(_owningAvatar)->getSkeletonModel(); if (skeletonModel) { skeletonModel->getEyePositions(_leftEyePosition, _rightEyePosition); } } + _eyePosition = 0.5f * (_leftEyePosition + _rightEyePosition); +} - _eyePosition = calculateAverageEyePosition(); +void Head::simulate(float deltaTime) { + computeAudioLoudness(deltaTime); + computeFaceMovement(deltaTime); + computeEyeMovement(deltaTime); + computeEyePosition(); } void Head::calculateMouthShapes(float deltaTime) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.h b/libraries/avatars-renderer/src/avatars-renderer/Head.h index aea6a41528..39331500b5 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.h @@ -83,7 +83,10 @@ public: float getTimeWithoutTalking() const { return _timeWithoutTalking; } protected: - glm::vec3 calculateAverageEyePosition() const { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * 0.5f; } + void computeAudioLoudness(float deltaTime); + void computeEyeMovement(float deltaTime); + void computeFaceMovement(float deltaTime); + void computeEyePosition(); // disallow copies of the Head, copy of owning Avatar is disallowed too Head(const Head&); diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 2e4eec73a8..2704b6539c 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -28,12 +28,6 @@ HeadData::HeadData(AvatarData* owningAvatar) : _basePitch(0.0f), _baseRoll(0.0f), _lookAtPosition(0.0f, 0.0f, 0.0f), - _isFaceTrackerConnected(false), - _isEyeTrackerConnected(false), - _leftEyeBlink(0.0f), - _rightEyeBlink(0.0f), - _averageLoudness(0.0f), - _browAudioLift(0.0f), _blendshapeCoefficients(QVector(0, 0.0f)), _transientBlendshapeCoefficients(QVector(0, 0.0f)), _summedBlendshapeCoefficients(QVector(0, 0.0f)), diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 9b28616b3f..be9d54e93e 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -63,7 +63,7 @@ public: void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; } const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } - void setLookAtPosition(const glm::vec3& lookAtPosition) { + void setLookAtPosition(const glm::vec3& lookAtPosition) { if (_lookAtPosition != lookAtPosition) { _lookAtPositionChanged = usecTimestampNow(); } @@ -85,12 +85,12 @@ protected: glm::vec3 _lookAtPosition; quint64 _lookAtPositionChanged { 0 }; - bool _isFaceTrackerConnected; - bool _isEyeTrackerConnected; - float _leftEyeBlink; - float _rightEyeBlink; - float _averageLoudness; - float _browAudioLift; + bool _isFaceTrackerConnected { false }; + bool _isEyeTrackerConnected { false }; + float _leftEyeBlink { 0.0f }; + float _rightEyeBlink { 0.0f }; + float _averageLoudness { 0.0f }; + float _browAudioLift { 0.0f }; QVector _blendshapeCoefficients; QVector _transientBlendshapeCoefficients; From 04827c3a2a39544cdddda68d0bde52d9beb2a512 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 11 May 2017 14:51:52 -0700 Subject: [PATCH 049/135] use const auto& where appropritate --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 51b69711dc..e2188478ed 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -445,7 +445,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasFaceTrackerInfo) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - auto blendshapeCoefficients = _headData->getSummedBlendshapeCoefficients(); + const auto& blendshapeCoefficients = _headData->getSummedBlendshapeCoefficients(); faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; From a43447310df5174942bc864bd1ed8333568ae2a9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 11 May 2017 15:22:22 -0700 Subject: [PATCH 050/135] remove physics dependency from avatars-render lib --- interface/src/avatar/AvatarManager.h | 2 +- .../src/avatar}/AvatarMotionState.cpp | 0 .../src/avatar}/AvatarMotionState.h | 2 +- libraries/avatars-renderer/CMakeLists.txt | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename {libraries/avatars-renderer/src/avatars-renderer => interface/src/avatar}/AvatarMotionState.cpp (100%) rename {libraries/avatars-renderer/src/avatars-renderer => interface/src/avatar}/AvatarMotionState.h (98%) diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 9df1639853..f1e71f7367 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -21,9 +21,9 @@ #include #include #include -#include #include +#include "AvatarMotionState.h" #include "MyAvatar.h" class AudioInjector; diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp similarity index 100% rename from libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.cpp rename to interface/src/avatar/AvatarMotionState.cpp diff --git a/libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h similarity index 98% rename from libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.h rename to interface/src/avatar/AvatarMotionState.h index f8801ddf2f..90bd2a60ac 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -14,10 +14,10 @@ #include +#include #include #include -#include "Avatar.h" class AvatarMotionState : public ObjectMotionState { public: diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index b13bc0a4a6..2ac5e6766d 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME avatars-renderer) AUTOSCRIBE_SHADER_LIB(gpu model render render-utils) setup_hifi_library(Widgets Network Script) -link_hifi_libraries(shared gpu model animation physics model-networking script-engine render image render-utils) +link_hifi_libraries(shared gpu model animation model-networking script-engine render image render-utils) target_bullet() From 5042d4d3129b667992769f5dbd62699a608241d4 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 12 May 2017 00:28:31 +0100 Subject: [PATCH 051/135] add print when device changes tracking result --- plugins/openvr/src/ViveControllerManager.cpp | 41 ++++++++++++++++---- plugins/openvr/src/ViveControllerManager.h | 1 + 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 606cc38da2..a5b742c32c 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -29,7 +29,6 @@ #include #include - #include #include @@ -67,6 +66,28 @@ static bool sortPucksYPosition(std::pair firstPuck, return (firstPuck.second.translation.y < firstPuck.second.translation.y); } +static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) { + QString result; + switch (trackingResult) { + case vr::TrackingResult_Uninitialized: + result = "vr::TrackingResult_Uninitialized"; + break; + case vr::TrackingResult_Calibrating_InProgress: + result = "vr::TrackingResult_Calibrating_InProgess"; + break; + case vr::TrackingResult_Calibrating_OutOfRange: + result = "vr::TrackingResult_Calibrating_OutOfRange"; + break; + case vr::TrackingResult_Running_OK: + result = "vr::TrackingResult_Running_OK"; + break; + case vr::TrackingResult_Running_OutOfRange: + result = "vr::TrackingResult_Running_OutOfRange"; + break; + } + return result; +} + bool ViveControllerManager::isSupported() const { return openVrSupported(); } @@ -212,7 +233,7 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { uint32_t poseIndex = controller::TRACKED_OBJECT_00 + deviceIndex; - + printDeviceTrackingResultChange(deviceIndex); if (_system->IsTrackedDeviceConnected(deviceIndex) && _system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_GenericTracker && _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid && @@ -222,7 +243,7 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde vec3 linearVelocity = vec3(); vec3 angularVelocity = vec3(); // check if the device is tracking out of range, then process the correct pose depending on the result. - if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult != vr::TrackingResult_Running_OutOfRange) { + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult != vr::TrackingResult_Running_OutOfRange) { mat = _nextSimPoseData.poses[deviceIndex]; linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex]; @@ -235,13 +256,9 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde _nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex]; _nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex]; _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; - + } -/* const mat4& mat; - const vec3 linearVelocity; - const vec3 angularVelocity;*/ - controller::Pose pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity); // transform into avatar frame @@ -466,6 +483,14 @@ enum ViveButtonChannel { RIGHT_APP_MENU }; +void ViveControllerManager::InputDevice::printDeviceTrackingResultChange(uint32_t deviceIndex) { + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult != _lastSimPoseData.vrPoses[deviceIndex].eTrackingResult) { + qDebug() << "OpenVR: Device" << deviceIndex << "Tracking Result changed from" << + deviceTrackingResultToString(_lastSimPoseData.vrPoses[deviceIndex].eTrackingResult) + << "to" << deviceTrackingResultToString(_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult); + } +} + bool ViveControllerManager::InputDevice::checkForCalibrationEvent() { auto& endOfMap = _buttonPressedMap.end(); auto& leftTrigger = _buttonPressedMap.find(controller::LT); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 30680ec264..c815506770 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -77,6 +77,7 @@ private: void handleHeadPoseEvent(const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity); void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); + void printDeviceTrackingResultChange(uint32_t deviceIndex); class FilteredStick { public: From f74e29e4b0f0546780082571e8819c84c7035ea6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 12 May 2017 11:43:12 +1200 Subject: [PATCH 052/135] Code review --- scripts/system/playRecordingAC.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index 6c7a5eb8b1..8351d6f5e1 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -368,16 +368,21 @@ }; }()); - function sendHeartbeat(isOneShot) { + function sendHeartbeat() { Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({ playing: Player.isPlaying(), recording: Player.recording(), entity: Entity.id() })); + } - if (!isOneShot) { - heartbeatTimer = Script.setTimeout(sendHeartbeat, HEARTBEAT_INTERVAL); - } + function onHeartbeatTimer() { + sendHeartbeat(); + heartbeatTimer = Script.setTimeout(onHeartbeatTimer, HEARTBEAT_INTERVAL); + } + + function startHeartbeat() { + onHeartbeatTimer(); } function stopHeartbeat() { @@ -401,12 +406,12 @@ } else { log("Didn't start playing " + message.recording + " because already playing " + Player.recording()); } - sendHeartbeat(true); + sendHeartbeat(); break; case PLAYER_COMMAND_STOP: Player.stop(); Player.autoPlay(); // There may be another recording to play. - sendHeartbeat(true); + sendHeartbeat(); break; } } @@ -421,7 +426,7 @@ Messages.subscribe(HIFI_PLAYER_CHANNEL); Player.autoPlay(); - sendHeartbeat(); + startHeartbeat(); UserActivityLogger.logAction("playRecordingAC_script_load"); } From d7a4396daaf7010152c509a97b65c350c837cd09 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 12 May 2017 11:45:11 +1200 Subject: [PATCH 053/135] Improve log messages --- scripts/system/playRecordingAC.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index 8351d6f5e1..e738c01d32 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -83,7 +83,7 @@ // Create a new persistence entity (even if already have one but that should never occur). var properties; - log("Create recording " + filename); + log("Create recording entity for " + filename); if (updateTimestampTimer !== null) { Script.clearInterval(updateTimestampTimer); // Just in case. @@ -114,6 +114,7 @@ return true; } + log("Could not create recording entity for " + filename); return false; } @@ -269,12 +270,12 @@ function play(recording, position, orientation) { if (Entity.create(recording, position, orientation)) { - log("Play new recording " + recordingFilename); + log("Play recording " + recordingFilename); isPlayingRecording = true; recordingFilename = recording; playRecording(recordingFilename, position, orientation); } else { - log("Could not create entity to play new recording " + recordingFilename); + log("Could not play recording " + recordingFilename); } } From a4406906f3d25d34aac707e0c3bdd4540649802b Mon Sep 17 00:00:00 2001 From: hiflex Date: Thu, 11 May 2017 20:12:10 -0500 Subject: [PATCH 054/135] Fix the Face Camera checkbox on the text entity properties tab. The checkbox appeared unchecked even if the underlying property was true. --- scripts/system/html/js/entityProperties.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 2f109597d7..e000e14aec 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1020,7 +1020,7 @@ function loaded() { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); - elTextFaceCamera = properties.faceCamera; + elTextFaceCamera.checked = properties.faceCamera; elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; From 26672b475ff280bee0b32b13fa75238cf00a1ba9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 12 May 2017 14:13:32 +1200 Subject: [PATCH 055/135] Clear timer value when timer stopped --- scripts/system/playRecordingAC.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index e738c01d32..cc4c0345a9 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -85,8 +85,9 @@ log("Create recording entity for " + filename); - if (updateTimestampTimer !== null) { - Script.clearInterval(updateTimestampTimer); // Just in case. + if (updateTimestampTimer !== null) { // Just in case. + Script.clearInterval(updateTimestampTimer); + updateTimestampTimer = null; } searchState = SEARCH_IDLE; @@ -234,6 +235,7 @@ } if (updateTimestampTimer !== null) { // Just in case. Script.clearInterval(updateTimestampTimer); + updateTimestampTimer = null; } } From 42aacb07ff4b0e4aaa2dacd671f68ef79ce2877e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 12 May 2017 14:14:24 +1200 Subject: [PATCH 056/135] Remove check that recording has started playing --- scripts/system/record.js | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/scripts/system/record.js b/scripts/system/record.js index 4d5427c874..f9936942fb 100644 --- a/scripts/system/record.js +++ b/scripts/system/record.js @@ -277,7 +277,6 @@ playerIsPlayings = [], // True if AC player script is playing a recording. playerRecordings = [], // Assignment client mappings of recordings being played. playerTimestamps = [], // Timestamps of last heartbeat update from player script. - playerStartupTimeouts = [], // Timers that check that recording has started playing. updateTimer, UPDATE_INTERVAL = 5000; // Must be > player's HEARTBEAT_INTERVAL. @@ -298,7 +297,6 @@ playerIsPlayings.splice(i, 1); playerRecordings.splice(i, 1); playerTimestamps.splice(i, 1); - playerStartupTimeouts.splice(i, 1); } } @@ -309,8 +307,7 @@ } function playRecording(recording, position, orientation) { - var index, - CHECK_PLAYING_TIMEOUT = 10000; + var index; // Optional function parameters. if (position === undefined) { @@ -334,26 +331,9 @@ position: position, orientation: orientation })); - - playerStartupTimeouts[index] = Script.setTimeout(function () { - if ((!playerIsPlayings[index] || playerRecordings[index] !== recording) && playerStartupTimeouts[index]) { - error("Didn't start playing recording " - + recording.slice(4) + "!"); // Remove leading "atp:" from recording. - } - playerStartupTimeouts[index] = null; - }, CHECK_PLAYING_TIMEOUT); } function stopPlayingRecording(playerID) { - var index; - - // Cancel check that recording started playing. - index = playerIDs.indexOf(playerID); - if (index !== -1 && playerStartupTimeouts[index] !== null) { - Script.clearTimeout(playerStartupTimeouts[index]); - playerStartupTimeouts[index] = null; - } - Messages.sendMessage(HIFI_PLAYER_CHANNEL, JSON.stringify({ player: playerID, command: PLAYER_COMMAND_STOP @@ -374,7 +354,6 @@ if (index === -1) { index = playerIDs.length; playerIDs[index] = sender; - playerStartupTimeouts[index] = null; } playerIsPlayings[index] = message.playing; playerRecordings[index] = message.recording; @@ -387,7 +366,6 @@ playerIsPlayings = []; playerRecordings = []; playerTimestamps = []; - playerStartupTimeouts = []; Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs); } From acd71caae273b973dc64b66072bb4afa3aae9efc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 12 May 2017 14:53:38 +1200 Subject: [PATCH 057/135] Inform user when an AC error prevents a recording from playing --- scripts/system/playRecordingAC.js | 37 +++++++++++++++++++++++-------- scripts/system/record.js | 21 +++++++++++------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index cc4c0345a9..5dab0c2ba0 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -14,6 +14,7 @@ var APP_NAME = "PLAYBACK", HIFI_RECORDER_CHANNEL = "HiFi-Recorder-Channel", + RECORDER_COMMAND_ERROR = "error", HIFI_PLAYER_CHANNEL = "HiFi-Player-Channel", PLAYER_COMMAND_PLAY = "play", PLAYER_COMMAND_STOP = "stop", @@ -69,12 +70,14 @@ if (sender !== scriptUUID) { message = JSON.parse(message); - index = otherPlayersPlaying.indexOf(message.entity); - if (index !== -1) { - otherPlayersPlayingCounts[index] += 1; - } else { - otherPlayersPlaying.push(message.entity); - otherPlayersPlayingCounts.push(1); + if (message.playing !== undefined) { + index = otherPlayersPlaying.indexOf(message.entity); + if (index !== -1) { + otherPlayersPlayingCounts[index] += 1; + } else { + otherPlayersPlaying.push(message.entity); + otherPlayersPlayingCounts.push(1); + } } } } @@ -270,14 +273,26 @@ playRecording; + function error(message) { + // Send error message to user. + Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({ + command: RECORDER_COMMAND_ERROR, + message: message + })); + } + function play(recording, position, orientation) { + var errorMessage; + if (Entity.create(recording, position, orientation)) { - log("Play recording " + recordingFilename); + log("Play recording " + recording); isPlayingRecording = true; recordingFilename = recording; playRecording(recordingFilename, position, orientation); } else { - log("Could not play recording " + recordingFilename); + errorMessage = "Could not play recording " + recording.slice(4); // Remove leading "atp:". + log(errorMessage); + error(errorMessage); } } @@ -299,6 +314,8 @@ playRecording = function (recording, position, orientation) { Recording.loadRecording(recording, function (success) { + var errorMessage; + if (success) { Users.disableIgnoreRadius(); @@ -321,7 +338,9 @@ UserActivityLogger.logAction("playRecordingAC_play_recording"); } else { - log("Failed to load recording " + recording); + errorMessage = "Could not load recording " + recording.slice(4); // Remove leading "atp:". + log(errorMessage); + error(errorMessage); autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Try again later. } }); diff --git a/scripts/system/record.js b/scripts/system/record.js index f9936942fb..1c36a122b9 100644 --- a/scripts/system/record.js +++ b/scripts/system/record.js @@ -269,6 +269,7 @@ Player = (function () { var HIFI_RECORDER_CHANNEL = "HiFi-Recorder-Channel", + RECORDER_COMMAND_ERROR = "error", HIFI_PLAYER_CHANNEL = "HiFi-Player-Channel", PLAYER_COMMAND_PLAY = "play", PLAYER_COMMAND_STOP = "stop", @@ -350,15 +351,19 @@ message = JSON.parse(message); - index = playerIDs.indexOf(sender); - if (index === -1) { - index = playerIDs.length; - playerIDs[index] = sender; + if (message.command === RECORDER_COMMAND_ERROR) { + error(message.message); + } else { + index = playerIDs.indexOf(sender); + if (index === -1) { + index = playerIDs.length; + playerIDs[index] = sender; + } + playerIsPlayings[index] = message.playing; + playerRecordings[index] = message.recording; + playerTimestamps[index] = Date.now(); + Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs); } - playerIsPlayings[index] = message.playing; - playerRecordings[index] = message.recording; - playerTimestamps[index] = Date.now(); - Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs); } function reset() { From 2bed69cfea194ec1315861b458ad7f9d20518730 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 12 May 2017 15:18:25 +1200 Subject: [PATCH 058/135] Display playback errors only for user that initiated playback --- scripts/system/playRecordingAC.js | 11 ++++++++--- scripts/system/record.js | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index 5dab0c2ba0..4cf65c7942 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -267,7 +267,8 @@ Player = (function () { // Recording playback functions. - var isPlayingRecording = false, + var userID = null, + isPlayingRecording = false, recordingFilename = "", autoPlayTimer = null, @@ -277,13 +278,16 @@ // Send error message to user. Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({ command: RECORDER_COMMAND_ERROR, + user: userID, message: message })); } - function play(recording, position, orientation) { + function play(user, recording, position, orientation) { var errorMessage; + userID = user; + if (Entity.create(recording, position, orientation)) { log("Play recording " + recording); isPlayingRecording = true; @@ -305,6 +309,7 @@ recording = Entity.find(); if (recording) { log("Play persisted recording " + recordingFilename); + userID = null; playRecording(recording.recording, recording.position, recording.orientation); } else { autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_SEARCH_INTERVAL); // Try again soon. @@ -424,7 +429,7 @@ switch (message.command) { case PLAYER_COMMAND_PLAY: if (!Player.isPlaying()) { - Player.play(message.recording, message.position, message.orientation); + Player.play(sender, message.recording, message.position, message.orientation); } else { log("Didn't start playing " + message.recording + " because already playing " + Player.recording()); } diff --git a/scripts/system/record.js b/scripts/system/record.js index 1c36a122b9..d5195d19cc 100644 --- a/scripts/system/record.js +++ b/scripts/system/record.js @@ -352,7 +352,9 @@ message = JSON.parse(message); if (message.command === RECORDER_COMMAND_ERROR) { - error(message.message); + if (message.user === MyAvatar.sessionUUID) { + error(message.message); + } } else { index = playerIDs.indexOf(sender); if (index === -1) { From a12e8e34dbd5833b8013cfbcfbeac267a32538e1 Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Fri, 12 May 2017 15:06:22 -0700 Subject: [PATCH 059/135] coding standard --- scripts/tutorials/createFloatingLanternBox.js | 7 +- .../entity_scripts/floatingLantern.js | 168 +++++++++--------- .../entity_scripts/floatingLanternBox.js | 20 ++- 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/scripts/tutorials/createFloatingLanternBox.js b/scripts/tutorials/createFloatingLanternBox.js index 611e995fcb..a925cfff22 100644 --- a/scripts/tutorials/createFloatingLanternBox.js +++ b/scripts/tutorials/createFloatingLanternBox.js @@ -19,6 +19,7 @@ var SCRIPT_URL = Script.resolvePath("./entity_scripts/floatingLanternBox.js?v=" var START_POSITION = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), 2)); START_POSITION.y -= .6; var LIFETIME = 3600; +var SCALE_FACTOR = 1; var lanternBox = { type: "Model", @@ -31,9 +32,9 @@ var lanternBox = { position: START_POSITION, lifetime: LIFETIME, dimensions: { - x: 0.8696, - y: 0.58531, - z: 0.9264 + x: 0.8696 * SCALE_FACTOR, + y: 0.58531 * SCALE_FACTOR, + z: 0.9264 * SCALE_FACTOR }, owningAvatarID: MyAvatar.sessionUUID }; diff --git a/scripts/tutorials/entity_scripts/floatingLantern.js b/scripts/tutorials/entity_scripts/floatingLantern.js index 8fa2828c90..aa25dc0003 100644 --- a/scripts/tutorials/entity_scripts/floatingLantern.js +++ b/scripts/tutorials/entity_scripts/floatingLantern.js @@ -14,93 +14,93 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html (function() { - var _this; + var _this; - var SLOW_SPIN_THRESHOLD = 0.1; - var ROTATION_COMPLETE_THRESHOLD = 0.01; - var ROTATION_SPEED = 0.2; - var HOME_ROTATION = {x: 0, y: 0, z: 0, w: 0}; + var SLOW_SPIN_THRESHOLD = 0.1; + var ROTATION_COMPLETE_THRESHOLD = 0.01; + var ROTATION_SPEED = 0.2; + var HOME_ROTATION = {x: 0, y: 0, z: 0, w: 0}; - floatingLantern = function() { - _this = this; - this.updateConnected = false; - }; - - floatingLantern.prototype = { - - preload: function(entityID) { - this.entityID = entityID; - }, - - unload: function(entityID) { - this.disconnectUpdate(); - }, - - startNearGrab: function() { - this.disconnectUpdate(); - }, - - startDistantGrab: function() { - this.disconnectUpdate(); - }, - - releaseGrab: function() { - Entities.editEntity(this.entityID, { - gravity: { - x: 0, - y: 0.5, - z: 0 - } - }); - }, - - update: function(dt) { - var lanternProps = Entities.getEntityProperties(_this.entityID); - - if(lanternProps && lanternProps.rotation && lanternProps.owningAvatarID === MyAvatar.sessionUUID) { - - var spinningSlowly = ( - Math.abs(lanternProps.angularVelocity.x) < SLOW_SPIN_THRESHOLD && - Math.abs(lanternProps.angularVelocity.y) < SLOW_SPIN_THRESHOLD && - Math.abs(lanternProps.angularVelocity.z) < SLOW_SPIN_THRESHOLD - ); - - var rotationComplete = ( - Math.abs(lanternProps.rotation.x - HOME_ROTATION.x) < ROTATION_COMPLETE_THRESHOLD && - Math.abs(lanternProps.rotation.y - HOME_ROTATION.y) < ROTATION_COMPLETE_THRESHOLD && - Math.abs(lanternProps.rotation.z - HOME_ROTATION.z) < ROTATION_COMPLETE_THRESHOLD - ); - - if(spinningSlowly && !rotationComplete) { - var newRotation = Quat.slerp(lanternProps.rotation, HOME_ROTATION, ROTATION_SPEED * dt); - - Entities.editEntity(_this.entityID, { - rotation: newRotation, - angularVelocity: { - x: 0, - y: 0, - z: 0 - } - }); - } - } - }, - - connectUpdate: function() { - if(!this.updateConnected) { - this.updateConnected = true; - Script.update.connect(this.update); - } - }, - - disconnectUpdate: function() { - if(this.updateConnected) { + floatingLantern = function() { + _this = this; this.updateConnected = false; - Script.update.disconnect(this.update); - } - } - }; + }; - return new floatingLantern(); + floatingLantern.prototype = { + + preload: function(entityID) { + this.entityID = entityID; + }, + + unload: function(entityID) { + this.disconnectUpdate(); + }, + + startNearGrab: function() { + this.disconnectUpdate(); + }, + + startDistantGrab: function() { + this.disconnectUpdate(); + }, + + releaseGrab: function() { + Entities.editEntity(this.entityID, { + gravity: { + x: 0, + y: 0.5, + z: 0 + } + }); + }, + + update: function(dt) { + var lanternProps = Entities.getEntityProperties(_this.entityID); + + if (lanternProps && lanternProps.rotation && lanternProps.owningAvatarID === MyAvatar.sessionUUID) { + + var spinningSlowly = ( + Math.abs(lanternProps.angularVelocity.x) < SLOW_SPIN_THRESHOLD && + Math.abs(lanternProps.angularVelocity.y) < SLOW_SPIN_THRESHOLD && + Math.abs(lanternProps.angularVelocity.z) < SLOW_SPIN_THRESHOLD + ); + + var rotationComplete = ( + Math.abs(lanternProps.rotation.x - HOME_ROTATION.x) < ROTATION_COMPLETE_THRESHOLD && + Math.abs(lanternProps.rotation.y - HOME_ROTATION.y) < ROTATION_COMPLETE_THRESHOLD && + Math.abs(lanternProps.rotation.z - HOME_ROTATION.z) < ROTATION_COMPLETE_THRESHOLD + ); + + if (spinningSlowly && !rotationComplete) { + var newRotation = Quat.slerp(lanternProps.rotation, HOME_ROTATION, ROTATION_SPEED * dt); + + Entities.editEntity(_this.entityID, { + rotation: newRotation, + angularVelocity: { + x: 0, + y: 0, + z: 0 + } + }); + } + } + }, + + connectUpdate: function() { + if (!this.updateConnected) { + this.updateConnected = true; + Script.update.connect(this.update); + } + }, + + disconnectUpdate: function() { + if (this.updateConnected) { + this.updateConnected = false; + Script.update.disconnect(this.update); + } + } + }; + + return new floatingLantern(); }); diff --git a/scripts/tutorials/entity_scripts/floatingLanternBox.js b/scripts/tutorials/entity_scripts/floatingLanternBox.js index 2c483f6129..ba44fbaa9d 100644 --- a/scripts/tutorials/entity_scripts/floatingLanternBox.js +++ b/scripts/tutorials/entity_scripts/floatingLanternBox.js @@ -21,6 +21,7 @@ var LIFETIME = 120; var RESPAWN_INTERVAL = 1000; var MAX_LANTERNS = 4; + var SCALE_FACTOR = 1; var LANTERN = { type: "Model", @@ -29,9 +30,9 @@ modelURL: LANTERN_MODEL_URL, script: LANTERN_SCRIPT_URL, dimensions: { - x: 0.2049, - y: 0.4, - z: 0.2049 + x: 0.2049 * SCALE_FACTOR, + y: 0.4 * SCALE_FACTOR, + z: 0.2049 * SCALE_FACTOR }, gravity: { x: 0, @@ -57,14 +58,15 @@ this.entityID = entityID; var props = Entities.getEntityProperties(this.entityID); - if(props.owningAvatarID === MyAvatar.sessionUUID){ + if (props.owningAvatarID === MyAvatar.sessionUUID) { this.respawnTimer = Script.setInterval(this.spawnAllLanterns.bind(this), RESPAWN_INTERVAL); } }, unload: function(entityID) { - if(this.respawnTimer) - Script.clearInterval(this.respawnTimer); + if (this.respawnTimer) { + Script.clearInterval(this.respawnTimer); + } }, spawnAllLanterns: function() { @@ -72,14 +74,14 @@ var lanternCount = 0; var nearbyEntities = Entities.findEntities(props.position, props.dimensions.x * 0.75); - for(var i = 0; i < nearbyEntities.length; i++) { + for (var i = 0; i < nearbyEntities.length; i++) { var name = Entities.getEntityProperties(nearbyEntities[i], ["name"]).name; - if(name === "Floating Lantern") { + if (name === "Floating Lantern") { lanternCount++; } } - while(lanternCount++ < MAX_LANTERNS) { + while (lanternCount++ < MAX_LANTERNS) { this.spawnLantern(); } }, From f3d8d1641f62b36365671dcf8f085bbfc97aa40e Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Fri, 12 May 2017 15:10:40 -0700 Subject: [PATCH 060/135] fix indent createFloatingLanternBox.js --- scripts/tutorials/createFloatingLanternBox.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/tutorials/createFloatingLanternBox.js b/scripts/tutorials/createFloatingLanternBox.js index a925cfff22..c84214e295 100644 --- a/scripts/tutorials/createFloatingLanternBox.js +++ b/scripts/tutorials/createFloatingLanternBox.js @@ -22,21 +22,21 @@ var LIFETIME = 3600; var SCALE_FACTOR = 1; var lanternBox = { - type: "Model", - name: "Floating Lantern Box", - description: "Spawns Lanterns that float away when grabbed and released!", - script: SCRIPT_URL, - modelURL: MODEL_URL, - shapeType: "Compound", - compoundShapeURL: COMPOUND_SHAPE_URL, - position: START_POSITION, - lifetime: LIFETIME, - dimensions: { - x: 0.8696 * SCALE_FACTOR, - y: 0.58531 * SCALE_FACTOR, - z: 0.9264 * SCALE_FACTOR - }, - owningAvatarID: MyAvatar.sessionUUID + type: "Model", + name: "Floating Lantern Box", + description: "Spawns Lanterns that float away when grabbed and released!", + script: SCRIPT_URL, + modelURL: MODEL_URL, + shapeType: "Compound", + compoundShapeURL: COMPOUND_SHAPE_URL, + position: START_POSITION, + lifetime: LIFETIME, + dimensions: { + x: 0.8696 * SCALE_FACTOR, + y: 0.58531 * SCALE_FACTOR, + z: 0.9264 * SCALE_FACTOR + }, + owningAvatarID: MyAvatar.sessionUUID }; Entities.addEntity(lanternBox); From 9fc0ad0d28dc79aa258e82f7c9af0229163570a6 Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Fri, 12 May 2017 15:12:43 -0700 Subject: [PATCH 061/135] fix indent floatingLanternBox --- .../entity_scripts/floatingLanternBox.js | 150 +++++++++--------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/scripts/tutorials/entity_scripts/floatingLanternBox.js b/scripts/tutorials/entity_scripts/floatingLanternBox.js index ba44fbaa9d..b5fb0c27d9 100644 --- a/scripts/tutorials/entity_scripts/floatingLanternBox.js +++ b/scripts/tutorials/entity_scripts/floatingLanternBox.js @@ -15,89 +15,89 @@ (function() { - var _this; - var LANTERN_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Welcome%20Area/Models/chinaLantern_capsule.fbx"; - var LANTERN_SCRIPT_URL = Script.resolvePath("floatingLantern.js?v=" + Date.now()); - var LIFETIME = 120; - var RESPAWN_INTERVAL = 1000; - var MAX_LANTERNS = 4; - var SCALE_FACTOR = 1; + var _this; + var LANTERN_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Welcome%20Area/Models/chinaLantern_capsule.fbx"; + var LANTERN_SCRIPT_URL = Script.resolvePath("floatingLantern.js?v=" + Date.now()); + var LIFETIME = 120; + var RESPAWN_INTERVAL = 1000; + var MAX_LANTERNS = 4; + var SCALE_FACTOR = 1; - var LANTERN = { - type: "Model", - name: "Floating Lantern", - description: "Spawns Lanterns that float away when grabbed and released!", - modelURL: LANTERN_MODEL_URL, - script: LANTERN_SCRIPT_URL, - dimensions: { - x: 0.2049 * SCALE_FACTOR, - y: 0.4 * SCALE_FACTOR, - z: 0.2049 * SCALE_FACTOR - }, - gravity: { - x: 0, - y: -1, - z: 0 - }, - velocity: { - x: 0, y: .01, z: 0 - }, - linearDampening: 0, - shapeType: 'Box', - lifetime: LIFETIME, - dynamic: true - }; + var LANTERN = { + type: "Model", + name: "Floating Lantern", + description: "Spawns Lanterns that float away when grabbed and released!", + modelURL: LANTERN_MODEL_URL, + script: LANTERN_SCRIPT_URL, + dimensions: { + x: 0.2049 * SCALE_FACTOR, + y: 0.4 * SCALE_FACTOR, + z: 0.2049 * SCALE_FACTOR + }, + gravity: { + x: 0, + y: -1, + z: 0 + }, + velocity: { + x: 0, y: .01, z: 0 + }, + linearDampening: 0, + shapeType: 'Box', + lifetime: LIFETIME, + dynamic: true + }; - lanternBox = function() { - _this = this; - }; + lanternBox = function() { + _this = this; + }; - lanternBox.prototype = { + lanternBox.prototype = { - preload: function(entityID) { - this.entityID = entityID; - var props = Entities.getEntityProperties(this.entityID); + preload: function(entityID) { + this.entityID = entityID; + var props = Entities.getEntityProperties(this.entityID); - if (props.owningAvatarID === MyAvatar.sessionUUID) { - this.respawnTimer = Script.setInterval(this.spawnAllLanterns.bind(this), RESPAWN_INTERVAL); - } - }, + if (props.owningAvatarID === MyAvatar.sessionUUID) { + this.respawnTimer = Script.setInterval(this.spawnAllLanterns.bind(this), RESPAWN_INTERVAL); + } + }, - unload: function(entityID) { - if (this.respawnTimer) { - Script.clearInterval(this.respawnTimer); - } - }, + unload: function(entityID) { + if (this.respawnTimer) { + Script.clearInterval(this.respawnTimer); + } + }, - spawnAllLanterns: function() { - var props = Entities.getEntityProperties(this.entityID); - var lanternCount = 0; - var nearbyEntities = Entities.findEntities(props.position, props.dimensions.x * 0.75); + spawnAllLanterns: function() { + var props = Entities.getEntityProperties(this.entityID); + var lanternCount = 0; + var nearbyEntities = Entities.findEntities(props.position, props.dimensions.x * 0.75); - for (var i = 0; i < nearbyEntities.length; i++) { - var name = Entities.getEntityProperties(nearbyEntities[i], ["name"]).name; - if (name === "Floating Lantern") { - lanternCount++; + for (var i = 0; i < nearbyEntities.length; i++) { + var name = Entities.getEntityProperties(nearbyEntities[i], ["name"]).name; + if (name === "Floating Lantern") { + lanternCount++; + } + } + + while (lanternCount++ < MAX_LANTERNS) { + this.spawnLantern(); + } + }, + + spawnLantern: function() { + var boxProps = Entities.getEntityProperties(this.entityID); + + LANTERN.position = boxProps.position; + LANTERN.position.x += Math.random() * .2 - .1; + LANTERN.position.y += Math.random() * .2 + .1; + LANTERN.position.z += Math.random() * .2 - .1; + LANTERN.owningAvatarID = boxProps.owningAvatarID; + + return Entities.addEntity(LANTERN); } - } + }; - while (lanternCount++ < MAX_LANTERNS) { - this.spawnLantern(); - } - }, - - spawnLantern: function() { - var boxProps = Entities.getEntityProperties(this.entityID); - - LANTERN.position = boxProps.position; - LANTERN.position.x += Math.random() * .2 - .1; - LANTERN.position.y += Math.random() * .2 + .1; - LANTERN.position.z += Math.random() * .2 - .1; - LANTERN.owningAvatarID = boxProps.owningAvatarID; - - return Entities.addEntity(LANTERN); - } - }; - - return new lanternBox(); + return new lanternBox(); }); From e6020e0137ae2e9abd0d1b88c235efc62002b498 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Sat, 13 May 2017 00:40:07 +0100 Subject: [PATCH 062/135] added lookup table for tracking result and configs --- plugins/openvr/src/ViveControllerManager.cpp | 59 ++++++++------------ plugins/openvr/src/ViveControllerManager.h | 3 +- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index ccc843b5cc..5e22272dd6 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -56,6 +56,14 @@ static const int CHEST = 3; const char* ViveControllerManager::NAME { "OpenVR" }; +const std::map TRACKING_RESULT_TO_STRING = { + {vr::TrackingResult_Uninitialized, QString("vr::TrackingResult_Uninitialized")}, + {vr::TrackingResult_Calibrating_InProgress, QString("vr::TrackingResult_Calibrating_InProgess")}, + {vr::TrackingResult_Calibrating_OutOfRange, QString("TrackingResult_Calibrating_OutOfRange")}, + {vr::TrackingResult_Running_OK, QString("TrackingResult_Running_Ok")}, + {vr::TrackingResult_Running_OutOfRange, QString("TrackingResult_Running_OutOfRange")} +}; + static glm::mat4 computeOffset(glm::mat4 defaultToReferenceMat, glm::mat4 defaultJointMat, controller::Pose puckPose) { glm::mat4 poseMat = createMatFromQuatAndPos(puckPose.rotation, puckPose.translation); glm::mat4 referenceJointMat = defaultToReferenceMat * defaultJointMat; @@ -68,22 +76,10 @@ static bool sortPucksYPosition(std::pair firstPuck, static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) { QString result; - switch (trackingResult) { - case vr::TrackingResult_Uninitialized: - result = "vr::TrackingResult_Uninitialized"; - break; - case vr::TrackingResult_Calibrating_InProgress: - result = "vr::TrackingResult_Calibrating_InProgess"; - break; - case vr::TrackingResult_Calibrating_OutOfRange: - result = "vr::TrackingResult_Calibrating_OutOfRange"; - break; - case vr::TrackingResult_Running_OK: - result = "vr::TrackingResult_Running_OK"; - break; - case vr::TrackingResult_Running_OutOfRange: - result = "vr::TrackingResult_Running_OutOfRange"; - break; + auto iterator = TRACKING_RESULT_TO_STRING.find(trackingResult); + + if (iterator != TRACKING_RESULT_TO_STRING.end()) { + return iterator->second; } return result; } @@ -166,6 +162,15 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu } } +ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : controller::InputDevice("Vive"), _system(system) { + createPreferences(); + + _configStringMap[Config::Auto] = QString("Auto"); + _configStringMap[Config::Feet] = QString("Feet"); + _configStringMap[Config::FeetAndHips] = QString("FeetAndHips"); + _configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest"); +} + void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _poseStateMap.clear(); _buttonPressedMap.clear(); @@ -626,25 +631,7 @@ void ViveControllerManager::InputDevice::saveSettings() const { } QString ViveControllerManager::InputDevice::configToString(Config config) { - QString currentConfig; - switch (config) { - case Config::Auto: - currentConfig = "Auto"; - break; - - case Config::Feet: - currentConfig = "Feet"; - break; - - case Config::FeetAndHips: - currentConfig = "FeetAndHips"; - break; - - case Config::FeetHipsAndChest: - currentConfig = "FeetHipsAndChest"; - break; - } - return currentConfig; + return _configStringMap[config]; } void ViveControllerManager::InputDevice::setConfigFromString(const QString& value) { @@ -665,7 +652,7 @@ void ViveControllerManager::InputDevice::createPreferences() { static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration"; { - auto getter = [this]()->QString { return configToString(_preferedConfig); }; + auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; }; auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); }; auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter); QStringList list = (QStringList() << "Auto" << "Feet" << "FeetAndHips" << "FeetHipsAndChest"); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index d7ab77ddbc..fa2566da45 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -51,7 +51,7 @@ public: private: class InputDevice : public controller::InputDevice { public: - InputDevice(vr::IVRSystem*& system) : controller::InputDevice("Vive"), _system(system) { createPreferences(); } + InputDevice(vr::IVRSystem*& system); private: // Device functions controller::Input::NamedVector getAvailableInputs() const override; @@ -111,6 +111,7 @@ private: std::vector> _validTrackedObjects; std::map _pucksOffset; std::map _jointToPuckMap; + std::map _configStringMap; PoseData _lastSimPoseData; // perform an action when the InputDevice mutex is acquired. using Locker = std::unique_lock; From 6d7e98274b0a18d8ca62d8cccc54cac29ed4c884 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 12 May 2017 17:07:00 -0700 Subject: [PATCH 063/135] revert debug change --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ded6d6b628..f4699009ac 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -336,7 +336,7 @@ public: // Don't actually crash in debug builds, in case this apparent deadlock is simply from // the developer actively debugging code - #if 0 //def NDEBUG + #ifdef NDEBUG deadlockDetectionCrash(); #endif } From eb3b27849c74e111e16cc831bfade32553c563aa Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 12 May 2017 17:07:47 -0700 Subject: [PATCH 064/135] only keep one copy of Triangles --- libraries/render-utils/src/Model.cpp | 2 - libraries/shared/src/TriangleSet.cpp | 153 ++++++++++++++++----------- libraries/shared/src/TriangleSet.h | 61 +++++++---- 3 files changed, 132 insertions(+), 84 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 2c338674eb..9af1c6802e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -462,8 +462,6 @@ bool Model::convexHullContains(glm::vec3 point) { void Model::calculateTriangleSets() { PROFILE_RANGE(render, __FUNCTION__); - qDebug() << __FUNCTION__ << "url:" << _url; - const FBXGeometry& geometry = getFBXGeometry(); int numberOfMeshes = geometry.meshes.size(); diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index e13d8b126f..ef8f3ab91e 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -13,6 +13,22 @@ #include "TriangleSet.h" +void TriangleSet::insert(const Triangle& t) { + _isBalanced = false; + + _triangles.push_back(t); + _bounds += t.v0; + _bounds += t.v1; + _bounds += t.v2; +} + +void TriangleSet::clear() { + _triangles.clear(); + _bounds.clear(); + _isBalanced = false; + + // delete the octree? +} bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) { @@ -27,53 +43,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& return result; } -void InternalTriangleSet::insert(const Triangle& t) { - _triangles.push_back(t); - - _bounds += t.v0; - _bounds += t.v1; - _bounds += t.v2; -} - -void InternalTriangleSet::clear() { - _triangles.clear(); - _bounds.clear(); -} - -// Determine of the given ray (origin/direction) in model space intersects with any triangles -// in the set. If an intersection occurs, the distance and surface normal will be provided. -bool InternalTriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { - - bool intersectedSomething = false; - float boxDistance = std::numeric_limits::max(); - float bestDistance = std::numeric_limits::max(); - - if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { - if (precision) { - for (const auto& triangle : _triangles) { - float thisTriangleDistance; - trianglesTouched++; - if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { - if (thisTriangleDistance < bestDistance) { - bestDistance = thisTriangleDistance; - intersectedSomething = true; - surfaceNormal = triangle.getNormal(); - distance = bestDistance; - } - } - } - } else { - intersectedSomething = true; - distance = boxDistance; - } - } - - return intersectedSomething; -} - - -bool InternalTriangleSet::convexHullContains(const glm::vec3& point) const { +bool TriangleSet::convexHullContains(const glm::vec3& point) const { if (!_bounds.contains(point)) { return false; } @@ -100,16 +70,76 @@ void TriangleSet::debugDump() { void TriangleSet::balanceOctree() { _triangleOctree.reset(_bounds, 0); - for (const auto& triangle : _triangles) { - _triangleOctree.insert(triangle); + + // insert all the triangles + + for (int i = 0; i < _triangles.size(); i++) { + _triangleOctree.insert(i); } + + // prune the empty cells + _triangleOctree.prune(); + _isBalanced = true; } + + +void InternalTriangleSet::insert(int triangleIndex) { + auto& triangle = _allTriangles[triangleIndex]; + + _triangleIndices.push_back(triangleIndex); + + _bounds += triangle.v0; + _bounds += triangle.v1; + _bounds += triangle.v2; +} + +void InternalTriangleSet::clear() { + _triangleIndices.clear(); + _bounds.clear(); +} + +// Determine of the given ray (origin/direction) in model space intersects with any triangles +// in the set. If an intersection occurs, the distance and surface normal will be provided. +bool InternalTriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { + + bool intersectedSomething = false; + float boxDistance = std::numeric_limits::max(); + float bestDistance = std::numeric_limits::max(); + + if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& triangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + intersectedSomething = true; + surfaceNormal = triangle.getNormal(); + distance = bestDistance; + } + } + } + } else { + intersectedSomething = true; + distance = boxDistance; + } + } + + return intersectedSomething; +} + static const int MAX_DEPTH = 3; // for now static const int MAX_CHILDREN = 8; -TriangleOctreeCell::TriangleOctreeCell(const AABox& bounds, int depth) { +TriangleOctreeCell::TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : + _allTriangles(allTriangles), + _triangleSet(allTriangles) +{ reset(bounds, depth); } @@ -118,8 +148,11 @@ void TriangleOctreeCell::clear() { _population = 0; } +void TriangleOctreeCell::prune() { + // do nothing yet... +} + void TriangleOctreeCell::reset(const AABox& bounds, int depth) { - //qDebug() << __FUNCTION__ << "bounds:" << bounds << "depth:" << depth; clear(); _triangleSet._bounds = bounds; _depth = depth; @@ -128,12 +161,7 @@ void TriangleOctreeCell::reset(const AABox& bounds, int depth) { _children.clear(); for (int child = 0; child < MAX_CHILDREN; child++) { AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child); - /* - qDebug() << __FUNCTION__ << "bounds:" << bounds << "depth:" << depth - << "child:" << child << "childBounds:" << childBounds << "childDepth:" << childDepth; - */ - - _children.push_back(TriangleOctreeCell(childBounds, childDepth)); + _children.push_back(TriangleOctreeCell(_allTriangles, childBounds, childDepth)); } } } @@ -142,7 +170,7 @@ void TriangleOctreeCell::debugDump() { qDebug() << __FUNCTION__; qDebug() << "bounds:" << getBounds(); qDebug() << "depth:" << _depth; - qDebug() << "triangleSet:" << _triangleSet.size() << "at this level"; + //qDebug() << "triangleSet:" << _triangleSet.size() << "at this level"; qDebug() << "population:" << _population << "this level or below"; if (_depth < MAX_DEPTH) { int childNum = 0; @@ -154,20 +182,21 @@ void TriangleOctreeCell::debugDump() { } } -void TriangleOctreeCell::insert(const Triangle& t) { +void TriangleOctreeCell::insert(int triangleIndex) { + const Triangle& triangle = _allTriangles[triangleIndex]; _population++; // if we're not yet at the max depth, then check which child the triangle fits in if (_depth < MAX_DEPTH) { for (auto& child : _children) { - if (child.getBounds().contains(t)) { - child.insert(t); + if (child.getBounds().contains(triangle)) { + child.insert(triangleIndex); return; } } } // either we're at max depth, or the triangle doesn't fit in one of our // children and so we want to just record it here - _triangleSet.insert(t); + _triangleSet.insert(triangleIndex); } bool TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index c8703c6445..d98ba86cb5 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -14,14 +14,15 @@ #include "AABox.h" #include "GeometryUtil.h" + + class InternalTriangleSet { public: - InternalTriangleSet() { } + InternalTriangleSet(std::vector& allTriangles) : + _allTriangles(allTriangles) + { } - void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles - size_t size() const { return _triangles.size(); } - - virtual void insert(const Triangle& t); + virtual void insert(int triangleIndex); void clear(); // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an @@ -30,14 +31,11 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); - // Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to - // determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a - // convex hull, the result of this method is meaningless and undetermined. - bool convexHullContains(const glm::vec3& point) const; const AABox& getBounds() const { return _bounds; } protected: - std::vector _triangles; + std::vector& _allTriangles; + std::vector _triangleIndices; AABox _bounds; friend class TriangleOctreeCell; @@ -45,11 +43,16 @@ protected: class TriangleOctreeCell { public: - TriangleOctreeCell() { } + TriangleOctreeCell(std::vector& allTriangles) : + _allTriangles(allTriangles), + _triangleSet(allTriangles) + { } - void insert(const Triangle& t); + + void insert(int triangleIndex); void reset(const AABox& bounds, int depth = 0); void clear(); + void prune(); // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an // intersection occurs, the distance and surface normal will be provided. @@ -61,8 +64,9 @@ public: void debugDump(); protected: - TriangleOctreeCell(const AABox& bounds, int depth); + TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth); + std::vector& _allTriangles; InternalTriangleSet _triangleSet; std::vector _children; int _depth { 0 }; @@ -71,25 +75,42 @@ protected: friend class TriangleSet; }; -class TriangleSet : public InternalTriangleSet { +class TriangleSet { // pass through public implementation all the features of InternalTriangleSet public: - TriangleSet() { } + TriangleSet() : + _triangleOctree(_triangles) + {} void debugDump(); - virtual void insert(const Triangle& t) { - _isBalanced = false; - InternalTriangleSet::insert(t); - } + void insert(const Triangle& t); bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision); void balanceOctree(); + void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles + size_t size() const { return _triangles.size(); } + void clear(); + + // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an + // intersection occurs, the distance and surface normal will be provided. + // note: this might side-effect internal structures + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + + // Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to + // determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a + // convex hull, the result of this method is meaningless and undetermined. + bool convexHullContains(const glm::vec3& point) const; + const AABox& getBounds() const { return _bounds; } + protected: - bool _isBalanced { false }; + bool _isBalanced{ false }; TriangleOctreeCell _triangleOctree; + std::vector _triangles; + AABox _bounds; }; From 89b6a79f6888e9e797c6dd2a9daa0f0a4f53c875 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 12 May 2017 17:56:45 -0700 Subject: [PATCH 065/135] make sure to exit early if box intersection is already larger than best triangle intersection --- libraries/shared/src/TriangleSet.cpp | 63 ++++++++++++++++++++-------- libraries/shared/src/TriangleSet.h | 3 +- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index ef8f3ab91e..f9dea7bcc6 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -33,13 +33,16 @@ void TriangleSet::clear() { bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) { + // reset our distance to be the max possible, lower level tests will store best distance here + distance = std::numeric_limits::max(); + if (!_isBalanced) { balanceOctree(); } int trianglesTouched = 0; auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched); - qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population; + //qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population; return result; } @@ -77,10 +80,9 @@ void TriangleSet::balanceOctree() { _triangleOctree.insert(i); } - // prune the empty cells - _triangleOctree.prune(); - _isBalanced = true; + + //debugDump(); } @@ -106,10 +108,17 @@ bool InternalTriangleSet::findRayIntersection(const glm::vec3& origin, const glm float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { bool intersectedSomething = false; - float boxDistance = std::numeric_limits::max(); - float bestDistance = std::numeric_limits::max(); + float boxDistance = distance; // std::numeric_limits::max(); + float bestDistance = distance; // std::numeric_limits::max(); if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { + + // if our bounding box intersects at a distance greater than the current known + // best distance, than we can safely not check any of our triangles + if (boxDistance > bestDistance) { + return false; + } + if (precision) { for (const auto& triangleIndex : _triangleIndices) { const auto& triangle = _allTriangles[triangleIndex]; @@ -148,10 +157,6 @@ void TriangleOctreeCell::clear() { _population = 0; } -void TriangleOctreeCell::prune() { - // do nothing yet... -} - void TriangleOctreeCell::reset(const AABox& bounds, int depth) { clear(); _triangleSet._bounds = bounds; @@ -159,10 +164,6 @@ void TriangleOctreeCell::reset(const AABox& bounds, int depth) { if (depth <= MAX_DEPTH) { int childDepth = depth + 1; _children.clear(); - for (int child = 0; child < MAX_CHILDREN; child++) { - AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child); - _children.push_back(TriangleOctreeCell(_allTriangles, childBounds, childDepth)); - } } } @@ -170,8 +171,9 @@ void TriangleOctreeCell::debugDump() { qDebug() << __FUNCTION__; qDebug() << "bounds:" << getBounds(); qDebug() << "depth:" << _depth; - //qDebug() << "triangleSet:" << _triangleSet.size() << "at this level"; qDebug() << "population:" << _population << "this level or below"; + qDebug() << "triangleSet:" << _triangleSet.size() << "in this cell"; + qDebug() << "child cells:" << _children.size(); if (_depth < MAX_DEPTH) { int childNum = 0; for (auto& child : _children) { @@ -187,12 +189,30 @@ void TriangleOctreeCell::insert(int triangleIndex) { _population++; // if we're not yet at the max depth, then check which child the triangle fits in if (_depth < MAX_DEPTH) { + + // check existing children to see if this triangle fits them... for (auto& child : _children) { if (child.getBounds().contains(triangle)) { child.insert(triangleIndex); return; } } + + // if it doesn't exist in an existing child, then check for new possible children + // note: this will actually re-check the bounds of all the existing children as well, hmmm + for (int child = 0; child < MAX_CHILDREN; child++) { + AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child); + if (childBounds.contains(triangle)) { + + // create a child node + auto child = TriangleOctreeCell(_allTriangles, childBounds, _depth + 1); + _children.push_back(child); + + // insert this triangle into it + child.insert(triangleIndex); + return; + } + } } // either we're at max depth, or the triangle doesn't fit in one of our // children and so we want to just record it here @@ -206,16 +226,23 @@ bool TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm: return false; // no triangles below here, so we can't intersect } - float bestLocalDistance = std::numeric_limits::max(); + float bestLocalDistance = distance; // std::numeric_limits::max(); BoxFace bestLocalFace; glm::vec3 bestLocalNormal; bool intersects = false; // if the ray intersects our bounding box, then continue if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) { - bestLocalDistance = std::numeric_limits::max(); - float childDistance = std::numeric_limits::max(); + // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) + // then we know that none of our triangles can represent a better intersection and we can return + if (bestLocalDistance > distance) { + return false; + } + + bestLocalDistance = distance; // std::numeric_limits::max(); + + float childDistance = distance; // std::numeric_limits::max(); BoxFace childFace; glm::vec3 childNormal; diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index d98ba86cb5..c5e4c719cf 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -33,6 +33,8 @@ public: const AABox& getBounds() const { return _bounds; } + size_t size() const { return _triangleIndices.size(); } + protected: std::vector& _allTriangles; std::vector _triangleIndices; @@ -52,7 +54,6 @@ public: void insert(int triangleIndex); void reset(const AABox& bounds, int depth = 0); void clear(); - void prune(); // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an // intersection occurs, the distance and surface normal will be provided. From bdb0414add26b3b792d9c1d16c36a933069acabb Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 12 May 2017 18:04:22 -0700 Subject: [PATCH 066/135] Adding a validation step at runtime for the cached KTX file in order to regenerate them if anything seems wrong --- libraries/gpu/src/gpu/Texture.cpp | 7 ++++++- libraries/gpu/src/gpu/Texture_ktx.cpp | 7 +++++++ libraries/ktx/src/ktx/KTX.cpp | 6 ++++++ libraries/ktx/src/ktx/Reader.cpp | 19 ++++++++++++++++++- libraries/ktx/src/ktx/Writer.cpp | 3 ++- .../src/model-networking/KTXCache.cpp | 2 +- .../src/model-networking/TextureCache.cpp | 4 +++- libraries/networking/src/FileCache.cpp | 11 ++++++++--- libraries/networking/src/FileCache.h | 2 +- libraries/shared/src/shared/Storage.cpp | 4 ++++ 10 files changed, 56 insertions(+), 9 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 0f84d2a3c9..a94a0e1621 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -441,7 +441,10 @@ void Texture::assignStoredMip(uint16 level, storage::StoragePointer& storage) { // THen check that the mem texture passed make sense with its format Size expectedSize = evalStoredMipSize(level, getStoredMipFormat()); auto size = storage->size(); - if (storage->size() <= expectedSize) { + if (storage->size() < expectedSize) { + _storage->assignMipData(level, storage); + _stamp++; + } else if (size == expectedSize) { _storage->assignMipData(level, storage); _stamp++; } else if (size > expectedSize) { @@ -469,6 +472,8 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin Size expectedSize = evalStoredMipFaceSize(level, getStoredMipFormat()); auto size = storage->size(); if (size <= expectedSize) { + _stamp++; + } else if (size == expectedSize) { _storage->assignMipFaceData(level, face, storage); _stamp++; } else if (size > expectedSize) { diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 3fc4e0d432..524fd0a88c 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -542,6 +542,13 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else { return false; } + } else if (header.getGLFormat() == ktx::GLFormat::RG && header.getGLType() == ktx::GLType::UNSIGNED_BYTE && header.getTypeSize() == 1) { + mipFormat = Format::VEC2NU8_XY; + if (header.getGLInternaFormat_Uncompressed() == ktx::GLInternalFormat_Uncompressed::RG8) { + texelFormat = Format::VEC2NU8_XY; + } else { + return false; + } } else if (header.getGLFormat() == ktx::GLFormat::COMPRESSED_FORMAT && header.getGLType() == ktx::GLType::COMPRESSED_TYPE) { if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT) { mipFormat = Format::COLOR_COMPRESSED_SRGB; diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index 38bb91e5c2..ff80695280 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -114,6 +114,9 @@ size_t Header::evalFaceSize(uint32_t level) const { } size_t Header::evalImageSize(uint32_t level) const { auto faceSize = evalFaceSize(level); + if ((faceSize < 4) || ((faceSize & 0x3) != 0)) { + return 0; + } if (numberOfFaces == NUM_CUBEMAPFACES && numberOfArrayElements == 0) { return faceSize; } else { @@ -139,6 +142,9 @@ ImageDescriptors Header::generateImageDescriptors() const { size_t imageOffset = 0; for (uint32_t level = 0; level < numberOfMipmapLevels; ++level) { auto imageSize = static_cast(evalImageSize(level)); + if ((imageSize < 4) || ((imageSize & 0x3) != 0)) { + return ImageDescriptors(); + } if (imageSize == 0) { return ImageDescriptors(); } diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index 440e2f048c..49fc8bac70 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -148,12 +148,24 @@ namespace ktx { size_t imageSize = *reinterpret_cast(currentPtr); currentPtr += sizeof(uint32_t); + auto expectedImageSize = header.evalImageSize(images.size()); + if (imageSize != expectedImageSize) { + break; + } else if ((imageSize < 4) || (imageSize & 0x3)) { + break; + } + + // The image size is the face size, beware! + size_t faceSize = imageSize; + if (numFaces == NUM_CUBEMAPFACES) { + imageSize = NUM_CUBEMAPFACES * faceSize; + } + // If enough data ahead then capture the pointer if ((currentPtr - srcBytes) + imageSize <= (srcSize)) { auto padding = Header::evalPadding(imageSize); if (numFaces == NUM_CUBEMAPFACES) { - size_t faceSize = imageSize / NUM_CUBEMAPFACES; Image::FaceBytes faces(NUM_CUBEMAPFACES); for (uint32_t face = 0; face < NUM_CUBEMAPFACES; face++) { faces[face] = currentPtr; @@ -166,6 +178,7 @@ namespace ktx { currentPtr += imageSize + padding; } } else { + // Stop here break; } } @@ -190,6 +203,10 @@ namespace ktx { // populate image table result->_images = parseImages(result->getHeader(), result->getTexelsDataSize(), result->getTexelsData()); + if (result->_images.size() != result->getHeader().getNumberOfLevels()) { + // Fail if the number of images produced doesn't match the header number of levels + return nullptr; + } return result; } diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index 4226b8fa84..50f63767a0 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -210,7 +210,8 @@ namespace ktx { if (currentDataSize + sizeof(uint32_t) < allocatedImagesDataSize) { uint32_t imageOffset = currentPtr - destBytes; size_t imageSize = srcImages[l]._imageSize; - *(reinterpret_cast (currentPtr)) = (uint32_t) imageSize; + size_t imageFaceSize = srcImages[l]._faceSize; + *(reinterpret_cast (currentPtr)) = (uint32_t)imageFaceSize; // the imageSize written in the ktx is the FACE size currentPtr += sizeof(uint32_t); currentDataSize += sizeof(uint32_t); diff --git a/libraries/model-networking/src/model-networking/KTXCache.cpp b/libraries/model-networking/src/model-networking/KTXCache.cpp index 8ec1c4e41c..e0447af8e6 100644 --- a/libraries/model-networking/src/model-networking/KTXCache.cpp +++ b/libraries/model-networking/src/model-networking/KTXCache.cpp @@ -22,7 +22,7 @@ KTXCache::KTXCache(const std::string& dir, const std::string& ext) : } KTXFilePointer KTXCache::writeFile(const char* data, Metadata&& metadata) { - FilePointer file = FileCache::writeFile(data, std::move(metadata)); + FilePointer file = FileCache::writeFile(data, std::move(metadata), true); return std::static_pointer_cast(file); } diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 9653cde7d8..47fc62a9b5 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -792,6 +792,8 @@ void ImageReader::read() { texture = gpu::Texture::unserialize(ktxFile->getFilepath()); if (texture) { texture = textureCache->cacheTextureByHash(hash, texture); + } else { + qCWarning(modelnetworking) << "Invalid cached KTX " << _url << " under hash " << hash.c_str() << ", recreating..."; } } } @@ -835,7 +837,7 @@ void ImageReader::read() { const char* data = reinterpret_cast(memKtx->_storage->data()); size_t length = memKtx->_storage->size(); auto& ktxCache = textureCache->_ktxCache; - networkTexture->_file = ktxCache.writeFile(data, KTXCache::Metadata(hash, length)); + networkTexture->_file = ktxCache.writeFile(data, KTXCache::Metadata(hash, length)); // if (!networkTexture->_file) { qCWarning(modelnetworking) << _url << "file cache failed"; } else { diff --git a/libraries/networking/src/FileCache.cpp b/libraries/networking/src/FileCache.cpp index 43055e7ed6..8f3509d8f3 100644 --- a/libraries/networking/src/FileCache.cpp +++ b/libraries/networking/src/FileCache.cpp @@ -97,7 +97,7 @@ FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath) return file; } -FilePointer FileCache::writeFile(const char* data, File::Metadata&& metadata) { +FilePointer FileCache::writeFile(const char* data, File::Metadata&& metadata, bool overwrite) { assert(_initialized); std::string filepath = getFilepath(metadata.key); @@ -107,8 +107,13 @@ FilePointer FileCache::writeFile(const char* data, File::Metadata&& metadata) { // if file already exists, return it FilePointer file = getFile(metadata.key); if (file) { - qCWarning(file_cache, "[%s] Attempted to overwrite %s", _dirname.c_str(), metadata.key.c_str()); - return file; + if (!overwrite) { + qCWarning(file_cache, "[%s] Attempted to overwrite %s", _dirname.c_str(), metadata.key.c_str()); + return file; + } else { + qCWarning(file_cache, "[%s] Overwriting %s", _dirname.c_str(), metadata.key.c_str()); + file.reset(); + } } QSaveFile saveFile(QString::fromStdString(filepath)); diff --git a/libraries/networking/src/FileCache.h b/libraries/networking/src/FileCache.h index f77db555bc..908ddcd285 100644 --- a/libraries/networking/src/FileCache.h +++ b/libraries/networking/src/FileCache.h @@ -80,7 +80,7 @@ protected: /// must be called after construction to create the cache on the fs and restore persisted files void initialize(); - FilePointer writeFile(const char* data, Metadata&& metadata); + FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false); FilePointer getFile(const Key& key); /// create a file diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index f6585e6ecb..7cff876aa0 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -21,6 +21,10 @@ ViewStorage::ViewStorage(const storage::StoragePointer& owner, size_t size, cons StoragePointer Storage::createView(size_t viewSize, size_t offset) const { auto selfSize = size(); + if ((viewSize < 4) || ((viewSize & 0x3) != 0)) { + throw std::runtime_error("Invalid mapping range"); + } + if (0 == viewSize) { viewSize = selfSize; } From 5bc8e098654bb0cbaa7897c02067278f38af4652 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 12 May 2017 18:10:48 -0700 Subject: [PATCH 067/135] Fixing the test... --- libraries/gpu/src/gpu/Texture.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index a94a0e1621..b027c25907 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -471,7 +471,8 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin // THen check that the mem texture passed make sense with its format Size expectedSize = evalStoredMipFaceSize(level, getStoredMipFormat()); auto size = storage->size(); - if (size <= expectedSize) { + if (size < expectedSize) { + _storage->assignMipFaceData(level, face, storage); _stamp++; } else if (size == expectedSize) { _storage->assignMipFaceData(level, face, storage); From d734358290cd4168591f1f4ac53d1471cb3b9e91 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 12 May 2017 18:15:00 -0700 Subject: [PATCH 068/135] Adding a comment for debug sake --- libraries/gpu/src/gpu/Texture.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index b027c25907..a545be9088 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -441,6 +441,7 @@ void Texture::assignStoredMip(uint16 level, storage::StoragePointer& storage) { // THen check that the mem texture passed make sense with its format Size expectedSize = evalStoredMipSize(level, getStoredMipFormat()); auto size = storage->size(); + // NOTE: doing the same thing in all the next block but beeing able to breakpoint with more accuracy if (storage->size() < expectedSize) { _storage->assignMipData(level, storage); _stamp++; @@ -471,6 +472,7 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin // THen check that the mem texture passed make sense with its format Size expectedSize = evalStoredMipFaceSize(level, getStoredMipFormat()); auto size = storage->size(); + // NOTE: doing the same thing in all the next block but beeing able to breakpoint with more accuracy if (size < expectedSize) { _storage->assignMipFaceData(level, face, storage); _stamp++; From f35b0297fa991ed2794500940aa53b69dbdda58f Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 12 May 2017 18:25:07 -0700 Subject: [PATCH 069/135] Replace the alignment test by a nice function --- libraries/ktx/src/ktx/KTX.cpp | 7 +++++-- libraries/ktx/src/ktx/KTX.h | 1 + libraries/ktx/src/ktx/Reader.cpp | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index ff80695280..b43d015d65 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -22,6 +22,9 @@ uint32_t Header::evalPadding(size_t byteSize) { return (uint32_t) (3 - (byteSize + 3) % PACKING_SIZE);// padding ? PACKING_SIZE - padding : 0); } +bool Header::checkAlignment(size_t byteSize) { + return ((byteSize & 0x3) == 0); +} const Header::Identifier ktx::Header::IDENTIFIER {{ 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A @@ -114,7 +117,7 @@ size_t Header::evalFaceSize(uint32_t level) const { } size_t Header::evalImageSize(uint32_t level) const { auto faceSize = evalFaceSize(level); - if ((faceSize < 4) || ((faceSize & 0x3) != 0)) { + if (!checkAlignment(faceSize)) { return 0; } if (numberOfFaces == NUM_CUBEMAPFACES && numberOfArrayElements == 0) { @@ -142,7 +145,7 @@ ImageDescriptors Header::generateImageDescriptors() const { size_t imageOffset = 0; for (uint32_t level = 0; level < numberOfMipmapLevels; ++level) { auto imageSize = static_cast(evalImageSize(level)); - if ((imageSize < 4) || ((imageSize & 0x3) != 0)) { + if (!checkAlignment(imageSize)) { return ImageDescriptors(); } if (imageSize == 0) { diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index e8fa019a07..656cf93f34 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -309,6 +309,7 @@ namespace ktx { static const uint32_t REVERSE_ENDIAN_TEST = 0x01020304; static uint32_t evalPadding(size_t byteSize); + static bool checkAlignment(size_t byteSize); Header(); diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index 49fc8bac70..e69ce53551 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -151,7 +151,7 @@ namespace ktx { auto expectedImageSize = header.evalImageSize(images.size()); if (imageSize != expectedImageSize) { break; - } else if ((imageSize < 4) || (imageSize & 0x3)) { + } else if (!Header::checkAlignment(imageSize)) { break; } From c4e8885842ef82793227f1716887b20c41c28856 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 12 May 2017 18:27:58 -0700 Subject: [PATCH 070/135] not testing in storage for alignment --- libraries/shared/src/shared/Storage.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index 7cff876aa0..f6585e6ecb 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -21,10 +21,6 @@ ViewStorage::ViewStorage(const storage::StoragePointer& owner, size_t size, cons StoragePointer Storage::createView(size_t viewSize, size_t offset) const { auto selfSize = size(); - if ((viewSize < 4) || ((viewSize & 0x3) != 0)) { - throw std::runtime_error("Invalid mapping range"); - } - if (0 == viewSize) { viewSize = selfSize; } From f1bd06cfa09f5065d4ea2d820242a9f068c67ce7 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 12 May 2017 18:33:28 -0700 Subject: [PATCH 071/135] cleanup some unneeded class --- libraries/shared/src/TriangleSet.cpp | 42 +++++++++++----------------- libraries/shared/src/TriangleSet.h | 40 ++++++-------------------- 2 files changed, 24 insertions(+), 58 deletions(-) diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index f9dea7bcc6..99b2e2fc79 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -42,7 +42,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& int trianglesTouched = 0; auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched); - //qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population; + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population; return result; } @@ -82,29 +82,13 @@ void TriangleSet::balanceOctree() { _isBalanced = true; - //debugDump(); + debugDump(); } - -void InternalTriangleSet::insert(int triangleIndex) { - auto& triangle = _allTriangles[triangleIndex]; - - _triangleIndices.push_back(triangleIndex); - - _bounds += triangle.v0; - _bounds += triangle.v1; - _bounds += triangle.v2; -} - -void InternalTriangleSet::clear() { - _triangleIndices.clear(); - _bounds.clear(); -} - // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. -bool InternalTriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, +bool TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { bool intersectedSomething = false; @@ -146,20 +130,20 @@ static const int MAX_DEPTH = 3; // for now static const int MAX_CHILDREN = 8; TriangleOctreeCell::TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : - _allTriangles(allTriangles), - _triangleSet(allTriangles) + _allTriangles(allTriangles) { reset(bounds, depth); } void TriangleOctreeCell::clear() { - _triangleSet.clear(); _population = 0; + _triangleIndices.clear(); + _bounds.clear(); } void TriangleOctreeCell::reset(const AABox& bounds, int depth) { clear(); - _triangleSet._bounds = bounds; + _bounds = bounds; _depth = depth; if (depth <= MAX_DEPTH) { int childDepth = depth + 1; @@ -172,7 +156,7 @@ void TriangleOctreeCell::debugDump() { qDebug() << "bounds:" << getBounds(); qDebug() << "depth:" << _depth; qDebug() << "population:" << _population << "this level or below"; - qDebug() << "triangleSet:" << _triangleSet.size() << "in this cell"; + qDebug() << "triangleIndices:" << _triangleIndices.size() << "in this cell"; qDebug() << "child cells:" << _children.size(); if (_depth < MAX_DEPTH) { int childNum = 0; @@ -216,7 +200,13 @@ void TriangleOctreeCell::insert(int triangleIndex) { } // either we're at max depth, or the triangle doesn't fit in one of our // children and so we want to just record it here - _triangleSet.insert(triangleIndex); + _triangleIndices.push_back(triangleIndex); + + /* + _bounds += triangle.v0; + _bounds += triangle.v1; + _bounds += triangle.v2; + */ } bool TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -263,7 +253,7 @@ bool TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm: } } // also check our local triangle set - if (_triangleSet.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; bestLocalFace = childFace; diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index c5e4c719cf..65033976cf 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -16,38 +16,10 @@ -class InternalTriangleSet { -public: - InternalTriangleSet(std::vector& allTriangles) : - _allTriangles(allTriangles) - { } - - virtual void insert(int triangleIndex); - void clear(); - - // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an - // intersection occurs, the distance and surface normal will be provided. - // note: this might side-effect internal structures - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); - - const AABox& getBounds() const { return _bounds; } - - size_t size() const { return _triangleIndices.size(); } - -protected: - std::vector& _allTriangles; - std::vector _triangleIndices; - AABox _bounds; - - friend class TriangleOctreeCell; -}; - class TriangleOctreeCell { public: TriangleOctreeCell(std::vector& allTriangles) : - _allTriangles(allTriangles), - _triangleSet(allTriangles) + _allTriangles(allTriangles) { } @@ -60,24 +32,28 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); - const AABox& getBounds() const { return _triangleSet.getBounds(); } + const AABox& getBounds() const { return _bounds; } void debugDump(); protected: TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth); + // checks our internal list of triangles + bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + std::vector& _allTriangles; - InternalTriangleSet _triangleSet; std::vector _children; int _depth { 0 }; int _population { 0 }; + AABox _bounds; + std::vector _triangleIndices; friend class TriangleSet; }; class TriangleSet { - // pass through public implementation all the features of InternalTriangleSet public: TriangleSet() : _triangleOctree(_triangles) From 4af3c760e6c304fbabce613ab2a4a80a6f2eaf09 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 12 May 2017 18:57:33 -0700 Subject: [PATCH 072/135] more cleanup --- libraries/shared/src/TriangleSet.cpp | 34 +++++++------ libraries/shared/src/TriangleSet.h | 74 +++++++++++++--------------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index 99b2e2fc79..2b6e12f005 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -27,7 +27,7 @@ void TriangleSet::clear() { _bounds.clear(); _isBalanced = false; - // delete the octree? + _triangleOctree.clear(); } bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -42,7 +42,12 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& int trianglesTouched = 0; auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched); - qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population; + + #if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population; + } + #endif return result; } @@ -82,13 +87,15 @@ void TriangleSet::balanceOctree() { _isBalanced = true; + #if WANT_DEBUGGING debugDump(); + #endif } // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. -bool TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, +bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { bool intersectedSomething = false; @@ -126,22 +133,23 @@ bool TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, co return intersectedSomething; } -static const int MAX_DEPTH = 3; // for now +static const int MAX_DEPTH = 4; // for now static const int MAX_CHILDREN = 8; -TriangleOctreeCell::TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : +TriangleSet::TriangleOctreeCell::TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : _allTriangles(allTriangles) { reset(bounds, depth); } -void TriangleOctreeCell::clear() { +void TriangleSet::TriangleOctreeCell::clear() { _population = 0; _triangleIndices.clear(); _bounds.clear(); + _children.clear(); } -void TriangleOctreeCell::reset(const AABox& bounds, int depth) { +void TriangleSet::TriangleOctreeCell::reset(const AABox& bounds, int depth) { clear(); _bounds = bounds; _depth = depth; @@ -151,7 +159,7 @@ void TriangleOctreeCell::reset(const AABox& bounds, int depth) { } } -void TriangleOctreeCell::debugDump() { +void TriangleSet::TriangleOctreeCell::debugDump() { qDebug() << __FUNCTION__; qDebug() << "bounds:" << getBounds(); qDebug() << "depth:" << _depth; @@ -168,7 +176,7 @@ void TriangleOctreeCell::debugDump() { } } -void TriangleOctreeCell::insert(int triangleIndex) { +void TriangleSet::TriangleOctreeCell::insert(int triangleIndex) { const Triangle& triangle = _allTriangles[triangleIndex]; _population++; // if we're not yet at the max depth, then check which child the triangle fits in @@ -201,15 +209,9 @@ void TriangleOctreeCell::insert(int triangleIndex) { // either we're at max depth, or the triangle doesn't fit in one of our // children and so we want to just record it here _triangleIndices.push_back(triangleIndex); - - /* - _bounds += triangle.v0; - _bounds += triangle.v1; - _bounds += triangle.v2; - */ } -bool TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, +bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { if (_population < 1) { diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 65033976cf..bedb038c58 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -14,46 +14,42 @@ #include "AABox.h" #include "GeometryUtil.h" - - -class TriangleOctreeCell { -public: - TriangleOctreeCell(std::vector& allTriangles) : - _allTriangles(allTriangles) - { } - - - void insert(int triangleIndex); - void reset(const AABox& bounds, int depth = 0); - void clear(); - - // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an - // intersection occurs, the distance and surface normal will be provided. - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); - - const AABox& getBounds() const { return _bounds; } - - void debugDump(); - -protected: - TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth); - - // checks our internal list of triangles - bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); - - std::vector& _allTriangles; - std::vector _children; - int _depth { 0 }; - int _population { 0 }; - AABox _bounds; - std::vector _triangleIndices; - - friend class TriangleSet; -}; - class TriangleSet { + + class TriangleOctreeCell { + public: + TriangleOctreeCell(std::vector& allTriangles) : + _allTriangles(allTriangles) + { } + + void insert(int triangleIndex); + void reset(const AABox& bounds, int depth = 0); + void clear(); + + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + + const AABox& getBounds() const { return _bounds; } + + void debugDump(); + + protected: + TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth); + + // checks our internal list of triangles + bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + + std::vector& _allTriangles; + std::vector _children; + int _depth{ 0 }; + int _population{ 0 }; + AABox _bounds; + std::vector _triangleIndices; + + friend class TriangleSet; + }; + public: TriangleSet() : _triangleOctree(_triangles) From dcb70aa50467dbdf6264cb00f3f79dd7e8da0882 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 12 May 2017 19:03:08 -0700 Subject: [PATCH 073/135] more cleanup --- libraries/shared/src/TriangleSet.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index 2b6e12f005..cd3f829d93 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -99,8 +99,8 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { bool intersectedSomething = false; - float boxDistance = distance; // std::numeric_limits::max(); - float bestDistance = distance; // std::numeric_limits::max(); + float boxDistance = distance; + float bestDistance = distance; if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { @@ -218,7 +218,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi return false; // no triangles below here, so we can't intersect } - float bestLocalDistance = distance; // std::numeric_limits::max(); + float bestLocalDistance = distance; BoxFace bestLocalFace; glm::vec3 bestLocalNormal; bool intersects = false; @@ -232,9 +232,9 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi return false; } - bestLocalDistance = distance; // std::numeric_limits::max(); + bestLocalDistance = distance; - float childDistance = distance; // std::numeric_limits::max(); + float childDistance = distance; BoxFace childFace; glm::vec3 childNormal; From b38be7e561f5a2b30a8179cab40fc8626ac74b65 Mon Sep 17 00:00:00 2001 From: Sam Cake Date: Fri, 12 May 2017 20:20:03 -0700 Subject: [PATCH 074/135] warning -1 --- libraries/ktx/src/ktx/Reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ktx/src/ktx/Reader.cpp b/libraries/ktx/src/ktx/Reader.cpp index e69ce53551..1b63af5262 100644 --- a/libraries/ktx/src/ktx/Reader.cpp +++ b/libraries/ktx/src/ktx/Reader.cpp @@ -148,7 +148,7 @@ namespace ktx { size_t imageSize = *reinterpret_cast(currentPtr); currentPtr += sizeof(uint32_t); - auto expectedImageSize = header.evalImageSize(images.size()); + auto expectedImageSize = header.evalImageSize((uint32_t) images.size()); if (imageSize != expectedImageSize) { break; } else if (!Header::checkAlignment(imageSize)) { From 20c27fc1338593498cb851498ddb38d004337589 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Sat, 13 May 2017 14:20:28 +0200 Subject: [PATCH 075/135] Fix crash on save Map data settings from scripts --- libraries/shared/src/SettingHelpers.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp index 9e2d15fcd0..cbda4e4096 100644 --- a/libraries/shared/src/SettingHelpers.cpp +++ b/libraries/shared/src/SettingHelpers.cpp @@ -126,7 +126,16 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { } switch (variantType) { - case QVariant::Map: + case QVariant::Map: { + auto varmap = variant.toMap(); + for (auto mapit = varmap.cbegin(); mapit != varmap.cend(); ++mapit) { + auto& mapkey = mapit.key(); + auto& mapvariant = mapit.value(); + object.insert(key + "/" + mapkey, QJsonValue::fromVariant(mapvariant)); + } + break; + } + case QVariant::List: case QVariant::Hash: { qCritical() << "Unsupported variant type" << variant.typeName(); From da404ce2ce573358344e5efb253e8edcda1659ce Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 13 May 2017 09:29:32 -0700 Subject: [PATCH 076/135] fix warning --- libraries/shared/src/TriangleSet.cpp | 8 ++------ libraries/shared/src/TriangleSet.h | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index cd3f829d93..d880b43a20 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -81,7 +81,7 @@ void TriangleSet::balanceOctree() { // insert all the triangles - for (int i = 0; i < _triangles.size(); i++) { + for (size_t i = 0; i < _triangles.size(); i++) { _triangleOctree.insert(i); } @@ -153,10 +153,6 @@ void TriangleSet::TriangleOctreeCell::reset(const AABox& bounds, int depth) { clear(); _bounds = bounds; _depth = depth; - if (depth <= MAX_DEPTH) { - int childDepth = depth + 1; - _children.clear(); - } } void TriangleSet::TriangleOctreeCell::debugDump() { @@ -176,7 +172,7 @@ void TriangleSet::TriangleOctreeCell::debugDump() { } } -void TriangleSet::TriangleOctreeCell::insert(int triangleIndex) { +void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { const Triangle& triangle = _allTriangles[triangleIndex]; _population++; // if we're not yet at the max depth, then check which child the triangle fits in diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index bedb038c58..18494d270e 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -22,7 +22,7 @@ class TriangleSet { _allTriangles(allTriangles) { } - void insert(int triangleIndex); + void insert(size_t triangleIndex); void reset(const AABox& bounds, int depth = 0); void clear(); From 5065c7c5c45712f058e5c7c3c762eab7f6bb9a1c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 13 May 2017 10:17:59 -0700 Subject: [PATCH 077/135] more warning fixes --- libraries/shared/src/TriangleSet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 18494d270e..3b39b935af 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -45,7 +45,7 @@ class TriangleSet { int _depth{ 0 }; int _population{ 0 }; AABox _bounds; - std::vector _triangleIndices; + std::vector _triangleIndices; friend class TriangleSet; }; From cc10fc81b79cdd7139e683b3967dd844f30f2e7c Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Sat, 13 May 2017 20:57:04 +0200 Subject: [PATCH 078/135] Fix crash in sit script --- interface/src/avatar/MyAvatar.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fd547e39e0..3da9b8a214 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -291,6 +291,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { } void MyAvatar::resetSensorsAndBody() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "resetSensorsAndBody"); + return; + } + qApp->getActiveDisplayPlugin()->resetSensors(); reset(true, false, true); } From 149750fe216b96f067287a1d908e4010e7362fe6 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sun, 14 May 2017 08:36:54 -0700 Subject: [PATCH 079/135] make the octree sparse --- libraries/shared/src/TriangleSet.cpp | 40 +++++++++++++--------------- libraries/shared/src/TriangleSet.h | 2 +- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index d880b43a20..aa21aa5cc0 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -45,7 +45,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& #if WANT_DEBUGGING if (precision) { - qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population; + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size(); } #endif return result; @@ -80,7 +80,6 @@ void TriangleSet::balanceOctree() { _triangleOctree.reset(_bounds, 0); // insert all the triangles - for (size_t i = 0; i < _triangles.size(); i++) { _triangleOctree.insert(i); } @@ -159,14 +158,15 @@ void TriangleSet::TriangleOctreeCell::debugDump() { qDebug() << __FUNCTION__; qDebug() << "bounds:" << getBounds(); qDebug() << "depth:" << _depth; - qDebug() << "population:" << _population << "this level or below"; - qDebug() << "triangleIndices:" << _triangleIndices.size() << "in this cell"; + qDebug() << "population:" << _population << "this level or below" + << " ---- triangleIndices:" << _triangleIndices.size() << "in this cell"; + qDebug() << "child cells:" << _children.size(); if (_depth < MAX_DEPTH) { int childNum = 0; for (auto& child : _children) { qDebug() << "child:" << childNum; - child.debugDump(); + child.second.debugDump(); childNum++; } } @@ -178,26 +178,21 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { // if we're not yet at the max depth, then check which child the triangle fits in if (_depth < MAX_DEPTH) { - // check existing children to see if this triangle fits them... - for (auto& child : _children) { - if (child.getBounds().contains(triangle)) { - child.insert(triangleIndex); - return; - } - } - - // if it doesn't exist in an existing child, then check for new possible children - // note: this will actually re-check the bounds of all the existing children as well, hmmm for (int child = 0; child < MAX_CHILDREN; child++) { AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child); + + + // if the child AABox would contain the triangle... if (childBounds.contains(triangle)) { + // if the child cell doesn't yet exist, create it... + if (_children.find((AABox::OctreeChild)child) == _children.end()) { + _children.insert( + std::pair + ((AABox::OctreeChild)child, TriangleOctreeCell(_allTriangles, childBounds, _depth + 1))); + } - // create a child node - auto child = TriangleOctreeCell(_allTriangles, childBounds, _depth + 1); - _children.push_back(child); - - // insert this triangle into it - child.insert(triangleIndex); + // insert the triangleIndex in the child cell + _children.at((AABox::OctreeChild)child).insert(triangleIndex); return; } } @@ -224,6 +219,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) // then we know that none of our triangles can represent a better intersection and we can return + if (bestLocalDistance > distance) { return false; } @@ -240,7 +236,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi // check each child, if there's an intersection, it will return some distance that we need // to compare against the other results, because there might be multiple intersections and // we will always choose the best (shortest) intersection - if (child.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; bestLocalFace = childFace; diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 3b39b935af..6cedc4da7e 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -41,7 +41,7 @@ class TriangleSet { float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); std::vector& _allTriangles; - std::vector _children; + std::map _children; int _depth{ 0 }; int _population{ 0 }; AABox _bounds; From 9c81a89ac3e43b0adc671e3233593c233b2ddcb1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sun, 14 May 2017 08:37:26 -0700 Subject: [PATCH 080/135] add test script --- .../tests/performance/rayPickPerformance.js | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 scripts/developer/tests/performance/rayPickPerformance.js diff --git a/scripts/developer/tests/performance/rayPickPerformance.js b/scripts/developer/tests/performance/rayPickPerformance.js new file mode 100644 index 0000000000..b4faf4c1be --- /dev/null +++ b/scripts/developer/tests/performance/rayPickPerformance.js @@ -0,0 +1,131 @@ +// +// rayPickingPerformance.js +// examples +// +// Created by Brad Hefta-Gaub on 5/13/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + + +var MIN_RANGE = -3; +var MAX_RANGE = 3; +var RANGE_DELTA = 0.5; +var OUTER_LOOPS = 10; + +// NOTE: These expected results depend completely on the model, and the range settings above +var EXPECTED_TESTS = 1385 * OUTER_LOOPS; +var EXPECTED_INTERSECTIONS = 1286 * OUTER_LOOPS; + + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation()))); +var model_url = "http://hifi-content.s3.amazonaws.com/caitlyn/production/Scansite/buddhaReduced.fbx"; + +var rayPickOverlays = Array(); + +var modelEntity = Entities.addEntity({ + type: "Model", + modelURL: model_url, + dimensions: { + x: 0.671, + y: 1.21, + z: 0.938 + }, + position: center +}); + +function rayCastTest() { + var tests = 0; + var intersections = 0; + + var testStart = Date.now(); + for (var t = 0; t < OUTER_LOOPS; t++) { + print("beginning loop:" + t); + for (var x = MIN_RANGE; x < MAX_RANGE; x += RANGE_DELTA) { + for (var y = MIN_RANGE; y < MAX_RANGE; y += RANGE_DELTA) { + for (var z = MIN_RANGE; z < MAX_RANGE; z += RANGE_DELTA) { + if ((x <= -2 || x >= 2) || + (y <= -2 || y >= 2) || + (z <= -2 || z >= 2)) { + + tests++; + + var origin = { x: center.x + x, + y: center.y + y, + z: center.z + z }; + var direction = Vec3.subtract(center, origin); + + var pickRay = { + origin: origin, + direction: direction + }; + + var pickResults = Entities.findRayIntersection(pickRay, true); + + var color; + var visible; + + if (pickResults.intersects && pickResults.entityID == modelEntity) { + intersections++; + color = { + red: 0, + green: 255, + blue: 0 + }; + visible = false; + + } else { + /* + print("NO INTERSECTION?"); + Vec3.print("origin:", origin); + Vec3.print("direction:", direction); + */ + + color = { + red: 255, + green: 0, + blue: 0 + }; + visible = true; + } + + var overlayID = Overlays.addOverlay("line3d", { + color: color, + alpha: 1, + visible: visible, + lineWidth: 2, + start: origin, + end: Vec3.sum(origin,Vec3.multiply(5,direction)) + }); + + rayPickOverlays.push(overlayID); + + } + } + } + } + print("ending loop:" + t); + } + var testEnd = Date.now(); + var testElapsed = testEnd - testStart; + + + print("EXPECTED tests:" + EXPECTED_TESTS + " intersections:" + EXPECTED_INTERSECTIONS); + print("ACTUAL tests:" + tests + " intersections:" + intersections); + print("ELAPSED TIME:" + testElapsed + " ms"); + +} + +function cleanup() { + Entities.deleteEntity(modelEntity); + rayPickOverlays.forEach(function(item){ + Overlays.deleteOverlay(item); + }); +} + +Script.scriptEnding.connect(cleanup); + +rayCastTest(); // run ray cast test immediately \ No newline at end of file From 67fbb15faab1e5547ae8899f1cce067760771005 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 10 May 2017 18:11:34 -0700 Subject: [PATCH 081/135] WIP: initial implementation of flexCoefficients --- .../resources/avatar/avatar-animation.json | 21 ++++--- .../animation/src/AnimInverseKinematics.cpp | 61 ++++++++++++++----- .../animation/src/AnimInverseKinematics.h | 19 +++--- libraries/animation/src/AnimNodeLoader.cpp | 13 +++- libraries/animation/src/IKTarget.cpp | 16 +++++ libraries/animation/src/IKTarget.h | 6 +- libraries/animation/src/Rig.cpp | 10 +-- 7 files changed, 107 insertions(+), 39 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index eb8403634a..a402358478 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -56,43 +56,50 @@ "jointName": "Hips", "positionVar": "hipsPosition", "rotationVar": "hipsRotation", - "typeVar": "hipsType" + "typeVar": "hipsType", + "flexCoefficients": [1] }, { "jointName": "RightHand", "positionVar": "rightHandPosition", "rotationVar": "rightHandRotation", - "typeVar": "rightHandType" + "typeVar": "rightHandType", + "flexCoefficients": [1, 0.5, 0.5, 0.5, 0.25, 0.1, 0.1, 0.1, 0.1] }, { "jointName": "LeftHand", "positionVar": "leftHandPosition", "rotationVar": "leftHandRotation", - "typeVar": "leftHandType" + "typeVar": "leftHandType", + "flexCoefficients": [1, 0.5, 0.5, 0.5, 0.25, 0.1, 0.1, 0.1, 0.1] }, { "jointName": "RightFoot", "positionVar": "rightFootPosition", "rotationVar": "rightFootRotation", - "typeVar": "rightFootType" + "typeVar": "rightFootType", + "flexCoefficients": [1, 0.45, 0.45] }, { "jointName": "LeftFoot", "positionVar": "leftFootPosition", "rotationVar": "leftFootRotation", - "typeVar": "leftFootType" + "typeVar": "leftFootType", + "flexCoefficients": [1, 0.45, 0.45] }, { "jointName": "Spine2", "positionVar": "spine2Position", "rotationVar": "spine2Rotation", - "typeVar": "spine2Type" + "typeVar": "spine2Type", + "flexCoefficients": [0.45, 0.45] }, { "jointName": "Head", "positionVar": "headPosition", "rotationVar": "headRotation", - "typeVar": "headType" + "typeVar": "headType", + "flexCoefficients": [1, 0.5, 0.25, 0.25, 0.25] } ] }, diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 5e6927afcb..2ab4282177 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -21,6 +21,35 @@ #include "SwingTwistConstraint.h" #include "AnimationLogging.h" +AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, + const QString& typeVarIn, const std::vector& flexCoefficientsIn) : + positionVar(positionVarIn), + rotationVar(rotationVarIn), + typeVar(typeVarIn), + jointName(jointNameIn), + numFlexCoefficients(flexCoefficientsIn.size()), + jointIndex(-1) +{ + numFlexCoefficients = std::min(numFlexCoefficients, MAX_FLEX_COEFFICIENTS); + for (size_t i = 0; i < numFlexCoefficients; i++) { + flexCoefficients[i] = flexCoefficientsIn[i]; + } +} + +AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) : + positionVar(orig.positionVar), + rotationVar(orig.rotationVar), + typeVar(orig.typeVar), + jointName(orig.jointName), + numFlexCoefficients(orig.numFlexCoefficients), + jointIndex(orig.jointIndex) +{ + numFlexCoefficients = std::min(numFlexCoefficients, MAX_FLEX_COEFFICIENTS); + for (size_t i = 0; i < numFlexCoefficients; i++) { + flexCoefficients[i] = orig.flexCoefficients[i]; + } +} + AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimNode::Type::InverseKinematics, id) { } @@ -60,26 +89,22 @@ void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) con } } -void AnimInverseKinematics::setTargetVars( - const QString& jointName, - const QString& positionVar, - const QString& rotationVar, - const QString& typeVar) { +void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, + const QString& typeVar, const std::vector& flexCoefficients) { + IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, flexCoefficients); + // if there are dups, last one wins. bool found = false; - for (auto& targetVar: _targetVarVec) { - if (targetVar.jointName == jointName) { - // update existing targetVar - targetVar.positionVar = positionVar; - targetVar.rotationVar = rotationVar; - targetVar.typeVar = typeVar; + for (auto& targetVarIter: _targetVarVec) { + if (targetVarIter.jointName == jointName) { + targetVarIter = targetVar; found = true; break; } } if (!found) { // create a new entry - _targetVarVec.push_back(IKTargetVar(jointName, positionVar, rotationVar, typeVar)); + _targetVarVec.push_back(targetVar); } } @@ -110,7 +135,10 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: target.setPose(rotation, translation); target.setIndex(targetVar.jointIndex); + target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients); + targets.push_back(target); + if (targetVar.jointIndex > _maxTargetIndex) { _maxTargetIndex = targetVar.jointIndex; } @@ -271,6 +299,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // cache tip absolute position glm::vec3 tipPosition = absolutePoses[tipIndex].trans(); + int chainDepth = 1; + // descend toward root, pivoting each joint to get tip closer to target position while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) { // compute the two lines that should be aligned @@ -312,9 +342,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe float angle = acosf(cosAngle); const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f; if (angle > MIN_ADJUSTMENT_ANGLE) { - // reduce angle by a fraction (for stability) - const float STABILITY_FRACTION = 0.5f; - angle *= STABILITY_FRACTION; + // reduce angle by a flexCoefficient + angle *= target.getFlexCoefficient(chainDepth); deltaRotation = glm::angleAxis(angle, axis); // The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's @@ -385,6 +414,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe pivotIndex = pivotsParentIndex; pivotsParentIndex = _skeleton->getParentIndex(pivotIndex); + + chainDepth++; } return lowestMovedIndex; } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index f73ed95935..ddbb420012 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -32,7 +32,8 @@ public: void loadPoses(const AnimPoseVec& poses); void computeAbsolutePoses(AnimPoseVec& absolutePoses) const; - void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, const QString& typeVar); + void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, + const QString& typeVar, const std::vector& flexCoefficients); virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override; virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; @@ -77,22 +78,18 @@ protected: AnimInverseKinematics(const AnimInverseKinematics&) = delete; AnimInverseKinematics& operator=(const AnimInverseKinematics&) = delete; + static const size_t MAX_FLEX_COEFFICIENTS = 10; struct IKTargetVar { - IKTargetVar(const QString& jointNameIn, - const QString& positionVarIn, - const QString& rotationVarIn, - const QString& typeVarIn) : - positionVar(positionVarIn), - rotationVar(rotationVarIn), - typeVar(typeVarIn), - jointName(jointNameIn), - jointIndex(-1) - {} + IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, + const QString& typeVarIn, const std::vector& flexCoefficientsIn); + IKTargetVar(const IKTargetVar& orig); QString positionVar; QString rotationVar; QString typeVar; QString jointName; + float flexCoefficients[MAX_FLEX_COEFFICIENTS]; + size_t numFlexCoefficients; int jointIndex; // cached joint index }; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 6bc7342d7f..e4ffaec5b7 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -471,7 +471,18 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS READ_STRING(rotationVar, targetObj, id, jsonUrl, nullptr); READ_OPTIONAL_STRING(typeVar, targetObj); - node->setTargetVars(jointName, positionVar, rotationVar, typeVar); + auto flexCoefficientsValue = targetObj.value("flexCoefficients"); + if (!flexCoefficientsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad or missing flexCoefficients array in \"targets\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + auto flexCoefficientsArray = flexCoefficientsValue.toArray(); + std::vector flexCoefficients; + for (const auto& value : flexCoefficientsArray) { + flexCoefficients.push_back((float)value.toDouble()); + } + + node->setTargetVars(jointName, positionVar, rotationVar, typeVar, flexCoefficients); }; READ_OPTIONAL_STRING(solutionSource, jsonObj); diff --git a/libraries/animation/src/IKTarget.cpp b/libraries/animation/src/IKTarget.cpp index fa4030ca6d..868293fdb8 100644 --- a/libraries/animation/src/IKTarget.cpp +++ b/libraries/animation/src/IKTarget.cpp @@ -14,6 +14,22 @@ void IKTarget::setPose(const glm::quat& rotation, const glm::vec3& translation) _pose.trans() = translation; } +void IKTarget::setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn) { + _numFlexCoefficients = std::min(numFlexCoefficientsIn, MAX_FLEX_COEFFICIENTS); + for (size_t i = 0; i < _numFlexCoefficients; i++) { + _flexCoefficients[i] = flexCoefficientsIn[i]; + } +} + +float IKTarget::getFlexCoefficient(int chainDepth) const { + const float DEFAULT_FLEX_COEFFICIENT = 0.5f; + if (chainDepth >= 0 && chainDepth < _numFlexCoefficients) { + return _flexCoefficients[chainDepth]; + } else { + return DEFAULT_FLEX_COEFFICIENT; + } +} + void IKTarget::setType(int type) { switch (type) { case (int)Type::RotationAndPosition: diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index acb01d9861..63a4b901a6 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -35,15 +35,19 @@ public: void setPose(const glm::quat& rotation, const glm::vec3& translation); void setIndex(int index) { _index = index; } void setType(int); + void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn); + float getFlexCoefficient(int chainDepth) const; // HACK: give HmdHead targets more "weight" during IK algorithm float getWeight() const { return _type == Type::HmdHead ? HACK_HMD_TARGET_WEIGHT : 1.0f; } + static const size_t MAX_FLEX_COEFFICIENTS = 10; private: AnimPose _pose; int _index{-1}; Type _type{Type::RotationAndPosition}; - + float _flexCoefficients[MAX_FLEX_COEFFICIENTS]; + size_t _numFlexCoefficients; }; #endif // hifi_IKTarget_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index cb5aebe930..f5a9008ce1 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1400,22 +1400,24 @@ void Rig::computeAvatarBoundingCapsule( AnimInverseKinematics ikNode("boundingShape"); ikNode.setSkeleton(_animSkeleton); + + // AJT: FIX ME!!!!! ensure that empty weights vector does something reasonable.... ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation", - "leftHandType"); + "leftHandType", {}); ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation", - "rightHandType"); + "rightHandType", {}); ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation", - "leftFootType"); + "leftFootType", {}); ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation", - "rightFootType"); + "rightFootType", {}); AnimPose geometryToRig = _modelOffset * _geometryOffset; From 1e276d113cf792305df60607479a07307fd3daab Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 12 May 2017 11:45:22 -0700 Subject: [PATCH 082/135] Added support for weights per target --- .../resources/avatar/avatar-animation.json | 20 ++++++++++++++++--- .../animation/src/AnimInverseKinematics.cpp | 14 +++++++++---- .../animation/src/AnimInverseKinematics.h | 6 ++++-- libraries/animation/src/AnimNodeLoader.cpp | 11 +++++++++- libraries/animation/src/IKTarget.cpp | 1 + libraries/animation/src/IKTarget.h | 5 +++-- libraries/animation/src/Rig.cpp | 10 ++++++---- 7 files changed, 51 insertions(+), 16 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index a402358478..b18599d8a9 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -57,6 +57,8 @@ "positionVar": "hipsPosition", "rotationVar": "hipsRotation", "typeVar": "hipsType", + "weightVar": "hipsWeight", + "weight": 1.0, "flexCoefficients": [1] }, { @@ -64,20 +66,26 @@ "positionVar": "rightHandPosition", "rotationVar": "rightHandRotation", "typeVar": "rightHandType", - "flexCoefficients": [1, 0.5, 0.5, 0.5, 0.25, 0.1, 0.1, 0.1, 0.1] + "weightVar": "rightHandWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.5, 0.5, 0.25, 0.1, 0.05, 0.01, 0.0, 0.0] }, { "jointName": "LeftHand", "positionVar": "leftHandPosition", "rotationVar": "leftHandRotation", "typeVar": "leftHandType", - "flexCoefficients": [1, 0.5, 0.5, 0.5, 0.25, 0.1, 0.1, 0.1, 0.1] + "weightVar": "leftHandWeight", + "weight": 1.0, + "flexCoefficients": [1, 0.5, 0.5, 0.25, 0.1, 0.05, 0.01, 0.0, 0.0] }, { "jointName": "RightFoot", "positionVar": "rightFootPosition", "rotationVar": "rightFootRotation", "typeVar": "rightFootType", + "weightVar": "rightFootWeight", + "weight": 1.0, "flexCoefficients": [1, 0.45, 0.45] }, { @@ -85,6 +93,8 @@ "positionVar": "leftFootPosition", "rotationVar": "leftFootRotation", "typeVar": "leftFootType", + "weightVar": "leftFootWeight", + "weight": 1.0, "flexCoefficients": [1, 0.45, 0.45] }, { @@ -92,6 +102,8 @@ "positionVar": "spine2Position", "rotationVar": "spine2Rotation", "typeVar": "spine2Type", + "weightVar": "spine2Weight", + "weight": 1.0, "flexCoefficients": [0.45, 0.45] }, { @@ -99,7 +111,9 @@ "positionVar": "headPosition", "rotationVar": "headRotation", "typeVar": "headType", - "flexCoefficients": [1, 0.5, 0.25, 0.25, 0.25] + "weightVar": "headWeight", + "weight": 4.0, + "flexCoefficients": [1, 0.5, 0.5, 0.5, 0.5] } ] }, diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 2ab4282177..11c143db23 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -22,10 +22,12 @@ #include "AnimationLogging.h" AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, - const QString& typeVarIn, const std::vector& flexCoefficientsIn) : + const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn) : positionVar(positionVarIn), rotationVar(rotationVarIn), typeVar(typeVarIn), + weightVar(weightVarIn), + weight(weightIn), jointName(jointNameIn), numFlexCoefficients(flexCoefficientsIn.size()), jointIndex(-1) @@ -40,6 +42,8 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) : positionVar(orig.positionVar), rotationVar(orig.rotationVar), typeVar(orig.typeVar), + weightVar(orig.weightVar), + weight(orig.weight), jointName(orig.jointName), numFlexCoefficients(orig.numFlexCoefficients), jointIndex(orig.jointIndex) @@ -90,8 +94,8 @@ void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) con } void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, - const QString& typeVar, const std::vector& flexCoefficients) { - IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, flexCoefficients); + const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients) { + IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients); // if there are dups, last one wins. bool found = false; @@ -132,9 +136,11 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, defaultPose.rot()); glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, defaultPose.trans()); + float weight = animVars.lookup(targetVar.weightVar, targetVar.weight); target.setPose(rotation, translation); target.setIndex(targetVar.jointIndex); + target.setWeight(weight); target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients); targets.push_back(target); @@ -837,7 +843,7 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setTwistLimits(-MAX_SHOULDER_TWIST, MAX_SHOULDER_TWIST); std::vector minDots; - const float MAX_SHOULDER_SWING = PI / 20.0f; + const float MAX_SHOULDER_SWING = PI / 16.0f; minDots.push_back(cosf(MAX_SHOULDER_SWING)); stConstraint->setSwingLimits(minDots); diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index ddbb420012..b798319ca9 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -33,7 +33,7 @@ public: void computeAbsolutePoses(AnimPoseVec& absolutePoses) const; void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, - const QString& typeVar, const std::vector& flexCoefficients); + const QString& typeVar, const QString& weightVar, float weight, const std::vector& flexCoefficients); virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override; virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override; @@ -81,13 +81,15 @@ protected: static const size_t MAX_FLEX_COEFFICIENTS = 10; struct IKTargetVar { IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, - const QString& typeVarIn, const std::vector& flexCoefficientsIn); + const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn); IKTargetVar(const IKTargetVar& orig); QString positionVar; QString rotationVar; QString typeVar; + QString weightVar; QString jointName; + float weight; float flexCoefficients[MAX_FLEX_COEFFICIENTS]; size_t numFlexCoefficients; int jointIndex; // cached joint index diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index e4ffaec5b7..44ed8c6053 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -173,6 +173,13 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { } \ float NAME = (float)NAME##_VAL.toDouble() +#define READ_OPTIONAL_FLOAT(NAME, JSON_OBJ, DEFAULT) \ + auto NAME##_VAL = JSON_OBJ.value(#NAME); \ + float NAME = (float)DEFAULT; \ + if (NAME##_VAL.isDouble()) { \ + NAME = (float)NAME##_VAL.toDouble(); \ + } \ + do {} while (0) static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) { auto idVal = jsonObj.value("id"); @@ -470,6 +477,8 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS READ_STRING(positionVar, targetObj, id, jsonUrl, nullptr); READ_STRING(rotationVar, targetObj, id, jsonUrl, nullptr); READ_OPTIONAL_STRING(typeVar, targetObj); + READ_OPTIONAL_STRING(weightVar, targetObj); + READ_OPTIONAL_FLOAT(weight, targetObj, 1.0f); auto flexCoefficientsValue = targetObj.value("flexCoefficients"); if (!flexCoefficientsValue.isArray()) { @@ -482,7 +491,7 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS flexCoefficients.push_back((float)value.toDouble()); } - node->setTargetVars(jointName, positionVar, rotationVar, typeVar, flexCoefficients); + node->setTargetVars(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients); }; READ_OPTIONAL_STRING(solutionSource, jsonObj); diff --git a/libraries/animation/src/IKTarget.cpp b/libraries/animation/src/IKTarget.cpp index 868293fdb8..ae23d9548f 100644 --- a/libraries/animation/src/IKTarget.cpp +++ b/libraries/animation/src/IKTarget.cpp @@ -23,6 +23,7 @@ void IKTarget::setFlexCoefficients(size_t numFlexCoefficientsIn, const float* fl float IKTarget::getFlexCoefficient(int chainDepth) const { const float DEFAULT_FLEX_COEFFICIENT = 0.5f; + if (chainDepth >= 0 && chainDepth < _numFlexCoefficients) { return _flexCoefficients[chainDepth]; } else { diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 63a4b901a6..0e8bb614ea 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -38,14 +38,15 @@ public: void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn); float getFlexCoefficient(int chainDepth) const; - // HACK: give HmdHead targets more "weight" during IK algorithm - float getWeight() const { return _type == Type::HmdHead ? HACK_HMD_TARGET_WEIGHT : 1.0f; } + void setWeight(float weight) { _weight = weight; } + float getWeight() const { return _weight; } static const size_t MAX_FLEX_COEFFICIENTS = 10; private: AnimPose _pose; int _index{-1}; Type _type{Type::RotationAndPosition}; + float _weight; float _flexCoefficients[MAX_FLEX_COEFFICIENTS]; size_t _numFlexCoefficients; }; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index f5a9008ce1..23db05eb73 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1088,10 +1088,12 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) { // Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type. // this will allow the spine to bend more, ensuring that it can reach the head target position. _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); + _animVars.unset("headWeight"); // use the default weight for this target. } else { // When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff, // but because the IK _hipsOffset is enabled, the hips will naturally follow underneath the head. _animVars.set("headType", (int)IKTarget::Type::HmdHead); + _animVars.set("headWeight", 8.0f); } } else { _animVars.unset("headPosition"); @@ -1405,19 +1407,19 @@ void Rig::computeAvatarBoundingCapsule( ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation", - "leftHandType", {}); + "leftHandType", "leftHandWeight", 1.0f, {}); ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation", - "rightHandType", {}); + "rightHandType", "rightHandWeight", 1.0f, {}); ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation", - "leftFootType", {}); + "leftFootType", "leftFootWeight", 1.0f, {}); ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation", - "rightFootType", {}); + "rightFootType", "rightFootWeight", 1.0f, {}); AnimPose geometryToRig = _modelOffset * _geometryOffset; From b627fe463bbba979a6a8a3e9a519b51268a673d0 Mon Sep 17 00:00:00 2001 From: samcake Date: Mon, 15 May 2017 10:50:10 -0700 Subject: [PATCH 083/135] Generate correct KTX for the default Sky --- .../images/Default-Sky-9-cubemap.ktx | Bin 33554432 -> 33554792 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/interface/resources/images/Default-Sky-9-cubemap.ktx b/interface/resources/images/Default-Sky-9-cubemap.ktx index 95710d2c0847a77858299e34a499b0dc8178f328..476d381a8c00b8bc5934354e319e365c3cefa5e5 100644 GIT binary patch delta 2810 zcmZ|Qd6d^v9|!Q?{mnE@W@@H1q*Y1{O35}&Q;kx*OXEA%3N=w-k}Z<0$-C#7H$@|( zFw(C<7(2jUox&>d++m>kDj|}nd@C;6$M{zTGe;W*K-Ot^&C8CUi~wLV?$}t zF+(>Oxn9tte8uV~GWz|#e%Q(l)oTYdy>xQF_l8a_s>$k~*?&sUL}KXD@zoteezG_* zGYX1=lT;Ma6oqs}p^2i9p(r#}6fzZsW{Sdoib9s6kgX^*R}@+(3OR~GuA?H7Dft>{&BCw0Vt^yAg*iB$}f%yUt6L`45BLwyk*i+z<0(%MUE$}FTeFPpY@EC!| z3Or8W@d673_7zwtu%Eypf&B#z5I9g^vA`1q4iY$6;E4i<2plT#B!MRjgur0}hYLJK z;0S>w0#6kf2`m+On!wWqo+0o|foBOkTi{56qXdo?c#gnx1)e8xjKHx1%LI-Sc)q|3 z1dbOtLEuDz7Ye*c;Kc&V1x^xpiNMJMFBN#1z{>?*A@E9pQv_Zm@M?im1zsa?n!sxX zUMKK+fj0=eQQ&le6#{1nyh$Jh&J_4Bf&Ug*DNq7$7I=%mSpsJZyj9?B0_O;<5_r47 zxdQJHSS|2Qf%62;7kHPzy9M4Outwm$0v8CpPvAmmjtdB__DxP1g;VIs=(I-zAkXBz;y!G3)~>^4S{b8+$iuZfo}_ZN8q~x-xIh= z;QInU5cr|Mj|6@!@DqWb3j9pqW`Umz{6gTD0>2WtMc~&0w+j45;I{(56Sz&__X4*I z+#ztMz#jzuDDWqNKMVXt;4Xo`3j9sr?*ji2xLe?#0`~~qEATIYe+&Fa;68!zY%o-T z!6Z$ZNjFVQhG}XtO*6Bf$uildxoKf?Os;8ZTABS#Ym;XVFbA48<{;D7v@`8Z2h-7X zG6$Q^<`C1xbTx;XZl=4*H;0+S%@L-D>1mEMy-aU&l<8xRHpiG_&2i>-D!%|qs4^N6W4kDABK zdi8<+^jGw%_{SPdC@eOm&|JOvU$aY zS!dRp4dxB=rrBuTGH;uA%)912v&p<~J}@7ekIcvB6Z5J0%xpHFho?$vzX)Z?bYF3O z6hiWml&`Z*YI<#D$WLvotqhq}HFaNws+zhj=4-Rnd}F>f-@|Oxze7X$ zq<_MWd1=Lo#zk$bMz`A+8b-H^Lp&iZw{h~OjE2pM!;DdB>5Vl*!^s5l@kBoft8 zC~H`=Ck~66B#RTNiE$iis>?!tDhfM8aViR1LvE+c9s}a0hZn}pyB5WH9frgi*)8KH zSvif#$ZDAy7`BGar{^788n-@bMBKVhNt}1w8FAC*E#r)=oW^*ZNF@Fzxrs!gRjZ@p zoX!PtdS-TGT6F0fH|x+VPH*NqD>X3e44sQII`xd3x9RPf#;%-03*($_MgQ$`XCjfv ZZk-=DYtubWOXfE2l9|^f&T8Ky{tt-AWuyQA delta 2610 zcmZ|Qdvw=x7zgn0=QpiU5|OFoo)AJ<+mcJ-U7GKNYE+vEZPipHw>IzCJ4L@Tg-O4L zXh;%PD<*QO$faCDNx7t4qbM}zoxgp~d0yu{&pFRI&pFR?&Sx_#T2@q4w473wh@z<@ zJCu)(%A#F6^0Q}FF7Mi6Vlt;kG%5;fd)G6o-Qso`VM%dR7WH45lbyd{L;cwCQTC8% zR5W{7QP!5u0~ZxcU9+%nR2*$9EYI4oW=Yp&n zI+fHfotvJseMPUuMNx6obj0wS)jh}LRyXUG(JlJ#RkaTt6BS{6925sfLUBk_9QINi znkWwGibGSyAwzN4TXEP&acHJEG*=w9hgOQifr`UHibHF~ z;b6rfQ*k(?E)H#Wl~)Z48L64Or-#DS`bCu?C9sXawgTG;JXBzNfrkk^T;LG`j}&;6 zz@r5oBd~+OV+D2;c$~oF1)d}OBJfm!rwQyL@N|J^2s~3@mcXt8 zvjye|%oW&8;8_B@3p`ukIReiWc%Hx>0?!xNQ{V*xFBAxYy#)3ac#*(90`ml3EHDjVxJSR$}g;4p#11&$C{Ch&TJBL$8UI9lKh0&f&JM&L~X#|pez;4K1g6?mJ#+Xapj zST3+a;2i=faJ;}f1>PmFQlJD*5O}x1i2^4Hyhq@@0w)Vh3Y;Qvs=)gMRtdac;532L z1wJ6~L4gkmtQPpNz!?G`5jazz?_!p~8i9`rd`#fu0%r?+Lg14E=LoD7_>{n>1wJEi zuE1vn&J*~Y!1)577q~#+3j$vh_>#anfeQsL5?C*AvA`t)Ul#a^zy^V@3S27iHG!`S zTqf`hfo}?YOW@lAmkV4WaHYU^1imY9mB9A|zAx|tfgcK7EpUy%j|6@!@DqWb3S2Aj zGl8EA{6gS5f$If+DR6_puLN!sxJlsG0>2UXt-$XDZWj2xz%2r|3j9Ifj{<)Z__M%Y z1pX>;o50@${w{F4z#Rhr5csFSodS0W{7c~90{;=XTVUfPFeJfX5+=>;Wty0D)6`^` zz0E$RnQ3nJH7(43rlr~69AH|R1I>innO$*)7G>zhnn{0Fmt#$!W?OiGDn+Z zOb2tU>1d8K$D0#OCv&1X$((FDn^VlG<}}mAoNmrAXPPY2)nuC-lWV$}vrKn$wmHX~ zYtA!0%=xCLxxid#V0xL}<|5O_!j! zyBQZ6!155v6NS! zW`TLZyl7rBb!MSiWa`agv&6h?UNH^kRkPH*W?nbT%p2xS^OkwrEH^96O7o6+*Q_${ znfJ{H=0mgEtT7*%kIg6MQ?u55WuBI|{ zOs%S^3>nGl+OI;gx^|=4WWF}vm~YK@X0!R;Y%yES59UYnllj^FVtzH-%x~s*v)$|n z4b`=OgoXVQ>Cu8XBue&-(>yh<)`OIk{uz=br8~_o^OyNMG?bM76ShuEs|b6V+9V6w z?hXwFZ5u=5khH?c#XHg))~PX63`on|lQT4I+}Ie(Tc@Q*QB@oY8C46~Yqid50g N#?UG?wy`lx_!nFsBtifH From 5c5eb298f5c50f4adb586e22ea3b31907242e00c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 16 May 2017 09:20:58 +1200 Subject: [PATCH 084/135] Better error message --- scripts/system/playRecordingAC.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index 4cf65c7942..c1c70587c6 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -294,7 +294,7 @@ recordingFilename = recording; playRecording(recordingFilename, position, orientation); } else { - errorMessage = "Could not play recording " + recording.slice(4); // Remove leading "atp:". + errorMessage = "Could not persist recording " + recording.slice(4); // Remove leading "atp:". log(errorMessage); error(errorMessage); } From a2a24b22a8ba82b9f2637e7408a83a81c7e8d72f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 15 May 2017 14:12:48 -0700 Subject: [PATCH 085/135] Force _identityUpdatedAt to stay above 0 --- libraries/avatars/src/AvatarData.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6992e66f0e..f839a99710 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1538,7 +1538,14 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC // use the timestamp from this identity, since we want to honor the updated times in "server clock" // this will overwrite any changes we made locally to this AvatarData's _identityUpdatedAt - _identityUpdatedAt = identity.updatedAt - clockSkew; + // Additionally, ensure that the timestamp that we try to record isn't negative, as + // "_identityUpdatedAt" is an *unsigned* 64-bit integer. Furthermore, negative timestamps + // wouldn't make sense. + if (identity.updatedAt - clockSkew >= 0) { + _identityUpdatedAt = identity.updatedAt - clockSkew; + } else { + _identityUpdatedAt = 0; + } } QByteArray AvatarData::identityByteArray() const { From e492ca9364c8b9f8aae61b09ed5ae4acaa97d4aa Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 15 May 2017 14:55:05 -0700 Subject: [PATCH 086/135] Improve the test - thanks Dave! --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f839a99710..efd5158c7d 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1541,7 +1541,7 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC // Additionally, ensure that the timestamp that we try to record isn't negative, as // "_identityUpdatedAt" is an *unsigned* 64-bit integer. Furthermore, negative timestamps // wouldn't make sense. - if (identity.updatedAt - clockSkew >= 0) { + if (identity.updatedAt > clockSkew) { _identityUpdatedAt = identity.updatedAt - clockSkew; } else { _identityUpdatedAt = 0; From 6bdd498ecfca598f73fbb2c0a23e331eabf5940e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 16 May 2017 10:47:52 +1200 Subject: [PATCH 087/135] Cancel autoplay when manually playing --- scripts/system/playRecordingAC.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index c1c70587c6..f62451154b 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -286,6 +286,11 @@ function play(user, recording, position, orientation) { var errorMessage; + if (autoPlayTimer) { // Cancel autoplay. + Script.clearTimeout(autoPlayTimer); + autoPlayTimer = null; + } + userID = user; if (Entity.create(recording, position, orientation)) { @@ -297,6 +302,8 @@ errorMessage = "Could not persist recording " + recording.slice(4); // Remove leading "atp:". log(errorMessage); error(errorMessage); + + autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Resume autoplay later. } } From bc1e97032a1cd855b942d7b8e2ae5df274d31cab Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 15 May 2017 17:39:19 -0700 Subject: [PATCH 088/135] Thanks to Andrew! --- libraries/avatars/src/AvatarData.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 08e8774dfb..26afed152f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1496,13 +1496,18 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { } void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, const qint64 clockSkew) { + quint64 identityPacketUpdatedAt = identity.updatedAt; + + if (identityPacketUpdatedAt <= (uint64_t)(abs(clockSkew))) { // Incoming timestamp is bad - compute our own timestamp + identityPacketUpdatedAt = usecTimestampNow() + clockSkew; + } // Consider the case where this packet is being processed on Client A, and Client A is connected to Sandbox B. // If Client A's system clock is *ahead of* Sandbox B's system clock, "clockSkew" will be *negative*. // If Client A's system clock is *behind* Sandbox B's system clock, "clockSkew" will be *positive*. - if ((_identityUpdatedAt > identity.updatedAt - clockSkew) && (_identityUpdatedAt != 0)) { + if ((_identityUpdatedAt > identityPacketUpdatedAt - clockSkew) && (_identityUpdatedAt != 0)) { qCDebug(avatars) << "Ignoring late identity packet for avatar " << getSessionUUID() - << "_identityUpdatedAt (" << _identityUpdatedAt << ") is greater than identity.updatedAt - clockSkew (" << identity.updatedAt << "-" << clockSkew << ")"; + << "_identityUpdatedAt (" << _identityUpdatedAt << ") is greater than identityPacketUpdatedAt - clockSkew (" << identityPacketUpdatedAt << "-" << clockSkew << ")"; return; } @@ -1538,14 +1543,7 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC // use the timestamp from this identity, since we want to honor the updated times in "server clock" // this will overwrite any changes we made locally to this AvatarData's _identityUpdatedAt - // Additionally, ensure that the timestamp that we try to record isn't negative, as - // "_identityUpdatedAt" is an *unsigned* 64-bit integer. Furthermore, negative timestamps - // wouldn't make sense. - if (identity.updatedAt > clockSkew) { - _identityUpdatedAt = identity.updatedAt - clockSkew; - } else { - _identityUpdatedAt = 0; - } + _identityUpdatedAt = identityPacketUpdatedAt - clockSkew; } QByteArray AvatarData::identityByteArray() const { From cde7d6857018846c2b74b37faf12b3e48000cab7 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 15 May 2017 18:11:30 -0700 Subject: [PATCH 089/135] Fix domain settings not getting cleared on re-assignment --- assignment-client/src/audio/AudioMixer.cpp | 22 +++++++++++++++++++--- assignment-client/src/audio/AudioMixer.h | 1 + 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index b95c429b2d..98eca60e85 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -38,13 +38,14 @@ #include "AudioMixer.h" static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance) +static const int DEFAULT_NUM_STATIC_JITTER_FRAMES = -1; static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f; static const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer"; static const QString AUDIO_ENV_GROUP_KEY = "audio_env"; static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer"; static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading"; -int AudioMixer::_numStaticJitterFrames{ -1 }; +int AudioMixer::_numStaticJitterFrames{ DEFAULT_NUM_STATIC_JITTER_FRAMES }; float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD }; float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE }; std::map> AudioMixer::_availableCodecs{ }; @@ -56,7 +57,12 @@ QVector AudioMixer::_zoneReverbSettings; AudioMixer::AudioMixer(ReceivedMessage& message) : ThreadedAssignment(message) { + // Always clear settings first + // This prevents previous assignment settings from sticking around + clearDomainSettings(); + // hash the available codecs (on the mixer) + _availableCodecs.clear(); // Make sure struct is clean auto codecPlugins = PluginManager::getInstance()->getCodecPlugins(); std::for_each(codecPlugins.cbegin(), codecPlugins.cend(), [&](const CodecPluginPointer& codec) { @@ -232,7 +238,7 @@ void AudioMixer::sendStatsPacket() { } // general stats - statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == -1; + statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == DEFAULT_NUM_STATIC_JITTER_FRAMES; statsObject["threads"] = _slavePool.numThreads(); @@ -490,6 +496,16 @@ int AudioMixer::prepareFrame(const SharedNodePointer& node, unsigned int frame) return data->checkBuffersBeforeFrameSend(); } +void AudioMixer::clearDomainSettings() { + _numStaticJitterFrames = DEFAULT_NUM_STATIC_JITTER_FRAMES; + _attenuationPerDoublingInDistance = DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE; + _noiseMutingThreshold = DEFAULT_NOISE_MUTING_THRESHOLD; + _codecPreferenceOrder.clear(); + _audioZones.clear(); + _zoneSettings.clear(); + _zoneReverbSettings.clear(); +} + void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) { qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled"); @@ -525,7 +541,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) { qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames; } else { qDebug() << "Disabling dynamic jitter buffers."; - _numStaticJitterFrames = -1; + _numStaticJitterFrames = DEFAULT_NUM_STATIC_JITTER_FRAMES; } // check for deprecated audio settings diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 0641c04a6c..18e754016e 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -79,6 +79,7 @@ private: QString percentageForMixStats(int counter); void parseSettingsObject(const QJsonObject& settingsObject); + void clearDomainSettings(); float _trailingMixRatio { 0.0f }; float _throttlingRatio { 0.0f }; From 08eb6442b5c3ada5b4f07c8aa5b77260b7b79b29 Mon Sep 17 00:00:00 2001 From: Liv Date: Mon, 15 May 2017 18:36:36 -0700 Subject: [PATCH 090/135] Add function findEntitiesByType EntityScriptingInterface Updated EntityScriptingInterface.cpp and .h to create a new function for the Entities API that can be called to filter out entities found by a specific entity type. --- .../entities/src/EntityScriptingInterface.cpp | 20 +++++++++++++++++++ .../entities/src/EntityScriptingInterface.h | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index ffb65a2dba..e2726dada2 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -662,6 +662,26 @@ QVector EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frust return result; } +QVector EntityScriptingInterface::findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const { + EntityTypes::EntityType type = EntityTypes::getEntityTypeFromName(entityType); + + QVector result; + if (_entityTree) { + QVector entities; + _entityTree->withReadLock([&] { + _entityTree->findEntities(center, radius, entities); + }); + + foreach(EntityItemPointer entity, entities) { + if (entity->getType() == type) + { + result << entity->getEntityItemID(); + } + } + } + return result; +} + RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { PROFILE_RANGE(script_entities, __FUNCTION__); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index f5656860e3..5e1d5ecf61 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -215,6 +215,13 @@ public slots: /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntitiesInFrustum(QVariantMap frustum) const; + /// finds models within a sphere given by the center point and radius + /// @param {QString} string representation of entity type + /// @param {vec3} center point + /// @param {float} radius to search + /// this function will not find any models in script engine contexts which don't have access to models + Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; + /// If the scripting context has visible entities, this will determine a ray intersection, the results /// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate /// will be false. From 6d9bf2b33eb5de7bfd81a313896e7b64610804e4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 16 May 2017 13:58:42 +1200 Subject: [PATCH 091/135] Typos --- scripts/system/playRecordingAC.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index f62451154b..3d87e9a53f 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -286,7 +286,7 @@ function play(user, recording, position, orientation) { var errorMessage; - if (autoPlayTimer) { // Cancel autoplay. + if (autoPlayTimer) { // Cancel auto-play. Script.clearTimeout(autoPlayTimer); autoPlayTimer = null; } @@ -295,7 +295,7 @@ if (Entity.create(recording, position, orientation)) { log("Play recording " + recording); - isPlayingRecording = true; + isPlayingRecording = true; // Immediate feedback. recordingFilename = recording; playRecording(recordingFilename, position, orientation); } else { @@ -303,7 +303,7 @@ log(errorMessage); error(errorMessage); - autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Resume autoplay later. + autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Resume auto-play later. } } @@ -315,7 +315,7 @@ Script.setTimeout(function () { recording = Entity.find(); if (recording) { - log("Play persisted recording " + recordingFilename); + log("Play persisted recording " + recording.recording); userID = null; playRecording(recording.recording, recording.position, recording.orientation); } else { From a606f8a8d46810820f7b4ff6d9b695476ad7ac10 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 15 May 2017 20:28:48 -0700 Subject: [PATCH 092/135] Fixing code style --- libraries/entities/src/EntityScriptingInterface.cpp | 3 +-- libraries/entities/src/EntityScriptingInterface.h | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index e2726dada2..ab2f342a12 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -673,8 +673,7 @@ QVector EntityScriptingInterface::findEntitiesByType(const QString entity }); foreach(EntityItemPointer entity, entities) { - if (entity->getType() == type) - { + if (entity->getType() == type){ result << entity->getEntityItemID(); } } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 5e1d5ecf61..ac176f6267 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -212,14 +212,14 @@ public slots: /// - orientation /// - projection /// - centerRadius - /// this function will not find any models in script engine contexts which don't have access to models + /// this function will not find any models in script engine contexts which don't have access to entities Q_INVOKABLE QVector findEntitiesInFrustum(QVariantMap frustum) const; /// finds models within a sphere given by the center point and radius /// @param {QString} string representation of entity type /// @param {vec3} center point /// @param {float} radius to search - /// this function will not find any models in script engine contexts which don't have access to models + /// this function will not find any entities in script engine contexts which don't have access to models Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; /// If the scripting context has visible entities, this will determine a ray intersection, the results From 4b175e98e953332d59ea5a279be93f33dd22eb00 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 16 May 2017 15:30:30 +1200 Subject: [PATCH 093/135] Delete persistence entity if recording fails to load for manual playback --- scripts/system/playRecordingAC.js | 38 ++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index 3d87e9a53f..3eb36a2a24 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -48,9 +48,17 @@ searchState = SEARCH_IDLE, otherPlayersPlaying, otherPlayersPlayingCounts, - pauseCount; + pauseCount, + isDestroyLater = false, + + destroy; function onUpdateTimestamp() { + if (isDestroyLater) { + destroy(); + return; + } + userData.timestamp = Date.now(); Entities.editEntity(entityID, { userData: JSON.stringify(userData) }); EntityViewer.queryOctree(); // Keep up to date ready for find(). @@ -229,7 +237,7 @@ return result; } - function destroy() { + destroy = function () { // Delete current persistence entity. if (entityID !== null) { // Just in case. Entities.deleteEntity(entityID); @@ -240,6 +248,11 @@ Script.clearInterval(updateTimestampTimer); updateTimestampTimer = null; } + }; + + function destroyLater() { + // Schedules a call to destroy() when timer threading suits. + isDestroyLater = true; } function setUp() { @@ -260,6 +273,7 @@ create: create, find: find, destroy: destroy, + destroyLater: destroyLater, setUp: setUp, tearDown: tearDown }; @@ -297,7 +311,7 @@ log("Play recording " + recording); isPlayingRecording = true; // Immediate feedback. recordingFilename = recording; - playRecording(recordingFilename, position, orientation); + playRecording(recordingFilename, position, orientation, true); } else { errorMessage = "Could not persist recording " + recording.slice(4); // Remove leading "atp:". log(errorMessage); @@ -317,14 +331,17 @@ if (recording) { log("Play persisted recording " + recording.recording); userID = null; - playRecording(recording.recording, recording.position, recording.orientation); + autoPlayTimer = null; + isPlayingRecording = true; // Immediate feedback. + recordingFilename = recording.recording; + playRecording(recording.recording, recording.position, recording.orientation, false); } else { autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_SEARCH_INTERVAL); // Try again soon. } }, Math.random() * AUTOPLAY_SEARCH_DELTA); } - playRecording = function (recording, position, orientation) { + playRecording = function (recording, position, orientation, isManual) { Recording.loadRecording(recording, function (success) { var errorMessage; @@ -342,17 +359,22 @@ Recording.setPlayerLoop(true); Recording.setPlayerUseSkeletonModel(true); - isPlayingRecording = true; - recordingFilename = recording; - Recording.setPlayerTime(0.0); Recording.startPlaying(); UserActivityLogger.logAction("playRecordingAC_play_recording"); } else { + if (isManual) { + // Delete persistence entity if manual play request. + Entity.destroyLater(); // Schedule for deletion; works around timer threading issues. + } + errorMessage = "Could not load recording " + recording.slice(4); // Remove leading "atp:". log(errorMessage); error(errorMessage); + + isPlayingRecording = false; + recordingFilename = ""; autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Try again later. } }); From ae458dcbcfaf32f1c9da0a0a752e8fc8fa506935 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 15 May 2017 20:33:22 -0700 Subject: [PATCH 094/135] changing models to more generally reference entities in comment --- libraries/entities/src/EntityScriptingInterface.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index ac176f6267..575528fa78 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -215,11 +215,11 @@ public slots: /// this function will not find any models in script engine contexts which don't have access to entities Q_INVOKABLE QVector findEntitiesInFrustum(QVariantMap frustum) const; - /// finds models within a sphere given by the center point and radius + /// finds entities of the indicated type within a sphere given by the center point and radius /// @param {QString} string representation of entity type /// @param {vec3} center point /// @param {float} radius to search - /// this function will not find any entities in script engine contexts which don't have access to models + /// this function will not find any entities in script engine contexts which don't have access to entities Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; /// If the scripting context has visible entities, this will determine a ray intersection, the results From 32fd905918c5dd480e8e6378f63305a75864da9d Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 15 May 2017 20:49:30 -0700 Subject: [PATCH 095/135] style pass --- libraries/entities/src/EntityScriptingInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index ab2f342a12..b184d648da 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -673,7 +673,7 @@ QVector EntityScriptingInterface::findEntitiesByType(const QString entity }); foreach(EntityItemPointer entity, entities) { - if (entity->getType() == type){ + if (entity->getType() == type) { result << entity->getEntityItemID(); } } From 8ab261d1b0a312c2cc151306b812a008aa6a4b5e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 16 May 2017 15:52:34 +1200 Subject: [PATCH 096/135] Work around Script.clearTimeout() occasionally failing --- scripts/system/playRecordingAC.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index 3eb36a2a24..d1b931bc41 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -301,6 +301,8 @@ var errorMessage; if (autoPlayTimer) { // Cancel auto-play. + // FIXME: Once in a while Script.clearTimeout() fails. + // [DEBUG] [hifi.scriptengine] [3748] [agent] stopTimer -- not in _timerFunctionMap QObject(0x0) Script.clearTimeout(autoPlayTimer); autoPlayTimer = null; } @@ -327,6 +329,11 @@ // Random delay to help reduce collisions between AC scripts. Script.setTimeout(function () { + // Guard against Script.clearTimeout() in play() not always working. + if (isPlayingRecording) { + return; + } + recording = Entity.find(); if (recording) { log("Play persisted recording " + recording.recording); From 0cd5fe35da64b1f83442864e580cac48c8069f71 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 16 May 2017 15:54:55 +1200 Subject: [PATCH 097/135] Lint --- scripts/system/playRecordingAC.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js index d1b931bc41..98e5220c31 100644 --- a/scripts/system/playRecordingAC.js +++ b/scripts/system/playRecordingAC.js @@ -286,6 +286,7 @@ recordingFilename = "", autoPlayTimer = null, + autoPlay, playRecording; function error(message) { @@ -323,7 +324,7 @@ } } - function autoPlay() { + autoPlay = function () { var recording, AUTOPLAY_SEARCH_DELTA = 1000; @@ -346,7 +347,7 @@ autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_SEARCH_INTERVAL); // Try again soon. } }, Math.random() * AUTOPLAY_SEARCH_DELTA); - } + }; playRecording = function (recording, position, orientation, isManual) { Recording.loadRecording(recording, function (success) { From 9e08dadaf40d05df5824935981b62406ef937474 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 15 May 2017 22:11:42 -0700 Subject: [PATCH 098/135] Remove ObjectActionSpring --- interface/src/InterfaceDynamicFactory.cpp | 4 +- .../entities/src/EntityDynamicInterface.cpp | 8 +- .../entities/src/EntityDynamicInterface.h | 9 - libraries/physics/src/ObjectActionSpring.cpp | 378 ------------------ libraries/physics/src/ObjectActionSpring.h | 56 --- 5 files changed, 6 insertions(+), 449 deletions(-) delete mode 100644 libraries/physics/src/ObjectActionSpring.cpp delete mode 100644 libraries/physics/src/ObjectActionSpring.h diff --git a/interface/src/InterfaceDynamicFactory.cpp b/interface/src/InterfaceDynamicFactory.cpp index 49edaae90f..1bb33a7341 100644 --- a/interface/src/InterfaceDynamicFactory.cpp +++ b/interface/src/InterfaceDynamicFactory.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -33,7 +32,8 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid case DYNAMIC_TYPE_OFFSET: return std::make_shared(id, ownerEntity); case DYNAMIC_TYPE_SPRING: - return std::make_shared(id, ownerEntity); + // the old "Spring" action moved to "Tractor" + return std::make_shared(id, ownerEntity); case DYNAMIC_TYPE_TRACTOR: return std::make_shared(id, ownerEntity); case DYNAMIC_TYPE_HOLD: diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index 822ac1c9d6..4bcb0a6027 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -43,7 +43,7 @@ | | +--------------------+ +-----------------------+ | | | | - | ObjectActionSpring | | ObjectConstraintHinge | + | ObjectActionTractor| | ObjectConstraintHinge | | (physics) | | (physics) | +--------------------+ +-----------------------+ @@ -60,7 +60,7 @@ script or when receiving information via an EntityTree data-stream (either over svo file). In the interface, if an EntityItem has dynamics, this EntityItem will have pointers to ObjectDynamic -subclass (like ObjectDynamicSpring) instantiations. Code in the entities library affects a dynamic-object +subclass (like ObjectDynamicTractor) instantiations. Code in the entities library affects a dynamic-object via the EntityDynamicInterface (which knows nothing about bullet). When the ObjectDynamic subclass instance is created, it is registered as a dynamic with bullet. Bullet will call into code in this instance with the btDynamicInterface every physics-simulation step. @@ -75,11 +75,11 @@ right now the AssignmentDynamic class is a place-holder. The dynamic-objects are instantiated by singleton (dependecy) subclasses of EntityDynamicFactoryInterface. In the interface, the subclass is an InterfaceDynamicFactory and it will produce things like -ObjectDynamicSpring. In an entity-server the subclass is an AssignmentDynamicFactory and it always +ObjectDynamicTractor. In an entity-server the subclass is an AssignmentDynamicFactory and it always produces AssignmentDynamics. Depending on the dynamic's type, it will have various arguments. When a script changes an argument of an -dynamic, the argument-holding member-variables of ObjectDynamicSpring (in this example) are updated and +dynamic, the argument-holding member-variables of ObjectDynamicTractor (in this example) are updated and also serialized into _dynamicData in the EntityItem. Each subclass of ObjectDynamic knows how to serialize and deserialize its own arguments. _dynamicData is what gets sent over the wire or saved in an svo file. When a packet-reader receives data for _dynamicData, it will save it in the EntityItem; this causes the diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h index b27e600660..40e39eecf1 100644 --- a/libraries/entities/src/EntityDynamicInterface.h +++ b/libraries/entities/src/EntityDynamicInterface.h @@ -94,15 +94,6 @@ public: QString argumentName, bool& ok, bool required = true); protected: - virtual glm::vec3 getPosition() = 0; - // virtual void setPosition(glm::vec3 position) = 0; - virtual glm::quat getRotation() = 0; - // virtual void setRotation(glm::quat rotation) = 0; - virtual glm::vec3 getLinearVelocity() = 0; - virtual void setLinearVelocity(glm::vec3 linearVelocity) = 0; - virtual glm::vec3 getAngularVelocity() = 0; - virtual void setAngularVelocity(glm::vec3 angularVelocity) = 0; - QUuid _id; EntityDynamicType _type; bool _active { false }; diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp deleted file mode 100644 index 0014d4e914..0000000000 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ /dev/null @@ -1,378 +0,0 @@ -// -// ObjectActionSpring.cpp -// libraries/physics/src -// -// Created by Seth Alves 2015-6-5 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "QVariantGLM.h" - -#include "ObjectActionSpring.h" - -#include "PhysicsLogging.h" - -const float SPRING_MAX_SPEED = 10.0f; -const float MAX_SPRING_TIMESCALE = 600.0f; // 10 min is a long time - -const uint16_t ObjectActionSpring::springVersion = 1; - - -ObjectActionSpring::ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity) : - ObjectAction(DYNAMIC_TYPE_SPRING, id, ownerEntity), - _positionalTarget(glm::vec3(0.0f)), - _desiredPositionalTarget(glm::vec3(0.0f)), - _linearTimeScale(FLT_MAX), - _positionalTargetSet(true), - _rotationalTarget(glm::quat()), - _desiredRotationalTarget(glm::quat()), - _angularTimeScale(FLT_MAX), - _rotationalTargetSet(true) { - #if WANT_DEBUG - qCDebug(physics) << "ObjectActionSpring::ObjectActionSpring"; - #endif -} - -ObjectActionSpring::~ObjectActionSpring() { - #if WANT_DEBUG - qCDebug(physics) << "ObjectActionSpring::~ObjectActionSpring"; - #endif -} - -bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity, - float& linearTimeScale, float& angularTimeScale) { - SpatiallyNestablePointer other = getOther(); - withReadLock([&]{ - linearTimeScale = _linearTimeScale; - angularTimeScale = _angularTimeScale; - - if (!_otherID.isNull()) { - if (other) { - rotation = _desiredRotationalTarget * other->getRotation(); - position = other->getRotation() * _desiredPositionalTarget + other->getPosition(); - } else { - // we should have an "other" but can't find it, so disable the spring. - linearTimeScale = FLT_MAX; - angularTimeScale = FLT_MAX; - } - } else { - rotation = _desiredRotationalTarget; - position = _desiredPositionalTarget; - } - linearVelocity = glm::vec3(); - angularVelocity = glm::vec3(); - }); - return true; -} - -bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) { - auto ownerEntity = _ownerEntity.lock(); - if (!ownerEntity) { - return false; - } - - glm::quat rotation; - glm::vec3 position; - glm::vec3 linearVelocity; - glm::vec3 angularVelocity; - - bool linearValid = false; - int linearSpringCount = 0; - bool angularValid = false; - int angularSpringCount = 0; - - QList springDerivedActions; - springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_SPRING)); - springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_FAR_GRAB)); - springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_HOLD)); - - foreach (EntityDynamicPointer action, springDerivedActions) { - std::shared_ptr springAction = std::static_pointer_cast(action); - glm::quat rotationForAction; - glm::vec3 positionForAction; - glm::vec3 linearVelocityForAction; - glm::vec3 angularVelocityForAction; - float linearTimeScale; - float angularTimeScale; - bool success = springAction->getTarget(deltaTimeStep, - rotationForAction, positionForAction, - linearVelocityForAction, angularVelocityForAction, - linearTimeScale, angularTimeScale); - if (success) { - if (angularTimeScale < MAX_SPRING_TIMESCALE) { - angularValid = true; - angularSpringCount++; - angularVelocity += angularVelocityForAction; - if (springAction.get() == this) { - // only use the rotation for this action - rotation = rotationForAction; - } - } - - if (linearTimeScale < MAX_SPRING_TIMESCALE) { - linearValid = true; - linearSpringCount++; - position += positionForAction; - linearVelocity += linearVelocityForAction; - } - } - } - - if ((angularValid && angularSpringCount > 0) || (linearValid && linearSpringCount > 0)) { - withWriteLock([&]{ - if (linearValid && linearSpringCount > 0) { - position /= linearSpringCount; - linearVelocity /= linearSpringCount; - _positionalTarget = position; - _linearVelocityTarget = linearVelocity; - _positionalTargetSet = true; - _active = true; - } - if (angularValid && angularSpringCount > 0) { - angularVelocity /= angularSpringCount; - _rotationalTarget = rotation; - _angularVelocityTarget = angularVelocity; - _rotationalTargetSet = true; - _active = true; - } - }); - } - - return linearValid || angularValid; -} - - -void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { - if (!prepareForSpringUpdate(deltaTimeStep)) { - return; - } - - withReadLock([&]{ - auto ownerEntity = _ownerEntity.lock(); - if (!ownerEntity) { - return; - } - - void* physicsInfo = ownerEntity->getPhysicsInfo(); - if (!physicsInfo) { - return; - } - ObjectMotionState* motionState = static_cast(physicsInfo); - btRigidBody* rigidBody = motionState->getRigidBody(); - if (!rigidBody) { - qCDebug(physics) << "ObjectActionSpring::updateActionWorker no rigidBody"; - return; - } - - if (_linearTimeScale < MAX_SPRING_TIMESCALE) { - btVector3 targetVelocity(0.0f, 0.0f, 0.0f); - btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget); - float offsetLength = offset.length(); - if (offsetLength > FLT_EPSILON) { - float speed = glm::min(offsetLength / _linearTimeScale, SPRING_MAX_SPEED); - targetVelocity = (-speed / offsetLength) * offset; - if (speed > rigidBody->getLinearSleepingThreshold()) { - forceBodyNonStatic(); - rigidBody->activate(); - } - } - // this action is aggresively critically damped and defeats the current velocity - rigidBody->setLinearVelocity(targetVelocity); - } - - if (_angularTimeScale < MAX_SPRING_TIMESCALE) { - btVector3 targetVelocity(0.0f, 0.0f, 0.0f); - - btQuaternion bodyRotation = rigidBody->getOrientation(); - auto alignmentDot = bodyRotation.dot(glmToBullet(_rotationalTarget)); - const float ALMOST_ONE = 0.99999f; - if (glm::abs(alignmentDot) < ALMOST_ONE) { - btQuaternion target = glmToBullet(_rotationalTarget); - if (alignmentDot < 0.0f) { - target = -target; - } - // if dQ is the incremental rotation that gets an object from Q0 to Q1 then: - // - // Q1 = dQ * Q0 - // - // solving for dQ gives: - // - // dQ = Q1 * Q0^ - btQuaternion deltaQ = target * bodyRotation.inverse(); - float speed = deltaQ.getAngle() / _angularTimeScale; - targetVelocity = speed * deltaQ.getAxis(); - if (speed > rigidBody->getAngularSleepingThreshold()) { - rigidBody->activate(); - } - } - // this action is aggresively critically damped and defeats the current velocity - rigidBody->setAngularVelocity(targetVelocity); - } - }); -} - -const float MIN_TIMESCALE = 0.1f; - - -bool ObjectActionSpring::updateArguments(QVariantMap arguments) { - glm::vec3 positionalTarget; - float linearTimeScale; - glm::quat rotationalTarget; - float angularTimeScale; - QUuid otherID; - - - bool needUpdate = false; - bool somethingChanged = ObjectDynamic::updateArguments(arguments); - withReadLock([&]{ - // targets are required, spring-constants are optional - bool ok = true; - positionalTarget = EntityDynamicInterface::extractVec3Argument("spring action", arguments, "targetPosition", ok, false); - if (!ok) { - positionalTarget = _desiredPositionalTarget; - } - ok = true; - linearTimeScale = EntityDynamicInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", ok, false); - if (!ok || linearTimeScale <= 0.0f) { - linearTimeScale = _linearTimeScale; - } - - ok = true; - rotationalTarget = EntityDynamicInterface::extractQuatArgument("spring action", arguments, "targetRotation", ok, false); - if (!ok) { - rotationalTarget = _desiredRotationalTarget; - } - - ok = true; - angularTimeScale = - EntityDynamicInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", ok, false); - if (!ok) { - angularTimeScale = _angularTimeScale; - } - - ok = true; - otherID = QUuid(EntityDynamicInterface::extractStringArgument("spring action", - arguments, "otherID", ok, false)); - if (!ok) { - otherID = _otherID; - } - - if (somethingChanged || - positionalTarget != _desiredPositionalTarget || - linearTimeScale != _linearTimeScale || - rotationalTarget != _desiredRotationalTarget || - angularTimeScale != _angularTimeScale || - otherID != _otherID) { - // something changed - needUpdate = true; - } - }); - - if (needUpdate) { - withWriteLock([&] { - _desiredPositionalTarget = positionalTarget; - _linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale)); - _desiredRotationalTarget = rotationalTarget; - _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); - _otherID = otherID; - _active = true; - - auto ownerEntity = _ownerEntity.lock(); - if (ownerEntity) { - ownerEntity->setDynamicDataDirty(true); - ownerEntity->setDynamicDataNeedsTransmit(true); - } - }); - activateBody(); - } - - return true; -} - -QVariantMap ObjectActionSpring::getArguments() { - QVariantMap arguments = ObjectDynamic::getArguments(); - withReadLock([&] { - arguments["linearTimeScale"] = _linearTimeScale; - arguments["targetPosition"] = glmToQMap(_desiredPositionalTarget); - - arguments["targetRotation"] = glmToQMap(_desiredRotationalTarget); - arguments["angularTimeScale"] = _angularTimeScale; - - arguments["otherID"] = _otherID; - }); - return arguments; -} - -void ObjectActionSpring::serializeParameters(QDataStream& dataStream) const { - withReadLock([&] { - dataStream << _desiredPositionalTarget; - dataStream << _linearTimeScale; - dataStream << _positionalTargetSet; - dataStream << _desiredRotationalTarget; - dataStream << _angularTimeScale; - dataStream << _rotationalTargetSet; - dataStream << localTimeToServerTime(_expires); - dataStream << _tag; - dataStream << _otherID; - }); -} - -QByteArray ObjectActionSpring::serialize() const { - QByteArray serializedActionArguments; - QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly); - - dataStream << DYNAMIC_TYPE_SPRING; - dataStream << getID(); - dataStream << ObjectActionSpring::springVersion; - - serializeParameters(dataStream); - - return serializedActionArguments; -} - -void ObjectActionSpring::deserializeParameters(QByteArray serializedArguments, QDataStream& dataStream) { - withWriteLock([&] { - dataStream >> _desiredPositionalTarget; - dataStream >> _linearTimeScale; - dataStream >> _positionalTargetSet; - - dataStream >> _desiredRotationalTarget; - dataStream >> _angularTimeScale; - dataStream >> _rotationalTargetSet; - - quint64 serverExpires; - dataStream >> serverExpires; - _expires = serverTimeToLocalTime(serverExpires); - - dataStream >> _tag; - - dataStream >> _otherID; - - _active = true; - }); -} - -void ObjectActionSpring::deserialize(QByteArray serializedArguments) { - QDataStream dataStream(serializedArguments); - - EntityDynamicType type; - dataStream >> type; - assert(type == getType()); - - QUuid id; - dataStream >> id; - assert(id == getID()); - - uint16_t serializationVersion; - dataStream >> serializationVersion; - if (serializationVersion != ObjectActionSpring::springVersion) { - assert(false); - return; - } - - deserializeParameters(serializedArguments, dataStream); -} diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h deleted file mode 100644 index 83a65b36b4..0000000000 --- a/libraries/physics/src/ObjectActionSpring.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// ObjectActionSpring.h -// libraries/physics/src -// -// Created by Seth Alves 2015-6-5 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_ObjectActionSpring_h -#define hifi_ObjectActionSpring_h - -#include "ObjectAction.h" - -class ObjectActionSpring : public ObjectAction { -public: - ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity); - virtual ~ObjectActionSpring(); - - virtual bool updateArguments(QVariantMap arguments) override; - virtual QVariantMap getArguments() override; - - virtual void updateActionWorker(float deltaTimeStep) override; - - virtual QByteArray serialize() const override; - virtual void deserialize(QByteArray serializedArguments) override; - - virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity, - float& linearTimeScale, float& angularTimeScale); - -protected: - static const uint16_t springVersion; - - glm::vec3 _positionalTarget; - glm::vec3 _desiredPositionalTarget; - float _linearTimeScale; - bool _positionalTargetSet; - - glm::quat _rotationalTarget; - glm::quat _desiredRotationalTarget; - float _angularTimeScale; - bool _rotationalTargetSet; - - glm::vec3 _linearVelocityTarget; - glm::vec3 _angularVelocityTarget; - - virtual bool prepareForSpringUpdate(btScalar deltaTimeStep); - - void serializeParameters(QDataStream& dataStream) const; - void deserializeParameters(QByteArray serializedArguments, QDataStream& dataStream); -}; - -#endif // hifi_ObjectActionSpring_h From 9b3754c2ee1bbee14ec810447ad10ad56cefeab4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 15 May 2017 22:13:03 -0700 Subject: [PATCH 099/135] remove unused API from ObjectDynamic and friends --- assignment-client/src/AssignmentDynamic.cpp | 28 ------------ assignment-client/src/AssignmentDynamic.h | 7 --- libraries/physics/src/ObjectDynamic.cpp | 50 --------------------- libraries/physics/src/ObjectDynamic.h | 6 --- 4 files changed, 91 deletions(-) diff --git a/assignment-client/src/AssignmentDynamic.cpp b/assignment-client/src/AssignmentDynamic.cpp index 026bc120bb..7adbd55c39 100644 --- a/assignment-client/src/AssignmentDynamic.cpp +++ b/assignment-client/src/AssignmentDynamic.cpp @@ -53,31 +53,3 @@ QVariantMap AssignmentDynamic::getArguments() { qDebug() << "UNEXPECTED -- AssignmentDynamic::getArguments called in assignment-client."; return QVariantMap(); } - -glm::vec3 AssignmentDynamic::getPosition() { - qDebug() << "UNEXPECTED -- AssignmentDynamic::getPosition called in assignment-client."; - return glm::vec3(0.0f); -} - -glm::quat AssignmentDynamic::getRotation() { - qDebug() << "UNEXPECTED -- AssignmentDynamic::getRotation called in assignment-client."; - return glm::quat(); -} - -glm::vec3 AssignmentDynamic::getLinearVelocity() { - qDebug() << "UNEXPECTED -- AssignmentDynamic::getLinearVelocity called in assignment-client."; - return glm::vec3(0.0f); -} - -void AssignmentDynamic::setLinearVelocity(glm::vec3 linearVelocity) { - qDebug() << "UNEXPECTED -- AssignmentDynamic::setLinearVelocity called in assignment-client."; -} - -glm::vec3 AssignmentDynamic::getAngularVelocity() { - qDebug() << "UNEXPECTED -- AssignmentDynamic::getAngularVelocity called in assignment-client."; - return glm::vec3(0.0f); -} - -void AssignmentDynamic::setAngularVelocity(glm::vec3 angularVelocity) { - qDebug() << "UNEXPECTED -- AssignmentDynamic::setAngularVelocity called in assignment-client."; -} diff --git a/assignment-client/src/AssignmentDynamic.h b/assignment-client/src/AssignmentDynamic.h index 604c418c13..d0fd72533f 100644 --- a/assignment-client/src/AssignmentDynamic.h +++ b/assignment-client/src/AssignmentDynamic.h @@ -39,13 +39,6 @@ private: QByteArray _data; protected: - virtual glm::vec3 getPosition() override; - virtual glm::quat getRotation() override; - virtual glm::vec3 getLinearVelocity() override; - virtual void setLinearVelocity(glm::vec3 linearVelocity) override; - virtual glm::vec3 getAngularVelocity() override; - virtual void setAngularVelocity(glm::vec3 angularVelocity) override; - bool _active; EntityItemWeakPointer _ownerEntity; }; diff --git a/libraries/physics/src/ObjectDynamic.cpp b/libraries/physics/src/ObjectDynamic.cpp index 253739375a..3deadd6468 100644 --- a/libraries/physics/src/ObjectDynamic.cpp +++ b/libraries/physics/src/ObjectDynamic.cpp @@ -160,56 +160,6 @@ btRigidBody* ObjectDynamic::getRigidBody() { return nullptr; } -glm::vec3 ObjectDynamic::getPosition() { - auto rigidBody = getRigidBody(); - if (!rigidBody) { - return glm::vec3(0.0f); - } - return bulletToGLM(rigidBody->getCenterOfMassPosition()); -} - -glm::quat ObjectDynamic::getRotation() { - auto rigidBody = getRigidBody(); - if (!rigidBody) { - return glm::quat(0.0f, 0.0f, 0.0f, 1.0f); - } - return bulletToGLM(rigidBody->getOrientation()); -} - -glm::vec3 ObjectDynamic::getLinearVelocity() { - auto rigidBody = getRigidBody(); - if (!rigidBody) { - return glm::vec3(0.0f); - } - return bulletToGLM(rigidBody->getLinearVelocity()); -} - -void ObjectDynamic::setLinearVelocity(glm::vec3 linearVelocity) { - auto rigidBody = getRigidBody(); - if (!rigidBody) { - return; - } - rigidBody->setLinearVelocity(glmToBullet(glm::vec3(0.0f))); - rigidBody->activate(); -} - -glm::vec3 ObjectDynamic::getAngularVelocity() { - auto rigidBody = getRigidBody(); - if (!rigidBody) { - return glm::vec3(0.0f); - } - return bulletToGLM(rigidBody->getAngularVelocity()); -} - -void ObjectDynamic::setAngularVelocity(glm::vec3 angularVelocity) { - auto rigidBody = getRigidBody(); - if (!rigidBody) { - return; - } - rigidBody->setAngularVelocity(glmToBullet(angularVelocity)); - rigidBody->activate(); -} - void ObjectDynamic::activateBody(bool forceActivation) { auto rigidBody = getRigidBody(); if (rigidBody) { diff --git a/libraries/physics/src/ObjectDynamic.h b/libraries/physics/src/ObjectDynamic.h index 582f47db77..7fdf2e323a 100644 --- a/libraries/physics/src/ObjectDynamic.h +++ b/libraries/physics/src/ObjectDynamic.h @@ -56,12 +56,6 @@ protected: btRigidBody* getOtherRigidBody(EntityItemID otherEntityID); EntityItemPointer getEntityByID(EntityItemID entityID) const; virtual btRigidBody* getRigidBody(); - virtual glm::vec3 getPosition() override; - virtual glm::quat getRotation() override; - virtual glm::vec3 getLinearVelocity() override; - virtual void setLinearVelocity(glm::vec3 linearVelocity) override; - virtual glm::vec3 getAngularVelocity() override; - virtual void setAngularVelocity(glm::vec3 angularVelocity) override; virtual void activateBody(bool forceActivation = false); virtual void forceBodyNonStatic(); From e6accb61b0e8ea69151f9e1fc3008c7315d1e5fe Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 16 May 2017 08:09:20 -0700 Subject: [PATCH 100/135] more purging of legacy 'Spring' action --- libraries/entities/src/EntityDynamicInterface.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index 4bcb0a6027..f424c02e6e 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -50,7 +50,7 @@ A dynamic is a callback which is registered with bullet. A dynamic is called-back every physics -simulation step and can do whatever it wants with the various datastructures it has available. An +simulation step and can do whatever it wants with the various datastructures it has available. A dynamic, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that point by a spring. @@ -103,7 +103,7 @@ EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicT return DYNAMIC_TYPE_OFFSET; } if (normalizedDynamicTypeString == "spring") { - return DYNAMIC_TYPE_SPRING; + return DYNAMIC_TYPE_TRACTOR; } if (normalizedDynamicTypeString == "tractor") { return DYNAMIC_TYPE_TRACTOR; @@ -142,7 +142,6 @@ QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicTyp case DYNAMIC_TYPE_OFFSET: return "offset"; case DYNAMIC_TYPE_SPRING: - return "spring"; case DYNAMIC_TYPE_TRACTOR: return "tractor"; case DYNAMIC_TYPE_HOLD: From d705f529a096edb1840c041e410a57c900028731 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 16 May 2017 08:35:54 -0700 Subject: [PATCH 101/135] use 'tractor' instead of 'spring' in JS files --- script-archive/vrShop/item/shopItemGrab.js | 50 +++++++++---------- .../developer/tests/dynamics/dynamicsTests.js | 12 ++--- .../tutorials/entity_scripts/springHold.js | 2 +- scripts/tutorials/entity_scripts/touch.js | 18 +++---- .../marketplace/boppo/lookAtEntity.js | 2 +- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/script-archive/vrShop/item/shopItemGrab.js b/script-archive/vrShop/item/shopItemGrab.js index 2b63ca1b53..f0488cb497 100644 --- a/script-archive/vrShop/item/shopItemGrab.js +++ b/script-archive/vrShop/item/shopItemGrab.js @@ -1,6 +1,6 @@ // shopItemGrab.js // -// Semplified and coarse version of handControllerGrab.js with the addition of the ownerID concept. +// Simplified and coarse version of handControllerGrab.js with the addition of the ownerID concept. // This grab is the only one which should run in the vrShop. It allows only near grab and add the feature of checking the ownerID. (See shopGrapSwapperEntityScript.js) // @@ -63,8 +63,8 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g // equip // -var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; -var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position +var EQUIP_TRACTOR_SHUTOFF_DISTANCE = 0.05; +var EQUIP_TRACTOR_TIMEFRAME = 0.4; // how quickly objects move to their new position // // other constants @@ -121,7 +121,7 @@ var STATE_EQUIP_SEARCHING = 11; var STATE_EQUIP = 12 var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down var STATE_CONTINUE_EQUIP = 14; -var STATE_EQUIP_SPRING = 16; +var STATE_EQUIP_TRACTOR = 16; function stateToName(state) { @@ -152,8 +152,8 @@ function stateToName(state) { return "continue_equip_bd"; case STATE_CONTINUE_EQUIP: return "continue_equip"; - case STATE_EQUIP_SPRING: - return "state_equip_spring"; + case STATE_EQUIP_TRACTOR: + return "state_equip_tractor"; } return "unknown"; @@ -216,7 +216,7 @@ function MyController(hand) { case STATE_EQUIP: this.nearGrabbing(); break; - case STATE_EQUIP_SPRING: + case STATE_EQUIP_TRACTOR: this.pullTowardEquipPosition() break; case STATE_CONTINUE_NEAR_GRABBING: @@ -404,14 +404,14 @@ function MyController(hand) { return; } else if (!properties.locked) { var ownerObj = getEntityCustomData('ownerKey', intersection.entityID, null); - + if (ownerObj == null || ownerObj.ownerID === MyAvatar.sessionUUID) { //I can only grab new or already mine items this.grabbedEntity = intersection.entityID; if (this.state == STATE_SEARCHING) { this.setState(STATE_NEAR_GRABBING); } else { // equipping if (typeof grabbableData.spatialKey !== 'undefined') { - this.setState(STATE_EQUIP_SPRING); + this.setState(STATE_EQUIP_TRACTOR); } else { this.setState(STATE_EQUIP); } @@ -558,7 +558,7 @@ function MyController(hand) { var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - // use a spring to pull the object to where it will be when equipped + // use a tractor to pull the object to where it will be when equipped var relativeRotation = { x: 0.0, y: 0.0, @@ -582,34 +582,34 @@ function MyController(hand) { var offset = Vec3.multiplyQbyV(targetRotation, relativePosition); var targetPosition = Vec3.sum(handPosition, offset); - if (typeof this.equipSpringID === 'undefined' || - this.equipSpringID === null || - this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, { + if (typeof this.equipTractorID === 'undefined' || + this.equipTractorID === null || + this.equipTractorID === NULL_ACTION_ID) { + this.equipTractorID = Entities.addAction("tractor", this.grabbedEntity, { targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, + linearTimeScale: EQUIP_TRACTOR_TIMEFRAME, targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, + angularTimeScale: EQUIP_TRACTOR_TIMEFRAME, ttl: ACTION_TTL }); - if (this.equipSpringID === NULL_ACTION_ID) { - this.equipSpringID = null; + if (this.equipTractorID === NULL_ACTION_ID) { + this.equipTractorID = null; this.setState(STATE_OFF); return; } } else { - Entities.updateAction(this.grabbedEntity, this.equipSpringID, { + Entities.updateAction(this.grabbedEntity, this.equipTractorID, { targetPosition: targetPosition, - linearTimeScale: EQUIP_SPRING_TIMEFRAME, + linearTimeScale: EQUIP_TRACTOR_TIMEFRAME, targetRotation: targetRotation, - angularTimeScale: EQUIP_SPRING_TIMEFRAME, + angularTimeScale: EQUIP_TRACTOR_TIMEFRAME, ttl: ACTION_TTL }); } - if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { - Entities.deleteAction(this.grabbedEntity, this.equipSpringID); - this.equipSpringID = null; + if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_TRACTOR_SHUTOFF_DISTANCE) { + Entities.deleteAction(this.grabbedEntity, this.equipTractorID); + this.equipTractorID = null; this.setState(STATE_EQUIP); } }; @@ -862,4 +862,4 @@ function cleanup() { } Script.scriptEnding.connect(cleanup); -Script.update.connect(update); \ No newline at end of file +Script.update.connect(update); diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js index 376eff182b..c0b001eab3 100644 --- a/scripts/developer/tests/dynamics/dynamicsTests.js +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -28,7 +28,7 @@ - function coneTwistAndSpringLeverTest(params) { + function coneTwistAndTractorLeverTest(params) { var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: -0.5, z: -2})); var lifetime = params.lifetime; @@ -84,10 +84,10 @@ tag: "cone-twist test" }); - Entities.addAction("spring", leverID, { + Entities.addAction("tractor", leverID, { targetRotation: { x: 0, y: 0, z: 0, w: 1 }, angularTimeScale: 0.2, - tag: "cone-twist test spring" + tag: "cone-twist test tractor" }); @@ -349,11 +349,11 @@ userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" }); - Entities.addAction("spring", headID, { + Entities.addAction("tractor", headID, { targetRotation: { x: 0, y: 0, z: 0, w: 1 }, angularTimeScale: 2.0, otherID: bodyID, - tag: "cone-twist test spring" + tag: "cone-twist test tractor" }); @@ -705,7 +705,7 @@ if (event["dynamics-tests-command"]) { var commandToFunctionMap = { - "cone-twist-and-spring-lever-test": coneTwistAndSpringLeverTest, + "cone-twist-and-tractor-lever-test": coneTwistAndTractorLeverTest, "door-vs-world-test": doorVSWorldTest, "hinge-chain-test": hingeChainTest, "slider-vs-world-test": sliderVSWorldTest, diff --git a/scripts/tutorials/entity_scripts/springHold.js b/scripts/tutorials/entity_scripts/springHold.js index 12b34381a6..059ea2cc6f 100644 --- a/scripts/tutorials/entity_scripts/springHold.js +++ b/scripts/tutorials/entity_scripts/springHold.js @@ -113,4 +113,4 @@ }; return new SpringHold(); -}); \ No newline at end of file +}); diff --git a/scripts/tutorials/entity_scripts/touch.js b/scripts/tutorials/entity_scripts/touch.js index d6a59aa167..1d0586b350 100644 --- a/scripts/tutorials/entity_scripts/touch.js +++ b/scripts/tutorials/entity_scripts/touch.js @@ -2,7 +2,7 @@ // touch.js // - // Sample file using spring action, haptic vibration, and color change to demonstrate two spheres + // Sample file using tractor action, haptic vibration, and color change to demonstrate two spheres // That can give a sense of touch to the holders. // Create two standard spheres, make them grabbable, and attach this entity script. Grab them and touch them together. // @@ -53,7 +53,7 @@ _this = this; } - function updateSpringAction(timescale) { + function updateTractorAction(timescale) { var targetProps = Entities.getEntityProperties(_this.entityID); // // Look for nearby entities to touch @@ -113,7 +113,7 @@ var success = Entities.updateAction(_this.copy, _this.actionID, props); } - function createSpringAction(timescale) { + function createTractorAction(timescale) { var targetProps = Entities.getEntityProperties(_this.entityID); var props = { @@ -123,7 +123,7 @@ angularTimeScale: timescale, ttl: ACTION_TTL }; - _this.actionID = Entities.addAction("spring", _this.copy, props); + _this.actionID = Entities.addAction("tractor", _this.copy, props); return; } @@ -170,7 +170,7 @@ }); } - function deleteSpringAction() { + function deleteTractorAction() { Entities.deleteAction(_this.copy, _this.actionID); } @@ -188,19 +188,19 @@ }, startNearGrab: function(entityID, data) { createCopy(); - createSpringAction(TIMESCALE); + createTractorAction(TIMESCALE); makeOriginalInvisible(); setHand(Entities.getEntityProperties(_this.entityID).position); }, continueNearGrab: function() { - updateSpringAction(TIMESCALE); + updateTractorAction(TIMESCALE); }, releaseGrab: function() { - deleteSpringAction(); + deleteTractorAction(); deleteCopy(); makeOriginalVisible(); } }; return new TouchExample(); -}); \ No newline at end of file +}); diff --git a/unpublishedScripts/marketplace/boppo/lookAtEntity.js b/unpublishedScripts/marketplace/boppo/lookAtEntity.js index ba072814f2..a0e9274db3 100644 --- a/unpublishedScripts/marketplace/boppo/lookAtEntity.js +++ b/unpublishedScripts/marketplace/boppo/lookAtEntity.js @@ -89,7 +89,7 @@ LookAtTarget = function(sourceEntityID) { } }); if (!actionFound) { - Entities.addAction('spring', _sourceEntityID, getNewActionProperties()); + Entities.addAction('tractor', _sourceEntityID, getNewActionProperties()); } } }; From e9c703b14728b3043a3d9f0efc7e0e0e674f0f42 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 16 May 2017 10:34:39 -0700 Subject: [PATCH 102/135] add deprecation log for DYNAMIC_TYPE_SPRING --- interface/src/InterfaceDynamicFactory.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/InterfaceDynamicFactory.cpp b/interface/src/InterfaceDynamicFactory.cpp index 1bb33a7341..e51f63d01b 100644 --- a/interface/src/InterfaceDynamicFactory.cpp +++ b/interface/src/InterfaceDynamicFactory.cpp @@ -32,8 +32,7 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid case DYNAMIC_TYPE_OFFSET: return std::make_shared(id, ownerEntity); case DYNAMIC_TYPE_SPRING: - // the old "Spring" action moved to "Tractor" - return std::make_shared(id, ownerEntity); + qDebug() << "The 'spring' Action is deprecated. Replacing with 'tractor' Action."; case DYNAMIC_TYPE_TRACTOR: return std::make_shared(id, ownerEntity); case DYNAMIC_TYPE_HOLD: From 8eacf0ad4b8579a5f6f2be561002a1020924a45c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 16 May 2017 10:56:33 -0700 Subject: [PATCH 103/135] Remove unnecessary assert in Model.cpp This clause inside was not longer valid - it is a valid state for part->_meshIndex < _modelMeshRenderItems.size(). A better check might be to compare it to the size of _meshStates which we use below, but we use _meshStates.at(...) which will do the bounds checking, which makes the assert unnecessary. --- libraries/render-utils/src/Model.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 766a584b85..3a4ec19d5c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1052,7 +1052,6 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { void Model::computeMeshPartLocalBounds() { for (auto& part : _modelMeshRenderItems) { - assert(part->_meshIndex < _modelMeshRenderItems.size()); const Model::MeshState& state = _meshStates.at(part->_meshIndex); part->computeAdjustedLocalBound(state.clusterMatrices); } From be7a9a2383aaa1cc9b84537458750d53f48acd4f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 16 May 2017 11:00:25 -0700 Subject: [PATCH 104/135] warning fixes --- libraries/animation/src/AnimInverseKinematics.cpp | 6 +++--- libraries/animation/src/AnimInverseKinematics.h | 2 +- libraries/animation/src/IKTarget.cpp | 4 ++-- libraries/animation/src/IKTarget.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 11c143db23..c43d7b76dd 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -23,12 +23,12 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn) : + jointName(jointNameIn), positionVar(positionVarIn), rotationVar(rotationVarIn), typeVar(typeVarIn), weightVar(weightVarIn), weight(weightIn), - jointName(jointNameIn), numFlexCoefficients(flexCoefficientsIn.size()), jointIndex(-1) { @@ -39,12 +39,12 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, cons } AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) : + jointName(orig.jointName), positionVar(orig.positionVar), rotationVar(orig.rotationVar), typeVar(orig.typeVar), weightVar(orig.weightVar), weight(orig.weight), - jointName(orig.jointName), numFlexCoefficients(orig.numFlexCoefficients), jointIndex(orig.jointIndex) { @@ -305,7 +305,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // cache tip absolute position glm::vec3 tipPosition = absolutePoses[tipIndex].trans(); - int chainDepth = 1; + size_t chainDepth = 1; // descend toward root, pivoting each joint to get tip closer to target position while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) { diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index b798319ca9..cf03005b68 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -84,11 +84,11 @@ protected: const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn); IKTargetVar(const IKTargetVar& orig); + QString jointName; QString positionVar; QString rotationVar; QString typeVar; QString weightVar; - QString jointName; float weight; float flexCoefficients[MAX_FLEX_COEFFICIENTS]; size_t numFlexCoefficients; diff --git a/libraries/animation/src/IKTarget.cpp b/libraries/animation/src/IKTarget.cpp index ae23d9548f..18e656f583 100644 --- a/libraries/animation/src/IKTarget.cpp +++ b/libraries/animation/src/IKTarget.cpp @@ -21,10 +21,10 @@ void IKTarget::setFlexCoefficients(size_t numFlexCoefficientsIn, const float* fl } } -float IKTarget::getFlexCoefficient(int chainDepth) const { +float IKTarget::getFlexCoefficient(size_t chainDepth) const { const float DEFAULT_FLEX_COEFFICIENT = 0.5f; - if (chainDepth >= 0 && chainDepth < _numFlexCoefficients) { + if (chainDepth < _numFlexCoefficients) { return _flexCoefficients[chainDepth]; } else { return DEFAULT_FLEX_COEFFICIENT; diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 0e8bb614ea..1951d3ba7b 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -36,7 +36,7 @@ public: void setIndex(int index) { _index = index; } void setType(int); void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn); - float getFlexCoefficient(int chainDepth) const; + float getFlexCoefficient(size_t chainDepth) const; void setWeight(float weight) { _weight = weight; } float getWeight() const { return _weight; } From aa0394c2ef289d160af9da7fceb1e70d6d11d710 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 16 May 2017 10:54:24 -0700 Subject: [PATCH 105/135] Fix case when user stories would be incorrectly deleted --- scripts/system/html/js/SnapshotReview.js | 29 +++++++++++++++++------- scripts/system/snapshot.js | 10 ++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 946e04beef..f962c7b624 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -359,7 +359,7 @@ function showUploadingMessage(selectedID, destination) { shareBarHelp.classList.add("uploading"); shareBarHelp.setAttribute("data-destination", destination); } -function hideUploadingMessageAndShare(selectedID, storyID) { +function hideUploadingMessageAndMaybeShare(selectedID, storyID) { if (selectedID.id) { selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID } @@ -382,21 +382,28 @@ function hideUploadingMessageAndShare(selectedID, storyID) { var facebookButton = document.getElementById(selectedID + "facebookButton"); window.open(facebookButton.getAttribute("href"), "_blank"); shareBarHelp.innerHTML = facebookShareText; + // This emitWebEvent() call isn't necessary in the "hifi" and "blast" cases + // because the "removeFromStoryIDsToMaybeDelete()" call happens + // in snapshot.js when sharing with that method. + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); break; case 'twitter': var twitterButton = document.getElementById(selectedID + "twitterButton"); window.open(twitterButton.getAttribute("href"), "_blank"); shareBarHelp.innerHTML = twitterShareText; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); break; } shareBarHelp.setAttribute("data-destination", ""); - - EventBridge.emitWebEvent(JSON.stringify({ - type: "snapshot", - action: "removeFromStoryIDsToMaybeDelete", - story_id: storyID - })); } } function updateShareInfo(containerID, storyID) { @@ -417,7 +424,7 @@ function updateShareInfo(containerID, storyID) { twitterButton.setAttribute("target", "_blank"); twitterButton.setAttribute("href", 'https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelityinc&hashtags=VR,HiFi'); - hideUploadingMessageAndShare(containerID, storyID); + hideUploadingMessageAndMaybeShare(containerID, storyID); } function blastToConnections(selectedID, isGif) { if (selectedID.id) { @@ -552,6 +559,12 @@ function shareButtonClicked(destination, selectedID) { if (!storyID) { showUploadingMessage(selectedID, destination); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 2a2e816d45..8b5ae3c9a7 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -51,6 +51,11 @@ function openLoginWindow() { } } +function removeFromStoryIDsToMaybeDelete(story_id) { + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1); + print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); +} + function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -191,6 +196,7 @@ function onMessage(message) { return; } else { print("SUCCESS uploading announcement story! Story ID:", response.user_story.id); + removeFromStoryIDsToMaybeDelete(message.story_id); // Don't delete original "for_url" story } }); } @@ -230,13 +236,13 @@ function onMessage(message) { return; } else { print("SUCCESS changing audience" + (message.isAnnouncement ? " and posting announcement!" : "!")); + removeFromStoryIDsToMaybeDelete(message.story_id); } }); } break; case 'removeFromStoryIDsToMaybeDelete': - storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); - print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); + removeFromStoryIDsToMaybeDelete(message.story_id); break; default: print('Unknown message action received by snapshot.js!'); From 733b0feabddacdb68e8f49a42476377f50472cf3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 17 May 2017 08:17:33 +1200 Subject: [PATCH 106/135] Fix attachment data not getting cleared --- libraries/avatars/src/AvatarData.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 26afed152f..32fe4cfef7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2086,14 +2086,16 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble()); } + QVector attachments; if (json.contains(JSON_AVATAR_ATTACHMENTS) && json[JSON_AVATAR_ATTACHMENTS].isArray()) { QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray(); - QVector attachments; for (auto attachmentJson : attachmentsJson) { AttachmentData attachment; attachment.fromJson(attachmentJson.toObject()); attachments.push_back(attachment); } + } + if (attachments != getAttachmentData()) { setAttachmentData(attachments); } From 7897bf8aa873d32235d3f2669b24cf3d8374d7b5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 17 May 2017 08:17:55 +1200 Subject: [PATCH 107/135] Fix display name not getting cleared --- libraries/avatars/src/AvatarData.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 32fe4cfef7..bee1168dcc 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2052,11 +2052,13 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { setSkeletonModelURL(bodyModelURL); } } + + QString newDisplayName = ""; if (json.contains(JSON_AVATAR_DISPLAY_NAME)) { - auto newDisplayName = json[JSON_AVATAR_DISPLAY_NAME].toString(); - if (newDisplayName != getDisplayName()) { - setDisplayName(newDisplayName); - } + newDisplayName = json[JSON_AVATAR_DISPLAY_NAME].toString(); + } + if (newDisplayName != getDisplayName()) { + setDisplayName(newDisplayName); } auto currentBasis = getRecordingBasis(); From e3cd85bd22b653291e6be414e0985dc503b03fd3 Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Tue, 16 May 2017 13:55:04 -0700 Subject: [PATCH 108/135] Changed calibration time from 2 to 1 sec --- plugins/openvr/src/ViveControllerManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 5e22272dd6..3bda481243 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -40,7 +40,7 @@ void releaseOpenVrSystem(); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; -const quint64 CALIBRATION_TIMELAPSE = 2 * USECS_PER_SECOND; +const quint64 CALIBRATION_TIMELAPSE = 1 * USECS_PER_SECOND; static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; From 201f81f20ad5b2bba3b05d6f9c007d74354d753c Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 10 May 2017 19:40:29 -0700 Subject: [PATCH 109/135] Don't wait on the sandbox --- interface/src/Application.cpp | 261 ++++++++++------------ interface/src/Application.h | 14 +- interface/src/main.cpp | 70 +++--- libraries/networking/src/SandboxUtils.cpp | 73 +++--- libraries/networking/src/SandboxUtils.h | 19 +- libraries/shared/src/RunningMarker.cpp | 6 +- libraries/shared/src/RunningMarker.h | 5 +- 7 files changed, 204 insertions(+), 244 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f4699009ac..3a09a2181c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -563,11 +563,8 @@ const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false; -Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : +Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : QApplication(argc, argv), - _shouldRunServer(runServer), - _runServerPath(runServerPathOption), - _runningMarker(this, RUNNING_MARKER_FILENAME), _window(new MainWindow(desktop())), _sessionRunTimer(startupTimer), _previousSessionCrashed(setupEssentials(argc, argv)), @@ -622,8 +619,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // make sure the debug draw singleton is initialized on the main thread. DebugDraw::getInstance().removeMarker(""); - _runningMarker.startRunningMarker(); - PluginContainer* pluginContainer = dynamic_cast(this); // set the container for any plugins that care PluginManager::getInstance()->setContainer(pluginContainer); @@ -675,38 +670,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo static const QString OCULUS_STORE_ARG = "--oculus-store"; setProperty(hifi::properties::OCULUS_STORE, arguments().indexOf(OCULUS_STORE_ARG) != -1); - static const QString NO_UPDATER_ARG = "--no-updater"; - static const bool noUpdater = arguments().indexOf(NO_UPDATER_ARG) != -1; - static const bool wantsSandboxRunning = shouldRunServer(); - static bool determinedSandboxState = false; - static bool sandboxIsRunning = false; - SandboxUtils sandboxUtils; - // updateHeartbeat() because we are going to poll shortly... updateHeartbeat(); - sandboxUtils.ifLocalSandboxRunningElse([&]() { - qCDebug(interfaceapp) << "Home sandbox appears to be running....."; - determinedSandboxState = true; - sandboxIsRunning = true; - }, [&]() { - qCDebug(interfaceapp) << "Home sandbox does not appear to be running...."; - if (wantsSandboxRunning) { - QString contentPath = getRunServerPath(); - SandboxUtils::runLocalSandbox(contentPath, true, RUNNING_MARKER_FILENAME, noUpdater); - sandboxIsRunning = true; - } - determinedSandboxState = true; - }); - - // SandboxUtils::runLocalSandbox currently has 2 sec delay after spawning sandbox, so 4 - // sec here is ok I guess. TODO: ping sandbox so we know it is up, perhaps? - quint64 MAX_WAIT_TIME = USECS_PER_SECOND * 4; - auto startWaiting = usecTimestampNow(); - while (!determinedSandboxState && (usecTimestampNow() - startWaiting <= MAX_WAIT_TIME)) { - QCoreApplication::processEvents(); - // updateHeartbeat() while polling so we don't scare the deadlock watchdog - updateHeartbeat(); - usleep(USECS_PER_MSEC * 50); // 20hz - } // start the nodeThread so its event loop is running QThread* nodeThread = new QThread(this); @@ -1223,6 +1187,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo #endif // If launched from Steam, let it handle updates + const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater"; + bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1; if (!noUpdater) { auto applicationUpdater = DependencyManager::get(); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); @@ -1465,110 +1431,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const auto testScript = property(hifi::properties::TEST).toUrl(); scriptEngines->loadScript(testScript, false); } else { - enum HandControllerType { - Vive, - Oculus - }; - static const std::map MIN_CONTENT_VERSION = { - { Vive, 1 }, - { Oculus, 27 } - }; - - // Get sandbox content set version - auto acDirPath = PathUtils::getAppDataPath() + "../../" + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/"; - auto contentVersionPath = acDirPath + "content-version.txt"; - qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version"; - int contentVersion = 0; - QFile contentVersionFile(contentVersionPath); - if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - QString line = contentVersionFile.readAll(); - contentVersion = line.toInt(); // returns 0 if conversion fails - } - - // Get controller availability - bool hasHandControllers = false; - HandControllerType handControllerType = Vive; - if (PluginUtils::isViveControllerAvailable()) { - hasHandControllers = true; - handControllerType = Vive; - } else if (PluginUtils::isOculusTouchControllerAvailable()) { - hasHandControllers = true; - handControllerType = Oculus; - } - - // Check tutorial content versioning - bool hasTutorialContent = contentVersion >= MIN_CONTENT_VERSION.at(handControllerType); - - // Check HMD use (may be technically available without being in use) - bool hasHMD = PluginUtils::isHMDAvailable(); - bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd(); - - Setting::Handle tutorialComplete { "tutorialComplete", false }; - Setting::Handle firstRun { Settings::firstRun, true }; - - bool isTutorialComplete = tutorialComplete.get(); - bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete; - - qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD; - qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent << - ", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial; - - // when --url in command line, teleport to location - const QString HIFI_URL_COMMAND_LINE_KEY = "--url"; - int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY); - QString addressLookupString; - if (urlIndex != -1) { - addressLookupString = arguments().value(urlIndex + 1); - } - - const QString TUTORIAL_PATH = "/tutorial_begin"; - - if (shouldGoToTutorial) { - if (sandboxIsRunning) { - qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; - DependencyManager::get()->goToLocalSandbox(TUTORIAL_PATH); - } else { - qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; - if (firstRun.get()) { - showHelp(); - } - if (addressLookupString.isEmpty()) { - DependencyManager::get()->goToEntry(); - } else { - DependencyManager::get()->loadSettings(addressLookupString); - } - } - } else { - - bool isFirstRun = firstRun.get(); - - if (isFirstRun) { - showHelp(); - } - - // If this is a first run we short-circuit the address passed in - if (isFirstRun) { - if (isUsingHMD) { - if (sandboxIsRunning) { - qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; - DependencyManager::get()->goToLocalSandbox(); - } else { - qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; - DependencyManager::get()->goToEntry(); - } - } else { - DependencyManager::get()->goToEntry(); - } - } else { - qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); - DependencyManager::get()->loadSettings(addressLookupString); - } - } - - _connectionMonitor.init(); - - // After all of the constructor is completed, then set firstRun to false. - firstRun.set(false); + PROFILE_RANGE(render, "GetSandboxStatus"); + auto reply = SandboxUtils::getStatus(); + connect(reply, &QNetworkReply::finished, this, [=] { + handleSandboxStatus(reply); + }); } // Monitor model assets (e.g., from Clara.io) added to the world that may need resizing. @@ -2474,6 +2341,118 @@ void Application::resizeGL() { } } +void Application::handleSandboxStatus(QNetworkReply* reply) { + PROFILE_RANGE(render, "HandleSandboxStatus"); + + bool sandboxIsRunning = SandboxUtils::readStatus(reply->readAll()); + qDebug() << "HandleSandboxStatus" << sandboxIsRunning; + + enum HandControllerType { + Vive, + Oculus + }; + static const std::map MIN_CONTENT_VERSION = { + { Vive, 1 }, + { Oculus, 27 } + }; + + // Get sandbox content set version + auto acDirPath = PathUtils::getAppDataPath() + "../../" + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/"; + auto contentVersionPath = acDirPath + "content-version.txt"; + qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version"; + int contentVersion = 0; + QFile contentVersionFile(contentVersionPath); + if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QString line = contentVersionFile.readAll(); + contentVersion = line.toInt(); // returns 0 if conversion fails + } + + // Get controller availability + bool hasHandControllers = false; + HandControllerType handControllerType = Vive; + if (PluginUtils::isViveControllerAvailable()) { + hasHandControllers = true; + handControllerType = Vive; + } else if (PluginUtils::isOculusTouchControllerAvailable()) { + hasHandControllers = true; + handControllerType = Oculus; + } + + // Check tutorial content versioning + bool hasTutorialContent = contentVersion >= MIN_CONTENT_VERSION.at(handControllerType); + + // Check HMD use (may be technically available without being in use) + bool hasHMD = PluginUtils::isHMDAvailable(); + bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd(); + + Setting::Handle tutorialComplete{ "tutorialComplete", false }; + Setting::Handle firstRun{ Settings::firstRun, true }; + + bool isTutorialComplete = tutorialComplete.get(); + bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete; + + qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD; + qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent << + ", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial; + + // when --url in command line, teleport to location + const QString HIFI_URL_COMMAND_LINE_KEY = "--url"; + int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY); + QString addressLookupString; + if (urlIndex != -1) { + addressLookupString = arguments().value(urlIndex + 1); + } + + const QString TUTORIAL_PATH = "/tutorial_begin"; + + if (shouldGoToTutorial) { + if (sandboxIsRunning) { + qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; + DependencyManager::get()->goToLocalSandbox(TUTORIAL_PATH); + } else { + qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; + if (firstRun.get()) { + showHelp(); + } + if (addressLookupString.isEmpty()) { + DependencyManager::get()->goToEntry(); + } else { + DependencyManager::get()->loadSettings(addressLookupString); + } + } + } else { + + bool isFirstRun = firstRun.get(); + + if (isFirstRun) { + showHelp(); + } + + // If this is a first run we short-circuit the address passed in + if (isFirstRun) { + if (isUsingHMD) { + if (sandboxIsRunning) { + qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; + DependencyManager::get()->goToLocalSandbox(); + } else { + qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; + DependencyManager::get()->goToEntry(); + } + } else { + DependencyManager::get()->goToEntry(); + } + } else { + qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); + DependencyManager::get()->loadSettings(addressLookupString); + } + } + + _connectionMonitor.init(); + + // After all of the constructor is completed, then set firstRun to false. + firstRun.set(false); +} + bool Application::importJSONFromURL(const QString& urlString) { // we only load files that terminate in just .json (not .svo.json and not .ava.json) // if they come from the High Fidelity Marketplace Assets CDN diff --git a/interface/src/Application.h b/interface/src/Application.h index 5027c58349..5cf3580c09 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -112,17 +112,7 @@ class Application : public QApplication, // TODO? Get rid of those friend class OctreePacketProcessor; -private: - bool _shouldRunServer { false }; - QString _runServerPath; - RunningMarker _runningMarker; - public: - // startup related getter/setters - bool shouldRunServer() const { return _shouldRunServer; } - bool hasRunServerPath() const { return !_runServerPath.isEmpty(); } - QString getRunServerPath() const { return _runServerPath; } - // virtual functions required for PluginContainer virtual ui::Menu* getPrimaryMenu() override; virtual void requestReset() override { resetSensors(true); } @@ -146,7 +136,7 @@ public: static void initPlugins(const QStringList& arguments); static void shutdownPlugins(); - Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runServer, QString runServerPathOption); + Application(int& argc, char** argv, QElapsedTimer& startup_time); ~Application(); void postLambdaEvent(std::function f) override; @@ -452,6 +442,8 @@ private slots: void addAssetToWorldInfoTimeout(); void addAssetToWorldErrorTimeout(); + void handleSandboxStatus(QNetworkReply* reply); + private: static void initDisplay(); void init(); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 99dbb1a28e..3430ffbd15 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include +#include #include @@ -28,7 +30,6 @@ #include "InterfaceLogging.h" #include "UserActivityLogger.h" #include "MainWindow.h" -#include #ifdef HAS_BUGSPLAT #include @@ -50,50 +51,49 @@ int main(int argc, const char* argv[]) { disableQtBearerPoll(); // Fixes wifi ping spikes + QElapsedTimer startupTime; + startupTime.start(); + // Set application infos QCoreApplication::setApplicationName(BuildInfo::INTERFACE_NAME); QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - const QString& applicationName = getInterfaceSharedMemoryName(); - - bool instanceMightBeRunning = true; - QStringList arguments; for (int i = 0; i < argc; ++i) { arguments << argv[i]; } - -#ifdef Q_OS_WIN - // Try to create a shared memory block - if it can't be created, there is an instance of - // interface already running. We only do this on Windows for now because of the potential - // for crashed instances to leave behind shared memory instances on unix. - QSharedMemory sharedMemory { applicationName }; - instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly); -#endif - - // allow multiple interfaces to run if this environment variable is set. - if (QProcessEnvironment::systemEnvironment().contains("HIFI_ALLOW_MULTIPLE_INSTANCES")) { - instanceMightBeRunning = false; - } - QCommandLineParser parser; + QCommandLineOption urlOption("url", "", "value"); + QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater"); QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications"); QCommandLineOption runServerOption("runServer", "Whether to run the server"); QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); + parser.addOption(urlOption); + parser.addOption(noUpdaterOption); parser.addOption(checkMinSpecOption); parser.addOption(runServerOption); parser.addOption(serverContentPathOption); parser.addOption(allowMultipleInstancesOption); parser.parse(arguments); - bool runServer = parser.isSet(runServerOption); - bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); - QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); - bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption); + + const QString& applicationName = getInterfaceSharedMemoryName(); + bool instanceMightBeRunning = true; +#ifdef Q_OS_WIN + // Try to create a shared memory block - if it can't be created, there is an instance of + // interface already running. We only do this on Windows for now because of the potential + // for crashed instances to leave behind shared memory instances on unix. + QSharedMemory sharedMemory{ applicationName }; + instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly); +#endif + + // allow multiple interfaces to run if this environment variable is set. + bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption) || + QProcessEnvironment::systemEnvironment().contains("HIFI_ALLOW_MULTIPLE_INSTANCES"); if (allowMultipleInstances) { instanceMightBeRunning = false; } @@ -108,11 +108,6 @@ int main(int argc, const char* argv[]) { // Try to connect - if we can't connect, interface has probably just gone down if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) { - QCommandLineParser parser; - QCommandLineOption urlOption("url", "", "value"); - parser.addOption(urlOption); - parser.process(arguments); - if (parser.isSet(urlOption)) { QUrl url = QUrl(parser.value(urlOption)); if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) { @@ -156,9 +151,6 @@ int main(int argc, const char* argv[]) { } } - QElapsedTimer startupTime; - startupTime.start(); - // Debug option to demonstrate that the client's local time does not // need to be in sync with any other network node. This forces clock // skew for the individual client @@ -199,7 +191,21 @@ int main(int argc, const char* argv[]) { int exitCode; { - Application app(argc, const_cast(argv), startupTime, runServer, serverContentPathOptionValue); + RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME); + runningMarker.writeRunningMarkerFile(); + + bool noUpdater = parser.isSet(noUpdaterOption); + bool runServer = parser.isSet(runServerOption); + bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); + QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); + if (runServer) { + SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater); + } + + Application app(argc, const_cast(argv), startupTime); + + // Now that the main event loop is setup, launch running marker thread + runningMarker.startRunningMarker(); // If we failed the OpenGLVersion check, log it. if (override) { diff --git a/libraries/networking/src/SandboxUtils.cpp b/libraries/networking/src/SandboxUtils.cpp index bf17a0b1e4..d816f7ebee 100644 --- a/libraries/networking/src/SandboxUtils.cpp +++ b/libraries/networking/src/SandboxUtils.cpp @@ -9,63 +9,52 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include -#include +#include "SandboxUtils.h" + #include #include #include #include -#include -#include -#include #include #include #include -#include "SandboxUtils.h" #include "NetworkAccessManager.h" #include "NetworkLogging.h" +namespace SandboxUtils { -void SandboxUtils::ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, - std::function localSandboxNotRunningDoThat) { - - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); +QNetworkReply* getStatus() { + auto& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(sandboxStatus); - - connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() { - if (reply->error() == QNetworkReply::NoError) { - auto statusData = reply->readAll(); - auto statusJson = QJsonDocument::fromJson(statusData); - if (!statusJson.isEmpty()) { - auto statusObject = statusJson.object(); - auto serversValue = statusObject.value("servers"); - if (!serversValue.isUndefined() && serversValue.isObject()) { - auto serversObject = serversValue.toObject(); - auto serversCount = serversObject.size(); - const int MINIMUM_EXPECTED_SERVER_COUNT = 5; - if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) { - localSandboxRunningDoThis(); - return; - } - } - } - } - localSandboxNotRunningDoThat(); - }); + return networkAccessManager.get(sandboxStatus); } +bool readStatus(QByteArray statusData) { + auto statusJson = QJsonDocument::fromJson(statusData); -void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) { - QString applicationDirPath = QFileInfo(QCoreApplication::applicationFilePath()).path(); - QString serverPath = applicationDirPath + "/server-console/server-console.exe"; - qCDebug(networking) << "Application dir path is: " << applicationDirPath; + if (!statusJson.isEmpty()) { + auto statusObject = statusJson.object(); + auto serversValue = statusObject.value("servers"); + if (!serversValue.isUndefined() && serversValue.isObject()) { + auto serversObject = serversValue.toObject(); + auto serversCount = serversObject.size(); + const int MINIMUM_EXPECTED_SERVER_COUNT = 5; + if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) { + return true; + } + } + } + + return false; +} + +void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) { + QString serverPath = "./server-console/server-console.exe"; + qCDebug(networking) << "Running marker path is: " << runningMarkerName; qCDebug(networking) << "Server path is: " << serverPath; qCDebug(networking) << "autoShutdown: " << autoShutdown; qCDebug(networking) << "noUpdater: " << noUpdater; @@ -80,7 +69,7 @@ void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QStri } if (hasContentPath) { - QString serverContentPath = applicationDirPath + "/" + contentPath; + QString serverContentPath = "./" + contentPath; args << "--contentPath" << serverContentPath; } @@ -93,10 +82,8 @@ void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QStri args << "--noUpdater"; } - qCDebug(networking) << applicationDirPath; qCDebug(networking) << "Launching sandbox with:" << args; qCDebug(networking) << QProcess::startDetached(serverPath, args); - - // Sleep a short amount of time to give the server a chance to start - usleep(2000000); /// do we really need this?? +} + } diff --git a/libraries/networking/src/SandboxUtils.h b/libraries/networking/src/SandboxUtils.h index aaceafb9ef..42484b8edf 100644 --- a/libraries/networking/src/SandboxUtils.h +++ b/libraries/networking/src/SandboxUtils.h @@ -12,21 +12,16 @@ #ifndef hifi_SandboxUtils_h #define hifi_SandboxUtils_h -#include -#include +#include +class QNetworkReply; -const QString SANDBOX_STATUS_URL = "http://localhost:60332/status"; +namespace SandboxUtils { + const QString SANDBOX_STATUS_URL = "http://localhost:60332/status"; -class SandboxUtils : public QObject { - Q_OBJECT -public: - /// determines if the local sandbox is likely running. It does not account for custom setups, and is only - /// intended to detect the standard local sandbox install. - void ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, - std::function localSandboxNotRunningDoThat); - - static void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater); + QNetworkReply* getStatus(); + bool readStatus(QByteArray statusData); + void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater); }; #endif // hifi_SandboxUtils_h diff --git a/libraries/shared/src/RunningMarker.cpp b/libraries/shared/src/RunningMarker.cpp index 89fb3ada23..f8aaee42df 100644 --- a/libraries/shared/src/RunningMarker.cpp +++ b/libraries/shared/src/RunningMarker.cpp @@ -33,11 +33,11 @@ void RunningMarker::startRunningMarker() { _runningMarkerThread->setObjectName("Running Marker Thread"); _runningMarkerThread->start(); - writeRunningMarkerFiler(); // write the first file, even before timer + writeRunningMarkerFile(); // write the first file, even before timer _runningMarkerTimer = new QTimer(); QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){ - writeRunningMarkerFiler(); + writeRunningMarkerFile(); }); _runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS); @@ -53,7 +53,7 @@ RunningMarker::~RunningMarker() { _runningMarkerThread->deleteLater(); } -void RunningMarker::writeRunningMarkerFiler() { +void RunningMarker::writeRunningMarkerFile() { QFile runningMarkerFile(getFilePath()); // always write, even it it exists, so that it touches the files diff --git a/libraries/shared/src/RunningMarker.h b/libraries/shared/src/RunningMarker.h index 16763d27e6..1137dbf5fa 100644 --- a/libraries/shared/src/RunningMarker.h +++ b/libraries/shared/src/RunningMarker.h @@ -27,10 +27,11 @@ public: QString getFilePath(); static QString getMarkerFilePath(QString name); -protected: - void writeRunningMarkerFiler(); + + void writeRunningMarkerFile(); void deleteRunningMarkerFile(); +private: QObject* _parent { nullptr }; QString _name; QThread* _runningMarkerThread { nullptr }; From c799ddc9276b207d14e778d12920076c1ebf349f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 16 May 2017 14:53:54 -0700 Subject: [PATCH 110/135] CR --- assignment-client/src/audio/AudioMixer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 98eca60e85..bb03a6ec93 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -38,14 +38,14 @@ #include "AudioMixer.h" static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance) -static const int DEFAULT_NUM_STATIC_JITTER_FRAMES = -1; +static const int DISABLE_STATIC_JITTER_FRAMES = -1; static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f; static const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer"; static const QString AUDIO_ENV_GROUP_KEY = "audio_env"; static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer"; static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading"; -int AudioMixer::_numStaticJitterFrames{ DEFAULT_NUM_STATIC_JITTER_FRAMES }; +int AudioMixer::_numStaticJitterFrames{ DISABLE_STATIC_JITTER_FRAMES }; float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD }; float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE }; std::map> AudioMixer::_availableCodecs{ }; @@ -238,7 +238,7 @@ void AudioMixer::sendStatsPacket() { } // general stats - statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == DEFAULT_NUM_STATIC_JITTER_FRAMES; + statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == DISABLE_STATIC_JITTER_FRAMES; statsObject["threads"] = _slavePool.numThreads(); @@ -497,7 +497,7 @@ int AudioMixer::prepareFrame(const SharedNodePointer& node, unsigned int frame) } void AudioMixer::clearDomainSettings() { - _numStaticJitterFrames = DEFAULT_NUM_STATIC_JITTER_FRAMES; + _numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES; _attenuationPerDoublingInDistance = DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE; _noiseMutingThreshold = DEFAULT_NOISE_MUTING_THRESHOLD; _codecPreferenceOrder.clear(); @@ -541,7 +541,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) { qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames; } else { qDebug() << "Disabling dynamic jitter buffers."; - _numStaticJitterFrames = DEFAULT_NUM_STATIC_JITTER_FRAMES; + _numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES; } // check for deprecated audio settings From bfd1274ed3ebbd7982683a013fa4177d5225f6e3 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 16 May 2017 10:10:11 -0700 Subject: [PATCH 111/135] Fix progressive loading of cubemaps In various places we were using the KTX _imageSize as the size of the entire mip level, when it is actually the size of the face for cubemaps. --- libraries/gpu/src/gpu/Texture_ktx.cpp | 7 ++++--- libraries/ktx/src/ktx/KTX.h | 19 +++++++++++-------- libraries/ktx/src/ktx/Writer.cpp | 9 ++++++--- .../src/model-networking/TextureCache.cpp | 8 ++++---- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 524fd0a88c..85fd5f044c 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -238,8 +238,9 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor throw std::runtime_error("Invalid level"); } - if (storage->size() != _ktxDescriptor->images[level]._imageSize) { - qWarning() << "Invalid image size: " << storage->size() << ", expected: " << _ktxDescriptor->images[level]._imageSize + auto& imageDesc = _ktxDescriptor->images[level]; + if (storage->size() != imageDesc._fullImageSize) { + qWarning() << "Invalid image size: " << storage->size() << ", expected: " << imageDesc._fullImageSize << ", level: " << level << ", filename: " << QString::fromStdString(_filename); return; } @@ -258,7 +259,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor return; } - memcpy(imageData, storage->data(), _ktxDescriptor->images[level]._imageSize); + memcpy(imageData, storage->data(), storage->size()); _minMipLevelAvailable = level; if (_offsetToMinMipKV > 0) { auto minMipKeyData = file->mutableData() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV; diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 656cf93f34..69a34d3507 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -38,15 +38,15 @@ UInt32 numberOfArrayElements UInt32 numberOfFaces UInt32 numberOfMipmapLevels UInt32 bytesOfKeyValueData - + for each keyValuePair that fits in bytesOfKeyValueData UInt32 keyAndValueByteSize Byte keyAndValue[keyAndValueByteSize] Byte valuePadding[3 - ((keyAndValueByteSize + 3) % 4)] end - + for each mipmap_level in numberOfMipmapLevels* - UInt32 imageSize; + UInt32 imageSize; for each array_element in numberOfArrayElements* for each face in numberOfFaces for each z_slice in pixelDepth* @@ -269,7 +269,7 @@ namespace ktx { COMPRESSED_RG11_EAC = 0x9272, COMPRESSED_SIGNED_RG11_EAC = 0x9273, }; - + enum class GLBaseInternalFormat : uint32_t { // GL 4.4 Table 8.11 DEPTH_COMPONENT = 0x1902, @@ -419,17 +419,20 @@ namespace ktx { using FaceOffsets = std::vector; using FaceBytes = std::vector; + const uint32_t _numFaces; // This is the byte offset from the _start_ of the image region. For example, level 0 // will have a byte offset of 0. - const uint32_t _numFaces; const size_t _imageOffset; const uint32_t _imageSize; + // The full size of this image / mip level. This will be equivalent to _imageSize except when _numFaces > 1 + const uint32_t _fullImageSize; const uint32_t _faceSize; const uint32_t _padding; ImageHeader(bool cube, size_t imageOffset, uint32_t imageSize, uint32_t padding) : _numFaces(cube ? NUM_CUBEMAPFACES : 1), _imageOffset(imageOffset), - _imageSize(imageSize * _numFaces), + _imageSize(imageSize), + _fullImageSize(imageSize * _numFaces), _faceSize(imageSize), _padding(padding) { } @@ -466,7 +469,7 @@ namespace ktx { class KTX; - // A KTX descriptor is a lightweight container for all the information about a serialized KTX file, but without the + // A KTX descriptor is a lightweight container for all the information about a serialized KTX file, but without the // actual image / face data available. struct KTXDescriptor { KTXDescriptor(const Header& header, const KeyValues& keyValues, const ImageDescriptors& imageDescriptors) : header(header), keyValues(keyValues), images(imageDescriptors) {} @@ -495,7 +498,7 @@ namespace ktx { // Instead of creating a full Copy of the src data in a KTX object, the write serialization can be performed with the // following two functions // size_t sizeNeeded = KTX::evalStorageSize(header, images); - // + // // //allocate a buffer of size "sizeNeeded" or map a file with enough capacity // Byte* destBytes = new Byte[sizeNeeded]; // diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index 50f63767a0..3afe705499 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -89,7 +89,7 @@ namespace ktx { for (uint32_t l = 0; l < numMips; l++) { if (imageDescriptors.size() > l) { storageSize += sizeof(uint32_t); - storageSize += imageDescriptors[l]._imageSize; + storageSize += imageDescriptors[l]._fullImageSize; storageSize += Header::evalPadding(imageDescriptors[l]._imageSize); } } @@ -127,6 +127,7 @@ namespace ktx { size_t KTX::writeWithoutImages(Byte* destBytes, size_t destByteSize, const Header& header, const ImageDescriptors& descriptors, const KeyValues& keyValues) { // Check again that we have enough destination capacity if (!destBytes || (destByteSize < evalStorageSize(header, descriptors, keyValues))) { + qWarning() << "Destination capacity is insufficient to write KTX without images"; return 0; } @@ -149,13 +150,15 @@ namespace ktx { for (size_t i = 0; i < descriptors.size(); ++i) { auto ptr = reinterpret_cast(currentDestPtr); *ptr = descriptors[i]._imageSize; - ptr++; + #ifdef DEBUG + ptr++; for (size_t k = 0; k < descriptors[i]._imageSize/4; k++) { *(ptr + k) = 0xFFFFFFFF; } #endif - currentDestPtr += descriptors[i]._imageSize + sizeof(uint32_t); + currentDestPtr += sizeof(uint32_t); + currentDestPtr += descriptors[i]._fullImageSize; } return destByteSize; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 47fc62a9b5..79b179f3b9 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -648,13 +648,13 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() { // TODO Move image offset calculation to ktx ImageDescriptor for (int level = static_cast(images.size()) - 1; level >= 0; --level) { auto& image = images[level]; - if (image._imageSize > imageSizeRemaining) { + if (image._fullImageSize > imageSizeRemaining) { break; } - ktxData -= image._imageSize; - texture->assignStoredMip(static_cast(level), image._imageSize, ktxData); + ktxData -= image._fullImageSize; + texture->assignStoredMip(static_cast(level), image._fullImageSize, ktxData); ktxData -= ktx::IMAGE_SIZE_WIDTH; - imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH); + imageSizeRemaining -= (image._fullImageSize + ktx::IMAGE_SIZE_WIDTH); } // We replace the texture with the one stored in the cache. This deals with the possible race condition of two different From 7225326bfc0bce18993eac5f8f37ae4f98cc8855 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 16 May 2017 10:11:07 -0700 Subject: [PATCH 112/135] Fix failed mip insertions not being handled by TextureCache --- .../src/model-networking/TextureCache.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 79b179f3b9..2880762987 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -501,12 +501,19 @@ void NetworkTexture::ktxMipRequestFinished() { if (texture) { texture->assignStoredMip(_ktxMipLevelRangeInFlight.first, _ktxMipRequest->getData().size(), reinterpret_cast(_ktxMipRequest->getData().data())); - _lowestKnownPopulatedMip = _textureSource->getGPUTexture()->minAvailableMipLevel(); + + if (texture->minAvailableMipLevel() <= _ktxMipLevelRangeInFlight.first) { + _lowestKnownPopulatedMip = texture->minAvailableMipLevel(); + _ktxResourceState = WAITING_FOR_MIP_REQUEST; + } else { + qWarning(networking) << "Failed to load mip: " << _url << ":" << _ktxMipLevelRangeInFlight.first; + _ktxResourceState = FAILED_TO_LOAD; + } } else { + _ktxResourceState = WAITING_FOR_MIP_REQUEST; qWarning(networking) << "Trying to update mips but texture is null"; } finishedLoading(true); - _ktxResourceState = WAITING_FOR_MIP_REQUEST; } else { finishedLoading(false); if (handleFailedRequest(_ktxMipRequest->getResult())) { From 5a742002e26738023f1b41a4cea75a460eb53665 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Tue, 16 May 2017 23:39:38 +0100 Subject: [PATCH 113/135] change default particle values --- scripts/system/edit.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index b06aeb2320..a6cbca008f 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -504,26 +504,26 @@ var toolBar = (function () { blue: 0 }, emitAcceleration: { - x: 0, - y: 5, - z: 0 + x: -0.5, + y: 2.5, + z: -0.5 }, accelerationSpread: { - x: 1, - y: 0, - z: 1 + x: 0.5, + y: 1, + z: 0.5 }, - emitRate: 3, + emitRate: 5.5, emitSpeed: 0, speedSpread: 0, - lifespan: 1, + lifespan: 1.5, maxParticles: 10, particleRadius: 0.25, radiusStart: 0, radiusFinish: 0.1, radiusSpread: 0, alpha: 0, - alphaStart: 0.1, + alphaStart: 1, alphaFinish: 0, polarStart: 0, polarFinish: 0, From e0212ac32bc824de8a2345f054b6e1b7a500695f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 16 May 2017 15:53:07 -0700 Subject: [PATCH 114/135] Remove _fullImageSize from ktx header --- libraries/gpu/src/gpu/Texture_ktx.cpp | 4 ++-- libraries/ktx/src/ktx/KTX.h | 5 +---- libraries/ktx/src/ktx/Writer.cpp | 4 ++-- .../src/model-networking/TextureCache.cpp | 8 ++++---- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 85fd5f044c..e033355514 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -239,8 +239,8 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor } auto& imageDesc = _ktxDescriptor->images[level]; - if (storage->size() != imageDesc._fullImageSize) { - qWarning() << "Invalid image size: " << storage->size() << ", expected: " << imageDesc._fullImageSize + if (storage->size() != imageDesc._imageSize) { + qWarning() << "Invalid image size: " << storage->size() << ", expected: " << imageDesc._imageSize << ", level: " << level << ", filename: " << QString::fromStdString(_filename); return; } diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 69a34d3507..3f220abac3 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -424,15 +424,12 @@ namespace ktx { // will have a byte offset of 0. const size_t _imageOffset; const uint32_t _imageSize; - // The full size of this image / mip level. This will be equivalent to _imageSize except when _numFaces > 1 - const uint32_t _fullImageSize; const uint32_t _faceSize; const uint32_t _padding; ImageHeader(bool cube, size_t imageOffset, uint32_t imageSize, uint32_t padding) : _numFaces(cube ? NUM_CUBEMAPFACES : 1), _imageOffset(imageOffset), - _imageSize(imageSize), - _fullImageSize(imageSize * _numFaces), + _imageSize(imageSize * _numFaces), _faceSize(imageSize), _padding(padding) { } diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index 3afe705499..2dcd0ea583 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -89,7 +89,7 @@ namespace ktx { for (uint32_t l = 0; l < numMips; l++) { if (imageDescriptors.size() > l) { storageSize += sizeof(uint32_t); - storageSize += imageDescriptors[l]._fullImageSize; + storageSize += imageDescriptors[l]._imageSize; storageSize += Header::evalPadding(imageDescriptors[l]._imageSize); } } @@ -158,7 +158,7 @@ namespace ktx { } #endif currentDestPtr += sizeof(uint32_t); - currentDestPtr += descriptors[i]._fullImageSize; + currentDestPtr += descriptors[i]._imageSize; } return destByteSize; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 2880762987..8683d56b6b 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -655,13 +655,13 @@ void NetworkTexture::maybeHandleFinishedInitialLoad() { // TODO Move image offset calculation to ktx ImageDescriptor for (int level = static_cast(images.size()) - 1; level >= 0; --level) { auto& image = images[level]; - if (image._fullImageSize > imageSizeRemaining) { + if (image._imageSize > imageSizeRemaining) { break; } - ktxData -= image._fullImageSize; - texture->assignStoredMip(static_cast(level), image._fullImageSize, ktxData); + ktxData -= image._imageSize; + texture->assignStoredMip(static_cast(level), image._imageSize, ktxData); ktxData -= ktx::IMAGE_SIZE_WIDTH; - imageSizeRemaining -= (image._fullImageSize + ktx::IMAGE_SIZE_WIDTH); + imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH); } // We replace the texture with the one stored in the cache. This deals with the possible race condition of two different From 805d23567dd17277c812a33f79377d881e817ced Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 16 May 2017 16:02:37 -0700 Subject: [PATCH 115/135] fix simple typo --- interface/src/avatar/MyAvatar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index cbd51aaaf9..521d6e195c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -125,7 +125,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose) Q_PROPERTY(float energy READ getEnergy WRITE setEnergy) - Q_PROPERTY(float isAway READ getIsAway WRITE setAway) + Q_PROPERTY(bool isAway READ getIsAway WRITE setAway) Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled) Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled) From d065b569d388340ae9ff0df5902067c4b3994859 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 16 May 2017 16:03:13 -0700 Subject: [PATCH 116/135] support for variable avatar mass by size --- interface/src/avatar/AvatarManager.cpp | 1 + interface/src/avatar/AvatarMotionState.cpp | 3 -- interface/src/avatar/MyAvatar.cpp | 1 + .../src/avatar/MyCharacterController.cpp | 30 +++++++++++--- interface/src/avatar/MyCharacterController.h | 4 ++ .../src/avatars-renderer/Avatar.cpp | 11 +++++ .../src/avatars-renderer/Avatar.h | 1 + libraries/avatars/src/AvatarData.cpp | 4 +- libraries/avatars/src/AvatarData.h | 3 ++ libraries/physics/src/CharacterController.cpp | 7 +++- libraries/physics/src/CharacterController.h | 1 + libraries/physics/src/ObjectMotionState.cpp | 41 +++++++++++++++++-- libraries/physics/src/ObjectMotionState.h | 12 +++--- 13 files changed, 98 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1306ce03ea..d47e4cfd10 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -189,6 +189,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); if (shape) { AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); + motionState->setMass(avatar->computeMass()); avatar->setPhysicsCallback([=] (uint32_t flags) { motionState->addDirtyFlags(flags); }); _motionStates.insert(avatar.get(), motionState); _motionStatesToAddToPhysics.insert(motionState); diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 0305634400..91c83afcbd 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -19,9 +19,6 @@ AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { assert(_avatar); _type = MOTIONSTATE_TYPE_AVATAR; - if (_shape) { - _mass = 100.0f; // HACK - } } AvatarMotionState::~AvatarMotionState() { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3da9b8a214..ec4da2c2a1 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -236,6 +236,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : }); connect(rig.get(), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete())); + _characterController.setDensity(_density); } MyAvatar::~MyAvatar() { diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 0cdbc77626..3d98a0e604 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -49,12 +49,9 @@ void MyCharacterController::updateShapeIfNecessary() { // create RigidBody if it doesn't exist if (!_rigidBody) { btCollisionShape* shape = computeShape(); - - // HACK: use some simple mass property defaults for now - const btScalar DEFAULT_AVATAR_MASS = 100.0f; - const btVector3 DEFAULT_AVATAR_INERTIA_TENSOR(30.0f, 8.0f, 30.0f); - - _rigidBody = new btRigidBody(DEFAULT_AVATAR_MASS, nullptr, shape, DEFAULT_AVATAR_INERTIA_TENSOR); + btScalar mass = 1.0f; + btVector3 inertia(1.0f, 1.0f, 1.0f); + _rigidBody = new btRigidBody(mass, nullptr, shape, inertia); } else { btCollisionShape* shape = _rigidBody->getCollisionShape(); if (shape) { @@ -63,6 +60,7 @@ void MyCharacterController::updateShapeIfNecessary() { shape = computeShape(); _rigidBody->setCollisionShape(shape); } + updateMassProperties(); _rigidBody->setSleepingThresholds(0.0f, 0.0f); _rigidBody->setAngularFactor(0.0f); @@ -331,3 +329,23 @@ void MyCharacterController::initRayShotgun(const btCollisionWorld* world) { } } } + +void MyCharacterController::updateMassProperties() { + assert(_rigidBody); + // the inertia tensor of a capsule with Y-axis of symmetry, radius R and cylinder height H is: + // Ix = density * (volumeCylinder * (H^2 / 12 + R^2 / 4) + volumeSphere * (2R^2 / 5 + H^2 / 2 + 3HR / 8)) + // Iy = density * (volumeCylinder * (R^2 / 2) + volumeSphere * (2R^2 / 5) + btScalar r2 = _radius * _radius; + btScalar h2 = 4.0f * _halfHeight * _halfHeight; + btScalar volumeSphere = 4.0f * PI * r2 * _radius / 3.0f; + btScalar volumeCylinder = TWO_PI * r2 * 2.0f * _halfHeight; + btScalar cylinderXZ = volumeCylinder * (h2 / 12.0f + r2 / 4.0f); + btScalar capsXZ = volumeSphere * (2.0f * r2 / 5.0f + h2 / 2.0f + 6.0f * _halfHeight * _radius / 8.0f); + btScalar inertiaXZ = _density * (cylinderXZ + capsXZ); + btScalar inertiaY = _density * ((volumeCylinder * r2 / 2.0f) + volumeSphere * (2.0f * r2 / 5.0f)); + btVector3 inertia(inertiaXZ, inertiaY, inertiaXZ); + + btScalar mass = _density * (volumeCylinder + volumeSphere); + + _rigidBody->setMassProps(mass, inertia); +} diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index 6b38736352..fd9caface2 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -40,8 +40,11 @@ public: /// return true if RayShotgun hits anything bool testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result); + void setDensity(btScalar density) { _density = density; } + protected: void initRayShotgun(const btCollisionWorld* world); + void updateMassProperties() override; private: btConvexHullShape* computeShape() const; @@ -52,6 +55,7 @@ protected: // shotgun scan data btAlignedObjectArray _topPoints; btAlignedObjectArray _bottomPoints; + btScalar _density { 1.0f }; }; #endif // hifi_MyCharacterController_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 664b0094f4..82e571d0e8 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1293,6 +1293,17 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { radius = halfExtents.x; } +float Avatar::computeMass() { + float radius; + glm::vec3 start, end; + getCapsule(start, end, radius); + // NOTE: + // volumeOfCapsule = volumeOfCylinder + volumeOfSphere + // volumeOfCapsule = (2PI * R^2 * H) + (4PI * R^3 / 3) + // volumeOfCapsule = 2PI * R^2 * (H + 2R/3) + return _density * TWO_PI * radius * radius * (glm::length(end - start) + 2.0f * radius / 3.0f); +} + // virtual void Avatar::rebuildCollisionShape() { addPhysicsFlags(Simulation::DIRTY_SHAPE); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 20704a08b2..148821db1e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -196,6 +196,7 @@ public: virtual void computeShapeInfo(ShapeInfo& shapeInfo); void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); + float computeMass(); using SpatiallyNestable::setPosition; virtual void setPosition(const glm::vec3& position) override; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 26afed152f..c23363e6b6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -52,6 +52,7 @@ const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData"; static const int TRANSLATION_COMPRESSION_RADIX = 12; static const int SENSOR_TO_WORLD_SCALE_RADIX = 10; static const float AUDIO_LOUDNESS_SCALE = 1024.0f; +static const float DEFAULT_AVATAR_DENSITY = 1000.0f; // density of water #define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0) @@ -65,7 +66,8 @@ AvatarData::AvatarData() : _headData(NULL), _errorLogExpiry(0), _owningAvatarMixer(), - _targetVelocity(0.0f) + _targetVelocity(0.0f), + _density(DEFAULT_AVATAR_DENSITY) { setBodyPitch(0.0f); setBodyYaw(-90.0f); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e6e0571878..c2bf98287a 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -626,6 +626,8 @@ public: _identityUpdatedAt = usecTimestampNow(); } + float getDensity() const { return _density; } + signals: void displayNameChanged(); @@ -782,6 +784,7 @@ protected: bool _identityDataChanged { false }; quint64 _identityUpdatedAt { 0 }; + float _density; private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index e4ff1b0b44..ee240a6aac 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -112,6 +112,9 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _dynamicsWorld = nullptr; } int16_t collisionGroup = computeCollisionGroup(); + if (_rigidBody) { + updateMassProperties(); + } if (world && _rigidBody) { // add to new world _dynamicsWorld = world; @@ -127,7 +130,9 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup)); _ghost.setCollisionWorld(_dynamicsWorld); _ghost.setRadiusAndHalfHeight(_radius, _halfHeight); - _ghost.setWorldTransform(_rigidBody->getWorldTransform()); + if (_rigidBody) { + _ghost.setWorldTransform(_rigidBody->getWorldTransform()); + } } if (_dynamicsWorld) { if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 0a11fad0b7..6790495ff8 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -128,6 +128,7 @@ protected: void setState(State state); #endif + virtual void updateMassProperties() = 0; void updateGravity(); void updateUpAxis(const glm::quat& rotation); bool checkForSupport(btCollisionWorld* collisionWorld); diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 503b39dc1c..5a36d69035 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -63,10 +63,7 @@ ShapeManager* ObjectMotionState::getShapeManager() { } ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) : - _motionType(MOTION_TYPE_STATIC), _shape(shape), - _body(nullptr), - _mass(0.0f), _lastKinematicStep(worldSimulationStep) { } @@ -74,7 +71,43 @@ ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) : ObjectMotionState::~ObjectMotionState() { assert(!_body); setShape(nullptr); - _type = MOTIONSTATE_TYPE_INVALID; +} + +void ObjectMotionState::setMass(float mass) { + _density = 1.0f; + if (_shape) { + // we compute the density for the current shape's Aabb volume + // and save that instead of the total mass + btTransform transform; + transform.setIdentity(); + btVector3 minCorner, maxCorner; + _shape->getAabb(transform, minCorner, maxCorner); + btVector3 diagonal = maxCorner - minCorner; + float volume = diagonal.getX() * diagonal.getY() * diagonal.getZ(); + if (volume > EPSILON) { + _density = fabsf(mass) / volume; + } + } +} + +float ObjectMotionState::getMass() const { + if (_shape) { + // scale the density by the current Aabb volume to get mass + btTransform transform; + transform.setIdentity(); + btVector3 minCorner, maxCorner; + _shape->getAabb(transform, minCorner, maxCorner); + btVector3 diagonal = maxCorner - minCorner; + float volume = diagonal.getX() * diagonal.getY() * diagonal.getZ(); + + // cap the max mass for numerical stability + const float MIN_OBJECT_MASS = 0.0f; + const float MAX_OBJECT_DENSITY = 20000.0f; // kg/m^3 density of Tungsten + const float MAX_OBJECT_VOLUME = 1.0e6f; + const float MAX_OBJECT_MASS = MAX_OBJECT_DENSITY * MAX_OBJECT_VOLUME; + return glm::clamp(_density * volume, MIN_OBJECT_MASS, MAX_OBJECT_MASS); + } + return 0.0f; } void ObjectMotionState::setBodyLinearVelocity(const glm::vec3& velocity) const { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 645bd6fc14..1e582ea854 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -93,8 +93,8 @@ public: MotionStateType getType() const { return _type; } virtual PhysicsMotionType getMotionType() const { return _motionType; } - void setMass(float mass) { _mass = fabsf(mass); } - float getMass() { return _mass; } + void setMass(float mass); + float getMass() const; void setBodyLinearVelocity(const glm::vec3& velocity) const; void setBodyAngularVelocity(const glm::vec3& velocity) const; @@ -159,12 +159,12 @@ protected: void setRigidBody(btRigidBody* body); virtual void setShape(const btCollisionShape* shape); - MotionStateType _type = MOTIONSTATE_TYPE_INVALID; // type of MotionState - PhysicsMotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC + MotionStateType _type { MOTIONSTATE_TYPE_INVALID }; // type of MotionState + PhysicsMotionType _motionType { MOTION_TYPE_STATIC }; // type of motion: KINEMATIC, DYNAMIC, or STATIC const btCollisionShape* _shape; - btRigidBody* _body; - float _mass; + btRigidBody* _body { nullptr }; + float _density { 1.0f }; uint32_t _lastKinematicStep; bool _hasInternalKinematicChanges { false }; From 1e4e2cc1b4810247bb26c36d2d393a64d249d8f7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 16 May 2017 16:22:40 -0700 Subject: [PATCH 117/135] when a parent entity is deleted, delete its children in the local tree, rather than waiting for the server to tell us about it --- libraries/entities/src/EntityTree.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 457b7c21f6..6975d017b0 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -452,6 +452,13 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(getThisPointer(), entityID); + + existingEntity->forEachDescendant([&](SpatiallyNestablePointer descendant) { + auto descendantID = descendant->getID(); + theOperator.addEntityIDToDeleteList(descendantID); + emit deletingEntity(descendantID); + }); + recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; From b9ddc3b19f5ada6d2e6a30e83914fdbc3e2950c5 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 16 May 2017 16:25:22 -0700 Subject: [PATCH 118/135] Improve robustness of display name focus in PAL --- interface/resources/qml/hifi/NameCard.qml | 98 ++++++++++++----------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 0927d46fdf..a86defdfd7 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -132,62 +132,16 @@ Item { color: hifi.colors.textFieldLightBackground border.color: hifi.colors.blueHighlight border.width: 0 - TextInput { - id: myDisplayNameText - // Properties - text: thisNameCard.displayName - maximumLength: 256 - clip: true - // Size - width: parent.width - height: parent.height - // Anchors - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - anchors.right: parent.right - anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin - // Style - color: hifi.colors.darkGray - FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } - font.family: firaSansSemiBold.name - font.pixelSize: displayNameTextPixelSize - selectionColor: hifi.colors.blueAccent - selectedTextColor: "black" - // Text Positioning - verticalAlignment: TextInput.AlignVCenter - horizontalAlignment: TextInput.AlignLeft - autoScroll: false; - // Signals - onEditingFinished: { - if (MyAvatar.displayName !== text) { - MyAvatar.displayName = text; - UserActivityLogger.palAction("display_name_change", text); - } - cursorPosition = 0 - focus = false - myDisplayName.border.width = 0 - color = hifi.colors.darkGray - pal.currentlyEditingDisplayName = false - autoScroll = false; - } - } MouseArea { anchors.fill: parent hoverEnabled: true onClicked: { - myDisplayName.border.width = 1 - myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll(); - myDisplayNameText.focus = true - myDisplayNameText.color = "black" - pal.currentlyEditingDisplayName = true - myDisplayNameText.autoScroll = true; + myDisplayNameText.focus = true; + myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX - myDisplayNameText.anchors.leftMargin, mouseY, TextInput.CursorOnCharacter); } onDoubleClicked: { myDisplayNameText.selectAll(); myDisplayNameText.focus = true; - pal.currentlyEditingDisplayName = true - myDisplayNameText.autoScroll = true; } onEntered: myDisplayName.color = hifi.colors.lightGrayText; onExited: myDisplayName.color = hifi.colors.textFieldLightBackground; @@ -207,6 +161,54 @@ Item { verticalAlignment: Text.AlignVCenter color: hifi.colors.baseGray } + TextInput { + id: myDisplayNameText + // Properties + text: thisNameCard.displayName + maximumLength: 256 + clip: true + // Size + width: parent.width + height: parent.height + // Anchors + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.right: parent.right + anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin + // Style + FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + font.family: firaSansSemiBold.name + font.pixelSize: displayNameTextPixelSize + selectionColor: hifi.colors.blueAccent + selectedTextColor: "black" + // Text Positioning + verticalAlignment: TextInput.AlignVCenter + horizontalAlignment: TextInput.AlignLeft + autoScroll: false; + // Signals + onEditingFinished: { + if (MyAvatar.displayName !== text) { + MyAvatar.displayName = text; + UserActivityLogger.palAction("display_name_change", text); + } + focus = false; + } + onFocusChanged: { + if (focus === true) { + myDisplayName.border.width = 1 + color = "black" + autoScroll = true; + pal.currentlyEditingDisplayName = true + } else { + myDisplayName.border.width = 0 + color: hifi.colors.darkGray + cursorPosition = 0; + autoScroll = false; + pal.currentlyEditingDisplayName = false + } + } + } } // DisplayName container for others' cards Item { From 04fcdfc4a2ccaa214bdba8ac71d17050c3cfaac0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 17 May 2017 12:23:53 +1200 Subject: [PATCH 119/135] Add domain to record script user activity logging --- scripts/system/record.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/system/record.js b/scripts/system/record.js index 3db82696ef..acc81f2526 100644 --- a/scripts/system/record.js +++ b/scripts/system/record.js @@ -37,6 +37,12 @@ Window.alert(message); } + function logDetails() { + return { + current_domain: location.placename + }; + } + RecordingIndicator = (function () { // Displays "recording" overlay. @@ -181,7 +187,7 @@ recordingState = IDLE; log("Finish recording"); - UserActivityLogger.logAction("record_finish_recording"); + UserActivityLogger.logAction("record_finish_recording", logDetails()); playSound(finishRecordingSound); Recording.stopRecording(); RecordingIndicator.hide(); @@ -519,7 +525,7 @@ value: Player.numberOfPlayers() })); updateRecordingStatus(!Recorder.isIdle()); - UserActivityLogger.logAction("record_open_dialog"); + UserActivityLogger.logAction("record_open_dialog", logDetails()); break; case STOP_PLAYING_RECORDING_ACTION: // Stop the specified player. @@ -530,7 +536,7 @@ recording = Window.browseAssets("Select Recording to Play", "recordings", "*.hfr"); if (recording) { log("Load recording " + recording); - UserActivityLogger.logAction("record_load_recording"); + UserActivityLogger.logAction("record_load_recording", logDetails()); Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation); } break; @@ -660,7 +666,7 @@ isConnected = Window.location.isConnected; Script.update.connect(onUpdate); - UserActivityLogger.logAction("record_run_script"); + UserActivityLogger.logAction("record_run_script", logDetails()); } function tearDown() { From c678fd67eddad7298342445c45fed4eb6c64c527 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Wed, 17 May 2017 14:52:11 +0100 Subject: [PATCH 120/135] change default particle texture to production --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a6cbca008f..f39165f3df 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -527,7 +527,7 @@ var toolBar = (function () { alphaFinish: 0, polarStart: 0, polarFinish: 0, - textures: "https://raw.githubusercontent.com/Triplelexx/hifi/f98c3556fd0011150e35dc20275434b6321ac014/interface/resources/images/wispy-smoke.png" + textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png" }); }); From 97ed7195069c864544ff56daa97960b954779455 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 17 May 2017 10:40:45 -0700 Subject: [PATCH 121/135] cap scripted linear and angular velocity inputs --- libraries/entities/src/EntityItem.cpp | 52 ++++++++++++++++++--------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index a6de541958..3e858f48be 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1691,14 +1691,20 @@ void EntityItem::updateVelocity(const glm::vec3& value) { setLocalVelocity(Vectors::ZERO); } } else { - const float MIN_LINEAR_SPEED = 0.001f; - if (glm::length(value) < MIN_LINEAR_SPEED) { - velocity = ENTITY_ITEM_ZERO_VEC3; - } else { - velocity = value; + float speed = glm::length(value); + if (!isnan(speed)) { + const float MIN_LINEAR_SPEED = 0.001f; + const float MAX_LINEAR_SPEED = 270.0f; // 3m per step at 90Hz + if (speed < MIN_LINEAR_SPEED) { + velocity = ENTITY_ITEM_ZERO_VEC3; + } else if (speed > MAX_LINEAR_SPEED) { + velocity = (MAX_LINEAR_SPEED / speed) * value; + } else { + velocity = value; + } + setLocalVelocity(velocity); + _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; } - setLocalVelocity(velocity); - _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; } } } @@ -1723,8 +1729,16 @@ void EntityItem::updateGravity(const glm::vec3& value) { if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { _gravity = Vectors::ZERO; } else { - _gravity = value; - _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; + float magnitude = glm::length(value); + if (!isnan(magnitude)) { + const float MAX_ACCELERATION_OF_GRAVITY = 10.0f * 9.8f; // 10g + if (magnitude > MAX_ACCELERATION_OF_GRAVITY) { + _gravity = (MAX_ACCELERATION_OF_GRAVITY / magnitude) * value; + } else { + _gravity = value; + } + _dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY; + } } } } @@ -1735,14 +1749,20 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) { if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { setLocalAngularVelocity(Vectors::ZERO); } else { - const float MIN_ANGULAR_SPEED = 0.0002f; - if (glm::length(value) < MIN_ANGULAR_SPEED) { - angularVelocity = ENTITY_ITEM_ZERO_VEC3; - } else { - angularVelocity = value; + float speed = glm::length(value); + if (!isnan(speed)) { + const float MIN_ANGULAR_SPEED = 0.0002f; + const float MAX_ANGULAR_SPEED = 9.0f * TWO_PI; // 1/10 rotation per step at 90Hz + if (speed < MIN_ANGULAR_SPEED) { + angularVelocity = ENTITY_ITEM_ZERO_VEC3; + } else if (speed > MAX_ANGULAR_SPEED) { + angularVelocity = (MAX_ANGULAR_SPEED / speed) * value; + } else { + angularVelocity = value; + } + setLocalAngularVelocity(angularVelocity); + _dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY; } - setLocalAngularVelocity(angularVelocity); - _dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY; } } } From 3570bd905e6e48d5eec1d43abae2ce75c62a235d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 17 May 2017 11:55:23 -0700 Subject: [PATCH 122/135] update for spring to tractor name-change --- scripts/developer/tests/dynamics/dynamics-tests.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/developer/tests/dynamics/dynamics-tests.html b/scripts/developer/tests/dynamics/dynamics-tests.html index 0f324e121c..6ef82c4b70 100644 --- a/scripts/developer/tests/dynamics/dynamics-tests.html +++ b/scripts/developer/tests/dynamics/dynamics-tests.html @@ -9,8 +9,8 @@ lifetime:


-
- A platform with a lever. The lever can be moved in a cone and rotated. A spring brings it back to its neutral position. +
+ A platform with a lever. The lever can be moved in a cone and rotated. A tractor brings it back to its neutral position.

A grabbable door with a hinge between it and world-space. @@ -31,7 +31,7 @@ A chain of spheres connected by ball-and-socket joints coincident-with the spheres.

- A self-righting ragdoll. The head is on a weak spring vs the body. + A self-righting ragdoll. The head is on a weak tractor vs the body. From 9b1fe60b7c3f3407f240a430dcf33ea8a437fb40 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 17 May 2017 11:55:31 -0700 Subject: [PATCH 123/135] suppress frequest log-spam from constraints. --- libraries/physics/src/ObjectConstraintBallSocket.cpp | 6 ++++++ libraries/physics/src/ObjectConstraintConeTwist.cpp | 6 ++++++ libraries/physics/src/ObjectConstraintHinge.cpp | 6 ++++++ libraries/physics/src/ObjectConstraintSlider.cpp | 6 ++++++ 4 files changed, 24 insertions(+) diff --git a/libraries/physics/src/ObjectConstraintBallSocket.cpp b/libraries/physics/src/ObjectConstraintBallSocket.cpp index fb36a49e92..9dd85954a3 100644 --- a/libraries/physics/src/ObjectConstraintBallSocket.cpp +++ b/libraries/physics/src/ObjectConstraintBallSocket.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "QVariantGLM.h" #include "EntityTree.h" @@ -83,6 +85,9 @@ btTypedConstraint* ObjectConstraintBallSocket::getConstraint() { return constraint; } + static QString repeatedBallSocketNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex( + "ObjectConstraintBallSocket::getConstraint -- no rigidBody.*"); + btRigidBody* rigidBodyA = getRigidBody(); if (!rigidBodyA) { qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyA"; @@ -94,6 +99,7 @@ btTypedConstraint* ObjectConstraintBallSocket::getConstraint() { btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); if (!rigidBodyB) { + qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyB"; return nullptr; } diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp index 15d9378443..49f926af81 100644 --- a/libraries/physics/src/ObjectConstraintConeTwist.cpp +++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "QVariantGLM.h" #include "EntityTree.h" @@ -94,6 +96,9 @@ btTypedConstraint* ObjectConstraintConeTwist::getConstraint() { return constraint; } + static QString repeatedConeTwistNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex( + "ObjectConstraintConeTwist::getConstraint -- no rigidBody.*"); + btRigidBody* rigidBodyA = getRigidBody(); if (!rigidBodyA) { qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyA"; @@ -125,6 +130,7 @@ btTypedConstraint* ObjectConstraintConeTwist::getConstraint() { btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); if (!rigidBodyB) { + qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyB"; return nullptr; } diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index dbadf433c3..52be64796a 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "QVariantGLM.h" #include "EntityTree.h" @@ -93,6 +95,9 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() { return constraint; } + static QString repeatedHingeNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex( + "ObjectConstraintHinge::getConstraint -- no rigidBody.*"); + btRigidBody* rigidBodyA = getRigidBody(); if (!rigidBodyA) { qCDebug(physics) << "ObjectConstraintHinge::getConstraint -- no rigidBodyA"; @@ -110,6 +115,7 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() { // This hinge is between two entities... find the other rigid body. btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); if (!rigidBodyB) { + qCDebug(physics) << "ObjectConstraintHinge::getConstraint -- no rigidBodyB"; return nullptr; } diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index 3716960a6e..ded9ad47e6 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "QVariantGLM.h" #include "EntityTree.h" @@ -85,6 +87,9 @@ btTypedConstraint* ObjectConstraintSlider::getConstraint() { return constraint; } + static QString repeatedSliderNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex( + "ObjectConstraintSlider::getConstraint -- no rigidBody.*"); + btRigidBody* rigidBodyA = getRigidBody(); if (!rigidBodyA) { qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyA"; @@ -116,6 +121,7 @@ btTypedConstraint* ObjectConstraintSlider::getConstraint() { btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); if (!rigidBodyB) { + qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyB"; return nullptr; } From 0d779779364606de02c30d6c9b085a968a148a22 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 17 May 2017 12:11:15 -0700 Subject: [PATCH 124/135] Enable Map/List storage in the settings --- libraries/shared/src/SettingHelpers.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/libraries/shared/src/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp index cbda4e4096..20d6170fc4 100644 --- a/libraries/shared/src/SettingHelpers.cpp +++ b/libraries/shared/src/SettingHelpers.cpp @@ -126,17 +126,6 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { } switch (variantType) { - case QVariant::Map: { - auto varmap = variant.toMap(); - for (auto mapit = varmap.cbegin(); mapit != varmap.cend(); ++mapit) { - auto& mapkey = mapit.key(); - auto& mapvariant = mapit.value(); - object.insert(key + "/" + mapkey, QJsonValue::fromVariant(mapvariant)); - } - break; - } - - case QVariant::List: case QVariant::Hash: { qCritical() << "Unsupported variant type" << variant.typeName(); Q_ASSERT(false); @@ -152,6 +141,8 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { case QVariant::UInt: case QVariant::Bool: case QVariant::Double: + case QVariant::Map: + case QVariant::List: object.insert(key, QJsonValue::fromVariant(variant)); break; From ab3ea65d5417a661bb561de88e3f62b099b1f7af Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 17 May 2017 12:36:12 -0700 Subject: [PATCH 125/135] Simply add protections when accessing the mip bytes of a texture when provided from a KtxStorage --- libraries/gpu/src/gpu/Texture_ktx.cpp | 11 ++++++++++- libraries/shared/src/shared/Storage.cpp | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 524fd0a88c..27d92fecf2 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -210,7 +210,16 @@ PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face); if (faceSize != 0 && faceOffset != 0) { auto file = maybeOpenFile(); - result = file->createView(faceSize, faceOffset)->toMemoryStorage(); + if (file) { + auto storageView = file->createView(faceSize, faceOffset); + if (storageView) { + return storageView->toMemoryStorage(); + } else { + qWarning() << "Failed to get a valid storageView for faceSize=" << faceSize << " faceOffset=" << faceOffset << "out of valid file " << QString::fromStdString(_filename); + } + } else { + qWarning() << "Failed to get a valid file out of maybeOpenFile " << QString::fromStdString(_filename); + } } return result; } diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index f6585e6ecb..e479559e6a 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -25,7 +25,9 @@ StoragePointer Storage::createView(size_t viewSize, size_t offset) const { viewSize = selfSize; } if ((viewSize + offset) > selfSize) { - throw std::runtime_error("Invalid mapping range"); + return StoragePointer(); + //TODO: Disable te exception for now and return an empty storage instead. + //throw std::runtime_error("Invalid mapping range"); } return std::make_shared(shared_from_this(), viewSize, data() + offset); } From 0f0ad11344c50f9e6c2f09548318127556e9c982 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 17 May 2017 13:47:24 -0700 Subject: [PATCH 126/135] Ensure avatar identity packet at mixer. --- assignment-client/src/avatars/AvatarMixerClientData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 1449005246..76519466b5 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -145,7 +145,7 @@ private: std::unordered_map> _lastOtherAvatarSentJoints; uint64_t _identityChangeTimestamp; - bool _avatarSessionDisplayNameMustChange{ false }; + bool _avatarSessionDisplayNameMustChange{ true }; int _numAvatarsSentLastFrame = 0; int _numFramesSinceAdjustment = 0; From d4752926b8f35d6ff093d9702e6bda8082fba812 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 17 May 2017 13:11:37 -0700 Subject: [PATCH 127/135] First pass - checking performance --- interface/src/Application.cpp | 3 +-- libraries/avatars/src/AvatarData.cpp | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3a09a2181c..1d31a84ed4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5258,9 +5258,8 @@ void Application::nodeActivated(SharedNodePointer node) { } if (node->getType() == NodeType::AvatarMixer) { - // new avatar mixer, send off our identity packet right away + // new avatar mixer, send off our identity packet on next update loop getMyAvatar()->markIdentityDataChanged(); - getMyAvatar()->sendIdentityPacket(); getMyAvatar()->resetLastSent(); } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index eebb9c2633..7ea48db745 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1588,8 +1588,6 @@ void AvatarData::setDisplayName(const QString& displayName) { _displayName = displayName; _sessionDisplayName = ""; - sendIdentityPacket(); - qCDebug(avatars) << "Changing display name for avatar to" << displayName; markIdentityDataChanged(); } From 689a0b5badb2479b382aee3d783e68939fb99b6f Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 17 May 2017 14:22:36 -0700 Subject: [PATCH 128/135] Switch from timestamp to sequence id for avatar identity --- libraries/avatars/src/AvatarData.cpp | 45 ++++++++------------ libraries/avatars/src/AvatarData.h | 36 ++++++++-------- libraries/networking/src/udt/PacketHeaders.h | 3 +- 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index eebb9c2633..d69af74c97 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -170,13 +170,13 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail) { AvatarDataPacket::HasFlags hasFlagsOut; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); - return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), + return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), hasFlagsOut, false, false, glm::vec3(0), nullptr, &_outboundDataRate); } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, + AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); @@ -199,7 +199,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // FIXME - // - // BUG -- if you enter a space bubble, and then back away, the avatar has wrong orientation until "send all" happens... + // BUG -- if you enter a space bubble, and then back away, the avatar has wrong orientation until "send all" happens... // this is an iFrame issue... what to do about that? // // BUG -- Resizing avatar seems to "take too long"... the avatar doesn't redraw at smaller size right away @@ -1471,13 +1471,13 @@ QStringList AvatarData::getJointNames() const { void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) { QDataStream packetStream(data); - packetStream >> identityOut.uuid - >> identityOut.skeletonModelURL + packetStream >> identityOut.uuid + >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.sessionDisplayName >> identityOut.avatarEntityData - >> identityOut.updatedAt; + >> identityOut.sequenceId; #ifdef WANT_DEBUG qCDebug(avatars) << __FUNCTION__ @@ -1500,20 +1500,14 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { } void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, const qint64 clockSkew) { - quint64 identityPacketUpdatedAt = identity.updatedAt; - if (identityPacketUpdatedAt <= (uint64_t)(abs(clockSkew))) { // Incoming timestamp is bad - compute our own timestamp - identityPacketUpdatedAt = usecTimestampNow() + clockSkew; - } - - // Consider the case where this packet is being processed on Client A, and Client A is connected to Sandbox B. - // If Client A's system clock is *ahead of* Sandbox B's system clock, "clockSkew" will be *negative*. - // If Client A's system clock is *behind* Sandbox B's system clock, "clockSkew" will be *positive*. - if ((_identityUpdatedAt > identityPacketUpdatedAt - clockSkew) && (_identityUpdatedAt != 0)) { - qCDebug(avatars) << "Ignoring late identity packet for avatar " << getSessionUUID() - << "_identityUpdatedAt (" << _identityUpdatedAt << ") is greater than identityPacketUpdatedAt - clockSkew (" << identityPacketUpdatedAt << "-" << clockSkew << ")"; + if (identity.sequenceId < _identitySequenceId) { + qCDebug(avatars) << "Ignoring older identity packet for avatar" << getSessionUUID() + << "_identitySequenceId (" << _identitySequenceId << ") is greater than" << identity.sequenceId; return; } + // otherwise, set the identitySequenceId to match the incoming identity + _identitySequenceId = identity.sequenceId; if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) { setSkeletonModelURL(identity.skeletonModelURL); @@ -1545,9 +1539,6 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC identityChanged = true; } - // use the timestamp from this identity, since we want to honor the updated times in "server clock" - // this will overwrite any changes we made locally to this AvatarData's _identityUpdatedAt - _identityUpdatedAt = identityPacketUpdatedAt - clockSkew; } QByteArray AvatarData::identityByteArray() const { @@ -1556,13 +1547,13 @@ QByteArray AvatarData::identityByteArray() const { const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL _avatarEntitiesLock.withReadLock([&] { - identityStream << getSessionUUID() - << urlToSend + identityStream << getSessionUUID() + << urlToSend << _attachmentData << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName << _avatarEntityData - << _identityUpdatedAt; + << _identitySequenceId; }); return identityData; @@ -2477,11 +2468,11 @@ QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEnt if (!jsonEntityProperties.isObject()) { qCDebug(avatars) << "bad AvatarEntityData in AvatarEntityMap" << QString(entityProperties.toHex()); } - + QVariant variantEntityProperties = jsonEntityProperties.toVariant(); QVariantMap entityPropertiesMap = variantEntityProperties.toMap(); QScriptValue scriptEntityProperties = variantMapToScriptValue(entityPropertiesMap, *engine); - + QString key = entityID.toString(); obj.setProperty(key, scriptEntityProperties); } @@ -2493,12 +2484,12 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& while (itr.hasNext()) { itr.next(); QUuid EntityID = QUuid(itr.name()); - + QScriptValue scriptEntityProperties = itr.value(); QVariant variantEntityProperties = scriptEntityProperties.toVariant(); QJsonDocument jsonEntityProperties = QJsonDocument::fromVariant(variantEntityProperties); QByteArray binaryEntityProperties = jsonEntityProperties.toBinaryData(); - + value[EntityID] = binaryEntityProperties; } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index f248874943..b4836e3a2e 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -165,15 +165,15 @@ namespace AvatarDataPacket { const size_t AVATAR_ORIENTATION_SIZE = 6; PACKED_BEGIN struct AvatarScale { - SmallFloat scale; // avatar's scale, compressed by packFloatRatioToTwoByte() + SmallFloat scale; // avatar's scale, compressed by packFloatRatioToTwoByte() } PACKED_END; const size_t AVATAR_SCALE_SIZE = 2; PACKED_BEGIN struct LookAtPosition { float lookAtPosition[3]; // world space position that eyes are focusing on. - // FIXME - unless the person has an eye tracker, this is simulated... + // FIXME - unless the person has an eye tracker, this is simulated... // a) maybe we can just have the client calculate this - // b) at distance this will be hard to discern and can likely be + // b) at distance this will be hard to discern and can likely be // descimated or dropped completely // // POTENTIAL SAVINGS - 12 bytes @@ -376,10 +376,10 @@ public: glm::vec3 getHandPosition() const; void setHandPosition(const glm::vec3& handPosition); - typedef enum { + typedef enum { NoData, PALMinimum, - MinimumData, + MinimumData, CullSmallData, IncludeSmallData, SendAllData @@ -388,7 +388,7 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail); virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, + AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const; virtual void doneEncoding(bool cullSmallChanges); @@ -417,23 +417,23 @@ public: void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. virtual void updateAttitude() {} // Tell skeleton mesh about changes - glm::quat getHeadOrientation() const { + glm::quat getHeadOrientation() const { lazyInitHeadData(); - return _headData->getOrientation(); + return _headData->getOrientation(); } - void setHeadOrientation(const glm::quat& orientation) { + void setHeadOrientation(const glm::quat& orientation) { if (_headData) { _headData->setOrientation(orientation); } } - void setLookAtPosition(const glm::vec3& lookAtPosition) { + void setLookAtPosition(const glm::vec3& lookAtPosition) { if (_headData) { - _headData->setLookAtPosition(lookAtPosition); + _headData->setLookAtPosition(lookAtPosition); } } - void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { + void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { if (_headData) { _headData->setBlendshapeCoefficients(blendshapeCoefficients); } @@ -470,7 +470,7 @@ public: void setDomainMinimumScale(float domainMinimumScale) { _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } - void setDomainMaximumScale(float domainMaximumScale) + void setDomainMaximumScale(float domainMaximumScale) { _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } // Hand State @@ -531,7 +531,7 @@ public: QString displayName; QString sessionDisplayName; AvatarEntityMap avatarEntityData; - quint64 updatedAt; + quint64 sequenceId; }; static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); @@ -548,8 +548,8 @@ public: virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); - virtual void setSessionDisplayName(const QString& sessionDisplayName) { - _sessionDisplayName = sessionDisplayName; + virtual void setSessionDisplayName(const QString& sessionDisplayName) { + _sessionDisplayName = sessionDisplayName; markIdentityDataChanged(); } @@ -626,7 +626,7 @@ public: bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called void markIdentityDataChanged() { _identityDataChanged = true; - _identityUpdatedAt = usecTimestampNow(); + _identitySequenceId++; } signals: @@ -784,7 +784,7 @@ protected: float _audioAverageLoudness { 0.0f }; bool _identityDataChanged { false }; - quint64 _identityUpdatedAt { 0 }; + quint64 _identitySequenceId { 0 }; private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 746ae80361..f88015a4e4 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -234,7 +234,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { VariableAvatarData, AvatarAsChildFixes, StickAndBallDefaultAvatar, - IdentityPacketsIncludeUpdateTime + IdentityPacketsIncludeUpdateTime, + AvatarIdentitySequenceId }; enum class DomainConnectRequestVersion : PacketVersion { From 057718bde3c6d8aff13cfff755d54c6ded3106c5 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 17 May 2017 15:14:56 -0700 Subject: [PATCH 129/135] remove clockSkew, reference new version for Avatar packets --- assignment-client/src/avatars/AvatarMixer.cpp | 10 +++++----- libraries/avatars/src/AvatarData.cpp | 2 +- libraries/avatars/src/AvatarData.h | 2 +- libraries/avatars/src/AvatarHashMap.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 998799f5e6..870149f1bc 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -402,7 +402,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); bool identityChanged = false; bool displayNameChanged = false; - avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged, senderNode->getClockSkewUsec()); + avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->flagIdentityChange(); @@ -665,12 +665,12 @@ void AvatarMixer::sendStatsPacket() { void AvatarMixer::run() { qCDebug(avatars) << "Waiting for connection to domain to request settings from domain-server."; - + // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &AvatarMixer::domainSettingsRequestComplete); connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AvatarMixer::domainSettingsRequestFailed); - + ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); } @@ -695,7 +695,7 @@ void AvatarMixer::domainSettingsRequestComplete() { // parse the settings to pull out the values we need parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); - + // start our tight loop... start(); } @@ -745,7 +745,7 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { } else { qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads."; } - + const QString AVATARS_SETTINGS_KEY = "avatars"; static const QString MIN_SCALE_OPTION = "min_avatar_scale"; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d69af74c97..57ef80000a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1499,7 +1499,7 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; } -void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, const qint64 clockSkew) { +void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) { if (identity.sequenceId < _identitySequenceId) { qCDebug(avatars) << "Ignoring older identity packet for avatar" << getSessionUUID() diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b4836e3a2e..99a437b885 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -538,7 +538,7 @@ public: // identityChanged returns true if identity has changed, false otherwise. // displayNameChanged returns true if displayName has changed, false otherwise. - void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, const qint64 clockSkew); + void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged); QByteArray identityByteArray() const; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 155ef9a0a2..2ccc64fee2 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -149,7 +149,7 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer bool identityChanged = false; bool displayNameChanged = false; // In this case, the "sendingNode" is the Avatar Mixer. - avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged, sendingNode->getClockSkewUsec()); + avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged); } } diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 82b4bf703d..9d970fa318 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -56,7 +56,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::IdentityPacketsIncludeUpdateTime); + return static_cast(AvatarMixerPacketVersion::AvatarIdentitySequenceId); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: From 78dd30f65636d42cb1766d2af299b1092e3adc28 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 17 May 2017 15:22:39 -0700 Subject: [PATCH 130/135] macosx warning fix --- libraries/animation/src/IKTarget.cpp | 2 +- libraries/animation/src/IKTarget.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/IKTarget.cpp b/libraries/animation/src/IKTarget.cpp index 18e656f583..c67c0621c3 100644 --- a/libraries/animation/src/IKTarget.cpp +++ b/libraries/animation/src/IKTarget.cpp @@ -15,7 +15,7 @@ void IKTarget::setPose(const glm::quat& rotation, const glm::vec3& translation) } void IKTarget::setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn) { - _numFlexCoefficients = std::min(numFlexCoefficientsIn, MAX_FLEX_COEFFICIENTS); + _numFlexCoefficients = std::min(numFlexCoefficientsIn, (size_t)MAX_FLEX_COEFFICIENTS); for (size_t i = 0; i < _numFlexCoefficients; i++) { _flexCoefficients[i] = flexCoefficientsIn[i]; } diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 1951d3ba7b..4f464c103c 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -41,7 +41,8 @@ public: void setWeight(float weight) { _weight = weight; } float getWeight() const { return _weight; } - static const size_t MAX_FLEX_COEFFICIENTS = 10; + enum FlexCoefficients { MAX_FLEX_COEFFICIENTS = 10 }; + private: AnimPose _pose; int _index{-1}; From 2ac5fbf91dda6e86de6b051835f0e0919fae503f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 17 May 2017 15:26:29 -0700 Subject: [PATCH 131/135] set wireless scan interval to -1 --- libraries/shared/src/SharedUtil.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 81a0eed9f3..a68d27e620 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -768,9 +768,10 @@ bool similarStrings(const QString& stringA, const QString& stringB) { } void disableQtBearerPoll() { - // to work around the Qt constant wireless scanning, set the env for polling interval very high - const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT16_MAX).toLocal8Bit(); - qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT); + // to disable the Qt constant wireless scanning, set the env for polling interval + qDebug() << "Disabling Qt wireless polling by using a negative value for QTimer::setInterval"; + const QByteArray DISABLE_BEARER_POLL_TIMEOUT = QString::number(-1).toLocal8Bit(); + qputenv("QT_BEARER_POLL_TIMEOUT", DISABLE_BEARER_POLL_TIMEOUT); } void printSystemInformation() { From 0018224fefb72e6c50041effe7b89e676fd2ef8b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 17 May 2017 16:23:39 -0700 Subject: [PATCH 132/135] moar warning fixes --- libraries/animation/src/AnimInverseKinematics.cpp | 4 ++-- libraries/animation/src/AnimInverseKinematics.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index c43d7b76dd..4471f11857 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -32,7 +32,7 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, cons numFlexCoefficients(flexCoefficientsIn.size()), jointIndex(-1) { - numFlexCoefficients = std::min(numFlexCoefficients, MAX_FLEX_COEFFICIENTS); + numFlexCoefficients = std::min(numFlexCoefficients, (size_t)MAX_FLEX_COEFFICIENTS); for (size_t i = 0; i < numFlexCoefficients; i++) { flexCoefficients[i] = flexCoefficientsIn[i]; } @@ -48,7 +48,7 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) : numFlexCoefficients(orig.numFlexCoefficients), jointIndex(orig.jointIndex) { - numFlexCoefficients = std::min(numFlexCoefficients, MAX_FLEX_COEFFICIENTS); + numFlexCoefficients = std::min(numFlexCoefficients, (size_t)MAX_FLEX_COEFFICIENTS); for (size_t i = 0; i < numFlexCoefficients; i++) { flexCoefficients[i] = orig.flexCoefficients[i]; } diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index cf03005b68..74face6d0b 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -78,7 +78,7 @@ protected: AnimInverseKinematics(const AnimInverseKinematics&) = delete; AnimInverseKinematics& operator=(const AnimInverseKinematics&) = delete; - static const size_t MAX_FLEX_COEFFICIENTS = 10; + enum FlexCoefficients { MAX_FLEX_COEFFICIENTS = 10 }; struct IKTargetVar { IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn, const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector& flexCoefficientsIn); From e8ab78089962c9150d0653cfad548f0d3ff0e995 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 17 May 2017 21:32:13 -0400 Subject: [PATCH 133/135] include details in critical variantMap parsing error --- libraries/shared/src/SettingHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp index cbda4e4096..4690416446 100644 --- a/libraries/shared/src/SettingHelpers.cpp +++ b/libraries/shared/src/SettingHelpers.cpp @@ -138,7 +138,7 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { case QVariant::List: case QVariant::Hash: { - qCritical() << "Unsupported variant type" << variant.typeName(); + qCritical() << "Unsupported variant type" << variant.typeName() << ";" << key << variant; Q_ASSERT(false); break; } From b05cb4af3b05ffc0fae69808810c438b98265a36 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 17 May 2017 20:51:34 -0700 Subject: [PATCH 134/135] fix nan --- libraries/entities/src/EntityItem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 3e858f48be..14122594fe 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1692,7 +1692,7 @@ void EntityItem::updateVelocity(const glm::vec3& value) { } } else { float speed = glm::length(value); - if (!isnan(speed)) { + if (!glm::isnan(speed)) { const float MIN_LINEAR_SPEED = 0.001f; const float MAX_LINEAR_SPEED = 270.0f; // 3m per step at 90Hz if (speed < MIN_LINEAR_SPEED) { @@ -1730,7 +1730,7 @@ void EntityItem::updateGravity(const glm::vec3& value) { _gravity = Vectors::ZERO; } else { float magnitude = glm::length(value); - if (!isnan(magnitude)) { + if (!glm::isnan(magnitude)) { const float MAX_ACCELERATION_OF_GRAVITY = 10.0f * 9.8f; // 10g if (magnitude > MAX_ACCELERATION_OF_GRAVITY) { _gravity = (MAX_ACCELERATION_OF_GRAVITY / magnitude) * value; @@ -1750,7 +1750,7 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) { setLocalAngularVelocity(Vectors::ZERO); } else { float speed = glm::length(value); - if (!isnan(speed)) { + if (!glm::isnan(speed)) { const float MIN_ANGULAR_SPEED = 0.0002f; const float MAX_ANGULAR_SPEED = 9.0f * TWO_PI; // 1/10 rotation per step at 90Hz if (speed < MIN_ANGULAR_SPEED) { From 6d2d838510e1ee6f10d4126ddac7a951ec379def Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 18 May 2017 11:46:14 -0700 Subject: [PATCH 135/135] Fix ktx image size in progrssively loaded cubemaps The size being written was the full image size (size of all faces), when it should have been the face size. --- libraries/ktx/src/ktx/Writer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index 2dcd0ea583..23f9d05596 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -149,7 +149,8 @@ namespace ktx { for (size_t i = 0; i < descriptors.size(); ++i) { auto ptr = reinterpret_cast(currentDestPtr); - *ptr = descriptors[i]._imageSize; + uint32_t imageFaceSize = descriptors[i]._faceSize; + *ptr = imageFaceSize; // the imageSize written in the ktx is the FACE size #ifdef DEBUG ptr++;